lp1471 方差

大力上一个线段树。
将方差展开为由区间平方的和与区间和两项构成的多项式,然后维护区间平方的和与区间和。
具体来说,通过将二项式展开可以得到公式:
$$ \sum_{i=l}^{r}(a_{i}+k)^2=\sum_{i=l}^{r}a_{i}^2+\sum_{i=l}^{r}*k*2+(r-l+1)*k^2 $$
套用这个公式,就可以用与维护普通的线段树相似的方法来维护这个数据结构。

#include<iostream>
#include<cstdio>
using namespace std;
#define LEN (R-L+1)
#define MID ((L+R)>>1)
#define LLEN (MID-L+1)
#define RLEN (R-MID)
#define LS (X<<1)
#define RS (X<<1|1)
struct data{
	double sm;
	double sm2;
	double lzy;
}tr[270005];//262141
inline void rnw(int X,int Len,double K){
	tr[X].sm2+=(tr[X].sm*2*K+K*K*Len);
	tr[X].sm+=(K*Len);
	tr[X].lzy+=K;
}
inline void pshd(int X,int L,int R){
	rnw(LS,LLEN,tr[X].lzy);rnw(RS,RLEN,tr[X].lzy);
	tr[X].lzy=0;
}
inline void updt(int X){
	tr[X].sm2=tr[LS].sm2+tr[RS].sm2;
	tr[X].sm=tr[LS].sm+tr[RS].sm;
}
inline void add(int X,int L,int R,int A,int B,double K){
	if(L>=A&&R<=B){
		rnw(X,LEN,K);
		return;
	}
	pshd(X,L,R);
	if(A<=MID){
		add(LS,L,MID,A,B,K);
	}
	if(B>MID){
		add(RS,MID+1,R,A,B,K);
	}
	updt(X);
}
inline double qrySm(int X,int L,int R,int A,int B){
	if(L>=A&&R<=B){
		return tr[X].sm;
	}
	pshd(X,L,R);
	double rt=0;
	if(A<=MID){
		rt+=qrySm(LS,L,MID,A,B);
	}
	if(B>MID){
		rt+=qrySm(RS,MID+1,R,A,B);
	}
	return rt;
}
inline double qrySm2(int X,int L,int R,int A,int B){
	if(L>=A&&R<=B){
		return tr[X].sm2;
	}
	pshd(X,L,R);
	double rt=0;
	if(A<=MID){
		rt+=qrySm2(LS,L,MID,A,B);
	}
	if(B>MID){
		rt+=qrySm2(RS,MID+1,R,A,B);
	}
	return rt;
}
int n,m;
double a[100005];
inline void build(int X,int L,int R){
	if(L==R){
		tr[X].sm=a[L];
		tr[X].sm2=a[L]*a[R];
		return;
	}
	build(LS,L,MID);build(RS,MID+1,R);
	updt(X);
}
void init(){
	scanf("%d%d",&n,&m);
	double x;
	for(int i=1;i<=n;++i){
		scanf("%lf",a+i);
	}
	build(1,1,n);
	int op,l,r;
	while(m--){
		scanf("%d%d%d",&op,&l,&r);
		if(op==1){
			scanf("%lf",&x);
			add(1,1,n,l,r,x);
		}else if(op==2){
			printf("%.4lf\n",qrySm(1,1,n,l,r)/(r-l+1));
		}else{
			x=qrySm(1,1,n,l,r);
			printf("%.4lf\n",(qrySm2(1,1,n,l,r)-x*x/(r-l+1))/(r-l+1));
		}
	}
}
int main(){
	init();
	return 0;
}

 

lp2822 NOIP2016 组合数问题

基础数论题。
首先我们知道,任何数的逆元模k,都不可能等于零。
故而我们不必考虑k是否是质数。
然后我们考虑先预处理出哪些\(n,m\)满足\(C_{n}^{m}≡0(mod\ k)\)
接着前缀和即可。
由于\(n,m\)较小,可以考虑用递推。

#include<iostream>
#include<cstdio> 
#include<cstring>
using namespace std;
int sm[2005][2005],C[2005][2005];
int n,m,k;
void prpr(){
    memset(sm,0,sizeof(sm));
    C[0][0]=C[1][1]=1;
    for(int i=1;i<=2000;++i){
        C[i][0]=1,C[i][i]=1;
    }
    for(int i=2;i<=2000;++i){
        for(int j=1;j<=i;++j){
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%k;
        }
    }
    for(int i=2;i<=2000;++i){
        for(int j=1;j<=i;++j){
            sm[i][j]=sm[i-1][j]+sm[i][j-1]-sm[i-1][j-1];
            if(C[i][j]==0){
                ++sm[i][j];
            }
        }
        sm[i][i+1]=sm[i][i];
    }
}
void init(){
    scanf("%d%d",&n,&m);
    printf("%d\n",(m>n)?(sm[n][n]):(sm[n][m]));
}
int main(){
    int T;
    scanf("%d%d",&T,&k);
    prpr();
    while(T--){
        init();
    }
    return 0;
}

 

lp1850 NOIP2016 换教室

一道优\(d\acute{u}\)秀\(li\acute{u}\)的期望DP。
简述题意:
原有一个包括\(n\)个节点的路径,你可以用\(m\)次操作,每一次操作可以把第\(i\)个节点从\(c_{i}\)换到\(b_{i}\)。
对于每一次操作,有\(p_{i}\)的概率成功。求最优决策下路径长度的期望。

首先考虑到是稠密图,故考虑用邻接矩阵来处理。不过需要注意读入时应当取较小值,以及每个点到其本身的距离为\(0\)
首先用\(Floyd\)预处理出所有点之间的距离。然后我们定义\(f_{i,j,k}\)表示,当前在决定第\(i\)个节点,已经使用了\(j\)次机会,当前选择为\(k\)时的期望路径长度。
那么我们考虑转移。
对于每一个状态,我们有两种选择:使用机会或者不使用机会。故而我们尝试推理状态转移方程:
首先,如果不使用机会的话,有两种情况可以转移过来,分别是,上一次使用了机会,或者上一次没有使用机会,故:
$$f_{i,j,0}=Min(f_{i-1,j,0}+mp_{c_{i-1},c_{i}},$$
$$f_{i-1,j,1}+mp_{d_{i-1},c_{i}}*p_{i-1}+mp_{c_{i-1},c_{i}}*(1-p_{i-1}));$$
如果在这个点使用机会且成功的话,则有:
$$f_{i,j-1,1}=Min(f_{i-1,j-1,0}+mp_{c_{i-1},d_{i}}*p_{i}+mp_{c_{i-1},c_{i}}*(1-p_{i}),$$
$$f_{i-1,j-1,1}+m(p_{d_{i-1},d_{i}}*p_{i-1}+mp_{c_{i-1},d_{i}}*(1-p_{i-1}))*p_{i}+$$
$$(mp_{d_{i-1},c_{i}}*p_{i-1}+mp_{c_{i-1},c_{i}}*(1-p_{i-1}))*(1-p_{i});$$
然后,只需要走到终点即可,因此:
$$ Ans=Min(f_{N,i,j})\ (i\in [0,M],j\in [0,1])$$

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define Min(_A,_B) ((_A)<(_B)?(_A):(_B))
int mp[305][305],N,M,V,E;
int c[2005],d[2005];
double p[2005],f[2005][2005][2];
void init(){
    memset(mp,0x3f,sizeof(mp));
    scanf("%d%d%d%d",&N,&M,&V,&E);
    for(int i=1;i<=N;++i){
        scanf("%d",&c[i]);
    }
    for(int i=1;i<=N;++i){
        scanf("%d",&d[i]);
    }
    for(int i=1;i<=N;++i){
        scanf("%lf",&p[i]);
    }
    int u,v,w;
    for(int i=1;i<=E;++i){
        scanf("%d%d%d",&u,&v,&w);
        mp[u][v]=Min(mp[u][v],w),mp[v][u]=Min(mp[u][v],w);
    }
    for(int i=1;i<=V;++i){
        mp[i][i]=0;
    }
    for(int k=1;k<=V;++k){
        for(int i=1;i<=V;++i){
            for(int j=1;j<=V;++j){
                mp[i][j]=Min(mp[i][j],mp[i][k]+mp[k][j]); 
            }
        }
    }
    for(int i=1;i<=N;++i){
        for(int j=0;j<=M;++j){
            for(int k=0;k<=1;++k){
                f[i][j][k]=1000007;
            }
        }
    }
    f[1][0][0]=f[1][1][1]=0;
    for(int i=2;i<=N;++i){
        for(int j=0;j<=M;++j){
            f[i][j][0]=Min(f[i-1][j][0]+mp[c[i-1]][c[i]],f[i-1][j][1]+mp[d[i-1]][c[i]]*p[i-1]+mp[c[i-1]][c[i]]*(1-p[i-1]));
            if(!j){
                continue;
            }
            f[i][j][1]=Min(f[i-1][j-1][0]+mp[c[i-1]][d[i]]*p[i]+mp[c[i-1]][c[i]]*(1-p[i]),f[i-1][j-1][1]+(mp[d[i-1]][d[i]]*p[i-1]+mp[c[i-1]][d[i]]*(1-p[i-1]))*p[i]+(mp[d[i-1]][c[i]]*p[i-1]+mp[c[i-1]][c[i]]*(1-p[i-1]))*(1-p[i]));
        }
    }
    double ans=1000007;
    for(int i=0;i<=M;++i){
        for(int j=0;j<2;++j){
            ans=Min(ans,f[N][i][j]);
        }
    }
    /*
    for(int i=1;i<=N;++i){
        for(int j=0;j<=M;++j){
            for(int k=0;k<2;++k){
                printf("%.2lf ",f[i][j][k]);
            }
            printf("|");
        }
        puts("");
    }
    */
    printf("%.2lf",ans);
}
int main(){
    init();
    return 0;
}

 

lp4578 FJOI2018 所罗门王的宝藏

首先,我们可以知道,令每一行、列数字的变化为\(dx_{i},dy_{i}\),那么对于\(z_{i,j}\),一定有:
$$dx_{i}+dy_{j}=z_{i,j}$$
对这个线性方程组变形,我们可以发现,如果指定序列合法,那么:
$$ \forall x ,z_{x,j_{1}}-z_{x,j_{2}}=dy_{j_{1}}-dy_{j_{2}} $$
$$ \forall y ,z_{i_{1},y}-z_{i_{2},y}=dx_{i_{1}}-dx_{i_{2}} $$
而,当\(dx\)和\(dy\)的相对关系不矛盾时,一定可以导出一个符合题意的矩阵。
因此,我们对于每组\(x,y\),统计它们的相对关系即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,k; 
int dx[1005][1005],dy[1005][1005];
bool visx[1005][1005],visy[1005][1005];
int x[1005],y[1005],z[1005];
void init(){
    memset(dx,0,sizeof(dx));
    memset(dy,0,sizeof(dy));
    memset(visx,0,sizeof(visx));
    memset(visy,0,sizeof(visy));
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=k;++i){
        scanf("%d%d%d",&x[i],&y[i],&z[i]);
    }
    int nw;
    for(int i=1;i<k;++i){
        for(int j=i+1;j<=k;++j){
            if(x[i]==x[j]&&y[i]==y[j]&&z[i]!=z[j]){
                puts("No");
                return; 
            }
            if(x[i]==x[j]){
                nw=y[i]>y[j]?z[j]-z[i]:z[i]-z[j];
                if(visx[y[i]][y[j]]&&dx[y[i]][y[j]]!=nw){
                    puts("No");
                    return;
                }
                dx[y[i]][y[j]]=dx[y[j]][y[i]]=nw;
                visx[y[i]][y[j]]=visx[y[j]][y[i]]=1; 
            }
            if(y[i]==y[j]){
                nw=x[i]>x[j]?z[j]-z[i]:z[i]-z[j];
                if(visy[x[i]][x[j]]&&dy[x[i]][x[j]]!=nw){
                    puts("No");
                    return;
                }
                dy[x[i]][x[j]]=dy[x[j]][x[i]]=nw;
                visy[x[i]][x[j]]=visy[x[j]][x[i]]=1; 
            }
        }
    }
    for(int i=1;i<=n;++i){
        if(dx[i][i]||dy[i][i]){
            puts("No");
            return;
        }
    }
    puts("Yes");
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        init();
    }
    return 0;
} 

 

lp1654 OSU!

真是棒到不行!OSU!

首先,我们可以知道,对于长度为\(x\)的线段,在其后加上一个新的长度为1的线段,新线段的价值是\((x+1)^3\)
那么,价值的差便是:
$$3*x^2+3*x+1$$
而这样的贡献必须要当前点被选中才可行的。似乎可以得到:
$$f_{i}=f_{i-1}*(1-p_{i})+(f_{i-1}^3+3*f_{i-1}^2+3*f_{i-1}+1)*p_{i}$$
但仔细观察就可以发现这样显然是错误的。
这是因为,平方的期望不等于期望的平方。
因此,对于平方的期望,以及一次方的期望,我们应当另外维护两个函数\(g,u\),分别表示平方的期望和一次方的期望。
那么:
$$g_{i}=(g_{i-1}+2*u_{i-1}+1)*p_{i}$$
$$u_{i}=(u_{i-1}+1)*p_{i}$$
于是:
$$f_{i}=f_{i-1}+(3*g_{i-1}+3*u_{i-1}+1)*p_{i}$$
问题得解。
这一题还是很有思维难度的。

#include<iostream>
#include<cstdio>
using namespace std;
int n;
double p[100005],u[100005],g[100005],f[100005];
void init(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%lf",&p[i]);
    }
    u[0]=g[0]=f[0]=0;
    double ans=0;
    for(int i=1;i<=n;++i){
        u[i]=(u[i-1]+1)*p[i];
        g[i]=(g[i-1]+2*u[i-1]+1)*p[i];
        f[i]=f[i-1]+(3*g[i-1]+3*u[i-1]+1)*p[i];
        ans+=f[i];
    }
    printf("%.1lf",f[n]);
    
}
int main(){
    init();
    return 0;
}

CF148D Bag of mice

看到\(w,b \le 1000\),我们可以大胆猜想复杂度是\(w*b\)的(大雾)
那么我们设\(f[i][j]\)表示,包里还剩下\(i\)只白老鼠,\(j\)只黑老鼠的时候,轮到公主抽,赢的期望。
仔细看了看题意(之前题意看错了,瞎想半天,淦。),我们可以发现,公主输会发生在两种情况下:
一:包里没有白老鼠了;二:公主抽到了黑老鼠而龙抽到了白老鼠。
同时,对于一个局面,公主胜利的概率有两部分;
一:公主抽到了白球。二:公主的状态转移到的状态抽到了白球。
我们分类讨论。
首先,依据公主抽到白球的概率是\(\frac{i}{i+j}\),我们可以知道:
$$f_{i,j}+=\frac{i}{i+j}$$
然后,用填表法,我们发现,公主抽到了黑老鼠且龙抽到黑老鼠的概率是:
$$\frac{j}{i+j}*\frac{j-1}{i+j-1}$$
在这种情况下,跑出一只白老鼠的概率是:
$$\frac{j}{i+j}*\frac{j-1}{i+j-1}*\frac{i}{i+j-2}$$
跑出一只黑老鼠的概率是:
$$\frac{j}{i+j}*\frac{j-1}{i+j-1}*\frac{j-2}{i+j-2}$$
所以得到转移方程:
$$f_{i,j}=\frac{j}{i+j}*\frac{j-1}{i+j-1}+f_{i-1,j-2}*\frac{j}{i+j}*\frac{j-1}{i+j-1}*\frac{i}{i+j-2}+$$
$$f_{i,j-3}*\frac{j}{i+j}*\frac{j-1}{i+j-1}*\frac{j-2}{i+j-2}$$
于是填表法可得解。

#include<iostream>
#include<cstdio>
using namespace std;
int w,b;
double f[1005][1005];
void init(){
    scanf("%d%d",&w,&b);
    for(int i=1;i<=w;++i){
        for(int j=1;j<=b;++j){
            f[i][j]=0;
        }
    }
    for(int i=1;i<=w;++i){
        f[i][0]=1;
    }
    for(int i=1;i<=b;++i){
        f[0][i]=0;
    }
    for(int i=1;i<=w;++i){
        for(int j=1;j<=b;++j){
            f[i][j]+=(double)i/(i+j);
            if(j>=2){
                f[i][j]+=f[i-1][j-2]*(double)j/(i+j)*(j-1)/(i+j-1)*i/(i+j-2);
            }
            if(j>=3){
                f[i][j]+=f[i][j-3]*(double)j/(i+j)*(j-1)/(i+j-1)*(j-2)/(i+j-2);
            }
        }
    }
    printf("%.9lf",f[w][b]);
}
int main(){
    init();
    return 0;
}

 

sp1026 Favorite Dice

开始学习期望DP。
这是一道期望DP入门题。
首先我们设\(f[i]\)表示,已经取了\(i\)种数,距离取完的期望回合数。
根据概率的基本定理,我们可以知道,已经取了\(k\)种以后,下一种取到的是未取到过的概率是\(\frac{n-k}{n} \);而取到的是取过的概率是\(\frac{k}{n}\)
我们很容易可以知道,已经取了\(i\)种数,下一次取可能有两种情况:
一:取到的是一种新的数。
二:取到的是已经取到过的数。
但无论如何都要再取一次才有造成状态改变的空间。
故而我们得到方程:
$$ f_{i}=\frac{n-i}{n}*f_{i+1}+\frac{i}{n}*f_{i}+1 $$
即:
$$ n*f_{i}=(n-i)*f_{i+1}+i*f_{i}+n $$
从而得到:
$$ f_{i}=\frac{(n-i)*f_{i+1}+n}{n-i} $$
等价于:
$$ f_{i}=f_{i+1}+\frac{n}{n-i} $$
于是我们得到了逆向的递推方程。
然后是边界条件。很显然,\(f_{n}=0\),这是因为,已经取到\(n\)种以后,就意味着已经取完了。

#include<iostream>
#include<cstdio>
using namespace std;
double f[1005];
int n;
void init(){
    scanf("%d",&n);
    f[n]=0;
    for(int i=n-1;i>=0;--i){
        f[i]=f[i+1]+(double)n/(n-i);
    }
    printf("%.2lf\n",f[0]);
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        init();
    }
    return 0;
}

 

lp2197 NIM游戏

图文无关,因为空和白玩的游戏中没有一个是ICG类的。

NIM游戏是一类经典的博弈论题目。
众所周知,NIM游戏的结果就是把所有的答案异或起来即可。为什么可以这么做呢?
我们定义,对于一个「均衡组合博弈(ICG)」 ,我们定义两种局面状态:P(先手必败)和N(先手必胜)。
首先我们可以知道,在ICG中,博弈是一定会终止的;同时,终止局面是P局面。如果说一个局面的所有子局面都是N局面或者P局面,那么这个局面也一定是N局面或者P局面:这是因为N局面和P局面存在性质:
一个局面是P局面,当且仅当它的所有子局面都是N局面;一个局面是N局面,当且仅当它的所有子局面中存在一个是P局面。
这也就意味着,对于任何一种状态,我们都可以判定它是N状态还是P状态。
那么,初始状态的N-P性是可以判断的。
如何计算一个局面的N-P性呢?
我们定义一种运算,使得:
P局面经过这种运算只能变成N局面;
N局面经过这种运算可以变成P局面。
当我们用一个数列描述一个局面后,我们惊讶地发现:异或——这里指的是将局面中的每一个子部分异或起来——是满足这个性质的。
我们定义,异或值为零的局面是必败局面;异或值非0的局面是必胜局面。
我们将描述这个局面的数列异或起来,如果它等于零,那么任意一种「减少」操作——导致它的一个值减少的,一定会导向一种P局面;
而对于一种P局面,依据按位异或的特性,一定可以通过减少最大的数,来变更想变更的任意一位。
故而,我们发现,对于任意一种局面,我们可以用异或运算来判断它的N-P性。
事实上,两者之间并不存在那么直接的数学上的对应关系。可以将NIM游戏理解为一个数学模型。
这是一种指代关系。换句话说,为了更方便地处理它,
我们可以将这个局面转化为数学模型,而异或运算刚好满足其性质——这并不是说异或运算本身就是这个局面的变化。
当理解这一点之后,异或的意义就很显然了。

#include<iostream>
#include<cstdio>
using namespace std;
int n,a[10005];
void init(){
    scanf("%d",&n);
    int x=0;
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        x^=a[i];
    }
    if(x){
        puts("Yes");
    }else{
        puts("No");
    }
    return;
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        init();
    }
    return 0;
}

 

Lucas定理

首先给出关于Lucas定理的简要证明:
定义:
$$ a=a_{k}*p^{k}+a_{k-1}*p^{k-1}+…+a_{1}*p+a_{0}=\sum_{i=1}^{k}a_{i}^p{i} $$
$$ b=b_{k}*p^{k}+b_{k-1}*b^{k-1}+…+b_{1}*p+b_{0}=\sum_{i=1}^{k}b_{i}^p{i} $$
求证:
$$ C_{a}^{b}=C_{a_{k}}^{b_{k}}*C_{a_{k-1}}^{b_{k-1}}…C_{a_{0}}^{b_{0}} $$

首先我们证明引理一:
$$ (1+x)^p≡1+x^p\ (mod\ p) $$
根据组合数基本性质,我们有:
$$\forall j\in [1,p-1],C_{p}^{j}=\frac{p}{j}*C_{p-1}^{j-1}≡0(mod\ p) $$

$$∴(1+x)^p≡\sum_{i=1}^{p}C_{p}^{i}*x^i≡1+x^p(mod\ p) $$
于是我们得到结论:
$$(1+x)^a≡\prod_{i=1}^{k}(1+x)^{p^k*a_{k}}≡\prod_{i=1}^{k}(1+x^{p^{k}})^{a_{k}}(mod\ p)\ (1)$$
根据进制的基本性质和幂的基本性质,我们有:
$$b=\sum_{i=1}^{k}b_{i}^p{i},x^b=\prod_{i=1}^{k}x^{p^k*b_{i}}$$
并且我们知道,用上述方法表示\(p\)进制数,是完全等价的。即,两者的集合构成双射。

故而我们比较\((1)\)式展开后左右各项,可以得到:
$$\forall b\in [1,a],C_{a}^{b}≡\prod_{i=1}^{k}C_{a_{i}}^{b_{i}}(mod\ p)$$

证毕。

故而对于实际处理问题,只需要逆向秦九韶即可。

 

#include<iostream>
#include<cstdio>
using namespace std;
long long n,m,p,inv[100005],fac[100005];
#define Max(_A,_B) ((_A)>(_B)?(_A):(_B))
#define Min(_A,_B) ((_A)<(_B)?(_A):(_B))

//y取x 
inline long long C0(long long x,long long y){
    return ((x>y)?0:(x?fac[y]*inv[x]*inv[y-x]%p:1))%p;
}
//模p意义下的y取x 
inline long long C(long long x,long long y){
    return ((x>y)?0:((y>=p)?C(x/p,y/p)*C0(x%p,y%p):C0(x%p,y%p)))%p;
}
void init(){
    scanf("%lld%lld%lld",&n,&m,&p);
    fac[0]=fac[1]=inv[0]=inv[1]=1;
    for(int i=2;i<=p;++i){
        fac[i]=fac[i-1]*i%p;
        inv[i]=(p-p/i)*inv[p%i]%p;
    }
    for(int i=2;i<=p;++i){
        inv[i]*=inv[i-1];
        inv[i]%=p;
    }
    printf("%lld\n",C(n,n+m)%p);
} 
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        init();
    }
    return 0;
}

 

lp1290 欧几里德的游戏

这是一道基础的复杂博弈论题——我到现在都不是很能理解。
其实我也不太懂SG函数,我就口chao胡xi一下这题的做法吧。
对于\(x,y st: x<y\),我们定义对于x,y的SG函数SG(S),其中S是一个局面。 我们定义关于一个局面S的后继S’,使得S’可以从S转移得到。 所以我们定义一个集合T,包含了局面S的所有后继的SG值。 对于必败局面,我们令它的SG值为0,否则为1。 则\(SG(S)=mex(T)\),其中mex表示最小的不在集合中的非负整数。 所以, $$SG(x,y)=mex(SG(x,y-x),SG(x,y-2*x),$$

$$SG(x,y-3*x)…SG(x,y%x))$$ 我们又知道,对于其中的每一个SG函数,递推式都是成立的。 所以事实上,当\(x/y>1时,SG(x,y)=1\),这是因为当\(x/y==1\)始终是等于\(!(SG(y%x,x))\)
所以事实上\(SG(x,y)\)只取决于\(SG(x,y%x)\)的值。

#include<iostream>
#include<cstdio>
using namespace std;
#define Max(_A,_B) ((_A)>(_B)?(_A):(_B))
#define Min(_A,_B) ((_A)<(_B)?(_A):(_B))
int a,b;

inline bool SG(int x,int y){
    if(!x){
        return 0;
    }
    if(y/x==1){
        return !SG(y%x,x);
    }else{
        return 1;
    }
}
void init(){
    scanf("%d%d",&a,&b);
    bool bo=SG(Min(a,b),Max(a,b));
    if(bo){
        puts("Stan wins");
    }else{
        puts("Ollie wins");
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        init();
    }
    return 0;
}

 

lp1288 取数游戏

事实上这是一道结论题。
首先,如果双方足够聪明,那么他们都不会回头。
这是因为,如果先手方往一个方向走,在背后留下了一个必败局面,那么后手方一定不会回头。
而如果先手方往一个方向走,在背后留下了一个必胜局面,那么他一定会选择破坏了这条边。
所以游戏必然成是链。
我们首先考虑边数为2的情况。此时先手必胜,这是因为如果先手足够聪明,那么他一定会选择把这整条边拿掉。此时后手输了。
而,对于边数是3的情况,先手必败。这是因为,先手无论取任何数,都会使得情况转化为边数为2的情况,那么后手可以走一步然后断绝通向必胜局面的路。
故而我们得知,如果起始点的两端有一条链的边数为偶,则先手必胜;否则后手必胜。

#include<iostream>
#include<cstdio>
using namespace std;
int n,a[20];
void init(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;++i){
        if(!a[i]){
            if(!(i&1)){
                puts("YES");
                return;
            }
            break;
        }
    }
    for(int i=1;i<=n;++i){
        if(!a[n-i+1]){
            if(!(i&1)){
                
                puts("YES");
                return;
            }
            break;
        }
    }
    puts("NO");
    return;

}
int main(){
    init();
    return 0;
}

 

lp1199 NOIP2009 三国游戏

首先我们定义最优配对:最优配对指的是,对于一个武将而言,与他默契值最高的武将。
其次我们定义次优配对,次优配对指的是,对于一个武将而言,与他默契值次高的武将。
那么我们知道,无论如何,人类能够选取的一定只能是一组次优配对,而不可能是一组最优配对。
当然人类一定可以取到次优配对中的最大值。这是因为电脑的操作一定会用于破坏人类取到最优配对,因此取到次优配对一定是可能的。
如果人类想要胜利,就必须防止电脑取到最优配对中比次优配对最大值更大的那些值。我们定义这样的值为「危险值」
如果危险值存在,那么组成它的两个部分一定都互为最优配对:证明如下。
如果组成危险值的两个部分不互为最优配对,那么危险值一定是两者中一者关于另一者的最优配对。
我们不妨设定甲武将是乙的最优配对,该配对是危险值,那么乙武将必须存在一个最优配对,使得该配对的值大于危险值。
这时候乙武将的次优配对一定大于等于危险值,但是这与危险值的定义矛盾。所以组成危险值的两个部分一定互为最优配对。
故而,我们发现,危险值一定是一种最优配对。
那么,当我们优先取得足以构成次优配对中的最大值的两个武将以后,电脑已经控制的一个武将总是不能构成危险值。
这是因为组成危险值的两个武将一定互为最优配对,而电脑已经控制的仅为其中的一个武将,并且与该武将构成最优配对的武将控制在玩家手中。
当游戏进行三步时,玩家已经控制了次优配对中的最大值,并且电脑不控制任何危险值,此时可以将游戏转化为
「电脑先手且必须控制危险值」的情况。
由于危险值总是需要由一组互为最优配对的武将构成,容易证明无论电脑如何选择,玩家都可以破坏危险值的构成。
因此,总是有解,且解总为次优配对中的最大值。

当然这一题还有一个实现难点在于读入,这里就不再细说。

#include<iostream>
#include<cstdio>
using namespace std;
#define Max(_A,_B) ((_A)>(_B)?(_A):(_B))
/*
lp1199 三国游戏
*/
int n,f[505][505];
void init(){
    scanf("%d",&n);
    int mx,lmx,ans=0,x;
    for(int i=1;i<n;++i){
        for(int j=i+1;j<=n;++j){
            scanf("%d",&f[i][j]);
            f[j][i]=f[i][j];
        }
    }
    /*
    for(int i=1;i<n;++i){
        for(int j=1;j<n;++j){
            printf("%d ",f[i][j]);
        }
        puts("");
    }
    */
    for(int i=1;i<=n;++i){
        mx=0,lmx=0;
        for(int j=1;j<=n;++j){
            x=f[i][j];
            if(mx<x){
                lmx=mx;
                mx=x;
            }else{
                lmx=Max(lmx,x);
            }
        }
        ans=Max(ans,lmx);
    }
    puts("1");
    printf("%d",ans);
}
int main(){
    init();
    return 0;
}