kgxw0430

取石子游戏


数据范围:

  • 30分。直接暴力枚举剩下哪些堆。然后判断是否异或和为0、去掉的数量是否是d的倍数即可。

  • 50分。考虑dp。f[i] [j] [k]表示到了第i堆石子,留下了j堆,此时子集异或和为k的方案数。\(n^2*max(a[i])\)递推即可。

  • 70分。首先我们先把上面所述50分的dp优化一下,考虑真正对答案有贡献的肯定是去掉的个数为d的倍数。所以我们传递状态的时候,只需要考虑留下的堆的数量在mod d下是什么值就好。那么递推复杂度变为\(n*d*max(a[i])\)。有20分的数据,本质不同的a[i]只有不超过5个。显然可以用数组存起来所有子集异或和,递推的时候只考虑这些。复杂度是\(n*d*2^5\)

  • 100分。前面的一个n我们已经优化到了d。所以现在考虑从a[i]下手。但是每次两个数异或的值随机大小,我们怎么给缩小范围啊?排序!反正排序不影响答案,我们把a数组按照从大到小排序。假如当前到了a[i],而\(a[i]<2^L\),因为我们考虑的是如何让剩下的数异或和为0,那么它能影响到状态显然都小于\(2^L\)。所以只需要枚举\(0\)~\(2^L\)

  • 复杂度证明:

  • 小结:比赛的时候,一定先打暴力。比如这道题50分的暴力很好打(为了确保正确性可以先打30的暴力来对拍)。跟着数据范围想优化暴力,思考算法的可行性之后再敲。今天上午想了线性基好像可以做,就直接去码了。码了码,调调发现算法是假的。而这时候已经11点了,如果是省选我就凉了。

Coding

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e6+10;
const int mod=1e9+7;
ll n,d,a[N],f[2][12][N];
ll read(){
    char ch=getchar();ll num=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
    return num*f;
}
bool cmp(ll a,ll b){return a>b;}
int main(){
    n=read(),d=read();
    for(int i=1;i<=n;++i) a[i]=read();
    sort(a+1,a+n+1,cmp);
    f[0][0][0]=1;ll last=0,now=1;
    for(int i=1;i<=n;++i){
        int l=log(1.0*a[i])/log(2.0)+1;
        for(int j=0;j<d;++j) for(int k=pow(2.0,1.0*l);k>=0;--k) f[now][j][k]=0;
        for(int j=0;j<d;++j){
            for(int k=pow(2.0,1.0*l);k>=0;--k){
                f[now][j][k]=(ll)(f[now][j][k]+f[last][(j-1+d)%d][k^a[i]])%mod;
                f[now][j][k]=(ll)(f[now][j][k]+f[last][j][k])%mod;
            }
        }
        int temp=now;now=last;last=temp;
    }
    int ans=f[last][n%d][0];
    if(n%d==0) ans=(ll)(ans-1+mod)%mod;
    cout<<ans<<endl;
    return 0;
}

路径计数


数据范围:

  • 这题暴力dfs没法儿写吧,反正我是写不了,机房大佬不知道怎么算的复杂度,让老师+时限想卡过去,其实根本不可能好吧。

  • 必须朝dp的这个思路想才行。其实这种计数类的题目,又显然没有什么式子可以推,那就是dp了。

  • 设f[s] [t] [d] [j]表示以s为起点,t为终点,走了d步到达j,中途不经过s和t的方案数。这个显然可以\(n^4*d\)转移,查询O\((1)\),复杂度O\((n^4*d+q)\),50分get。

  • 上面所述状态因为要考虑中途不出现s和t,所以必须状态多了一维,考虑如何优化掉这一维。

  • 假如我们现在要求得是从s走到t,走了d步,中途任意的方案数,显然可以\(n^3*d\)求。具体来说,设g[s] [t] [d] 表示从s走d步到t,任意走的方案数。

    \[g[s][t][d]=\sum_{i=1}^{n}g[s][i][d-1]\]

  • 如何求答案呢?其实是一个广义容斥,合法的方案数=所有的方案数-不合法的方案数。设f[s] [t] [d]表示从s走d到t的合法方案数。如何求不合法的方案数?想一下,一条不合法的路径,从s到t肯定有第一个不合法的点,即第一次遇到了s或者t。分两种情况:
    • 第一次遇到了t,那么不合法的方案即为\(f[s][t][i]*g[t][t][d-i]\),i从1~d-1.
    • 第一次遇到了s,那么不合法的方案应该是从s到s,中途不经过s和t的方案数再乘上\(g[s][t][d-i]\)。我们定义从s到s,中途不经过s和t的方案数为\(h[s][t][i]\)
  • 到此,f数组的求法已经知道。现在考虑如何求h数组。其实和f数组一样,我们仍然把路径分为两种情况:
    • 先遇到s,\(h[s][t][d]-\sum_{i=1}^{d-1}h[s][t][i]*g[s][s][d-i]\).
    • 先遇到t,\(h[s][t][d]-\sum_{i=1}^{d-1}f[s][t][i]*g[t][s][d-i]\).
    • 记得最初\(h[s][t][d]=g[s][s][d]\).
  • 还有比较sb的一点是,手动取模,不然会T。

Coding

#include<bits/stdc++.h>
#define ll long long
#define mul(a,b)    1LL*a*b%p
using namespace std;
const int N=110;
int f[N][N][52],g[N][N][52],h[N][N][52];
int n,m,p,q,a[N][N];
inline void add(int &a,int b){a+=b;a-= a>=p? p:0;}
inline void sub(int &a,int b){a-=b;a=(a+p)%p;}
int read(){
    char ch=getchar();int num=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
    return num*f;
}
void work(){
    for(int i=1;i<=n;++i) g[i][i][0]=h[i][i][0]=f[i][i][0]=1;
    for(int d=2;d<=50;++d){
        for(int i=1;i<=n;++i){
            for(int j=1;j<=n;++j){
                for(int k=1;k<=n;++k){
                    add(g[i][j][d],mul(g[i][k][d-1],g[k][j][1]));
                }
            }
        }
    }
    for(int d=2;d<=50;++d){
        for(int s=1;s<=n;++s){
            for(int t=1;t<=n;++t){
                h[s][t][d]=g[s][s][d];
                for(int i=1;i<d;++i){
                    sub(h[s][t][d],mul(f[s][t][i],g[t][s][d-i]));
                    if(s==t) continue;
                    sub(h[s][t][d],mul(h[s][t][i],g[s][s][d-i]));
                }
                f[s][t][d]=g[s][t][d];
                for(int i=1;i<d;++i){
                    sub(f[s][t][d],mul(f[s][t][i],g[t][t][d-i]));
                    if(s==t) continue;
                    sub(f[s][t][d],mul(h[s][t][i],g[s][t][d-i]));
                }
            }
        }
    }
}
int main(){
    n=read(),m=read(),p=read();
    for(int i=1;i<=m;++i){
        int x,y;x=read(),y=read();
        a[x][y]=1;
        g[x][y][1]=1;
        f[x][y][1]=1;
    }
    work();
    q=read();
    for(int i=1;i<=q;++i){
        int x,y,z;x=read(),y=read(),z=read();
        printf("%d\n",f[x][y][z]);
    }
    return 0;
}

相关文章: