zoukankan      html  css  js  c++  java
  • 容斥

    bzoj2440 完全平方数

    题目大意:求第k个不是完全平方数倍数的数(1不算完全平方数)。

    思路:二分+容斥+莫比乌斯函数。我们可以二分x,看1~x中有几个不是完全平方数倍数的数。求这个的过程是容斥原理,我们可以用所有的数-有1个质数(分解质因数后的,下同)平方了的+有2个的-有3个的...,然后我们可以穷举一些数,他们的平方一定是完全平方数,而他们有几个质数就是之前的那个1、2、3...,而这个系数又恰好和莫比乌斯函数吻合。这里的容斥(!!!)用到很多技巧,但代码很简单。(关于二分的上界好像是2k,但我设了一个比较大的上界1e10)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define maxnode 100005
    #define LL long long
    using namespace std;
    int mu[maxnode]={0},prime[maxnode]={0};
    bool flag[maxnode]={false};
    void shai(int n)
    {
        int i,j;
        mu[1]=1;
        for (i=2;i<=n;++i)
        {
            if (!flag[i])
            {
                prime[++prime[0]]=i;mu[i]=-1;
            }
            for (j=1;j<=prime[0]&&prime[j]*i<=n;++j)
            {
                flag[i*prime[j]]=true;
                if (i%prime[j]) mu[i*prime[j]]=-mu[i];
                else {mu[i*prime[j]]=0;break;}
            }
        }
    }
    LL calc(LL n)
    {
        LL i;LL ans=n;
        for (i=2;i*i<=n;++i) ans+=mu[(int)i]*(n/(i*i));
        return ans;
    }
    int main()
    {
        int t;LL l,r,mid,n;
        shai(100000);scanf("%d",&t);
        while(t--)
        {
            scanf("%I64d",&n);
            l=1;r=1e10;
            while(l!=r)
            {
                mid=(l+r)/2;
                if (calc(mid)<n) l=mid+1;
                else r=mid;
            }
            printf("%I64d
    ",l);
        }
    }
    View Code

    bzoj1042 硬币购物

    题目大意:给定4种硬币的货值和询问次数,每次询问给定4中硬币的个数和要买的物品价值,求购买的方案数。

    思路:dp预处理+容斥。首先不考虑硬币数量的限制,无限背包预处理出f[i](表示价值为i的方案数有多少)(注意无限背包先循环物品,如果循环顺序反了,就会对同一种方案统计多次(有点像排列数和组合数的关系))。然后对每一个询问都用f[s]-1种硬币超过的+2种的-3种的+4种的,这里统计一种超的可以强制着一种取要求数di+1个,那么方案数就是f[s-(di+1)*ci]种了。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long
    #define maxnode 100005
    using namespace std;
    LL f[maxnode]={0};
    int ci[5]={0},di[5]={0},s;
    LL calc(int a,int b,int c,int d)
    {
        int sum=a*ci[1]+b*ci[2]+c*ci[3]+d*ci[4];
        return (sum>s ? 0 : f[s-sum]);
    }
    int main()
    {
        int tot,i,j;LL ans;
        for (i=1;i<=4;++i) scanf("%d",&ci[i]);
        scanf("%d",&tot);f[0]=1;
        for (j=1;j<=4;++j)
          for (i=ci[j];i<=100000;++i) f[i]+=f[i-ci[j]];
        for (i=1;i<=tot;++i)
        {
            for (j=1;j<=4;++j) scanf("%d",&di[j]);
            scanf("%d",&s);
            ans=f[s]-calc(di[1]+1,0,0,0)-calc(0,di[2]+1,0,0)-calc(0,0,di[3]+1,0)-calc(0,0,0,di[4]+1)
                +calc(di[1]+1,di[2]+1,0,0)+calc(di[1]+1,0,di[3]+1,0)+calc(di[1]+1,0,0,di[4]+1)
                +calc(0,di[2]+1,di[3]+1,0)+calc(0,di[2]+1,0,di[4]+1)+calc(0,0,di[3]+1,di[4]+1)
                -calc(di[1]+1,di[2]+1,di[3]+1,0)-calc(di[1]+1,di[2]+1,0,di[4]+1)
                -calc(di[1]+1,0,di[3]+1,di[4]+1)-calc(0,di[2]+1,di[3]+1,di[4]+1)
                +calc(di[1]+1,di[2]+1,di[3]+1,di[4]+1);
            printf("%I64d
    ",ans);
        }
    }
    View Code

    bzoj4086 travel

    题目大意:给定一张无向图,求路径上经过k个点的点对,输出矩阵ij表示ij间有无道路。

    思路:k<=7所以可以分情况+容斥。

    k=2的时候,枚举边;

    k=3的时候,枚举边、第二条边;

    k=4的时候,枚举两边的边,判断中间是否连通;

    k=5的时候,枚举第2、4个点,求出所有的次数和每个点在中间出现的次数,然后找两边的点,tot-cntx-cnty>0就可以;

    k=6的时候,同5,只是要判断两个点是否能组成四元组;

    k=7的时候,先找出所有的三元组保存下来,枚举2、6个点,预处理所有能组成五元组的三元组的点分别的个数、总的个数、xy同时出现在边上的、xy一边一中的,然后判断的时候tot-cntx-cnty+calxy+c1xy+c1yx>0就可以。(注意这里不能清数组,用一个数组保存这个位置的值有无更新过,同时判断的时候也要判断是否更新过。)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #define N 1005
    #define M 10005
    using namespace std;
    int point[N]={0},next[M]={0},en[M]={0},tot=0,n,cnt[N],cal[N][N],c1[N][N],pal[N][N],p1[N][N];
    bool li[N][N],mi[N][N];
    vector<int> ci[N][N];
    int in(){
        char ch=getchar();int x=0;
        while(ch<'0'||ch>'9') ch=getchar();
        while(ch>='0'&&ch<='9'){
            x=x*10+ch-'0';ch=getchar();
        }return x;}
    void add(int u,int v){
        if (u==v) return;
        next[++tot]=point[u];point[u]=tot;en[tot]=v;
        next[++tot]=point[v];point[v]=tot;en[tot]=u;
        mi[u][v]=mi[v][u]=true;}
    void work2(){
        for (int i=1;i<=n;++i)
          for (int j=1;j<=n;++j)
            if (mi[i][j]) li[i][j]=true;}
    void work3(){
        int i,j,t,u;
        for (i=1;i<=n;++i)
            for (j=point[i];j;j=next[j])
                for (t=point[u=en[j]];t;t=next[t]){
                    if (en[t]==i) continue;
                    li[i][en[t]]=true;
                }
    }
    void work4(){
        int i,j,a,b,u,v;
        for (i=1;i<n;++i)
          for (j=i+1;j<=n;++j)
              for (a=point[i];a;a=next[a]){
                  if ((u=en[a])==j) continue;
                  for (b=point[j];b;b=next[b]){
                      if ((v=en[b])==u||v==i) continue;
                      if (mi[u][v]) li[i][j]=li[j][i]=true;
                  }
            }}
    void work5(){
        int i,j,a,b,u,v,tot;
        for (i=1;i<n;++i)
          for (j=i+1;j<=n;++j){
              memset(cnt,0,sizeof(cnt));tot=0;
            for (a=point[i];a;a=next[a]){
                  if ((u=en[a])==j||!mi[u][j]) continue;
                  ++cnt[u];++tot;
              }for (a=point[i];a;a=next[a]){
                  if ((u=en[a])==j) continue;
                  for (b=point[j];b;b=next[b]){
                      if ((v=en[b])==i||v==u) continue;
                      if (tot-cnt[u]-cnt[v]>0) li[u][v]=li[v][u]=true;
                  }
            }
          }}
    void work6(){
        int i,j,a,b,u,v,tot;
        for (i=1;i<n;++i)
          for (j=i+1;j<=n;++j){
              memset(cnt,0,sizeof(cnt));tot=0;
              for (a=point[i];a;a=next[a]){
                  if ((u=en[a])==j) continue;
                  for (b=point[j];b;b=next[b]){
                      if ((v=en[b])==i||v==u) continue;
                      if (mi[u][v]){++cnt[u];++cnt[v];++tot;}}
              }for (a=point[i];a;a=next[a]){
                  if ((u=en[a])==j) continue;
                  for (b=point[j];b;b=next[b]){
                      if ((v=en[b])==i||v==u) continue;
                      if (tot-cnt[u]-cnt[v]+mi[u][v]>0)
                        li[u][v]=li[v][u]=true;
                  }
            }
          }}
    void work7(){
        int i,j,k,a,b,u,v,t,tot,cur=0;
        for (i=1;i<=n;++i)
          for (j=1;j<=n;++j) ci[i][j].clear();
        for (i=1;i<=n;++i)
            for (a=point[i];a;a=next[a])
                for (b=point[u=en[a]];b;b=next[b]){
                    if ((v=en[b])==i) continue;
                    ci[i][v].push_back(u);}
        memset(pal,0,sizeof(pal));
        memset(p1,0,sizeof(p1));
        for (i=1;i<=n;++i)
          for (j=i+1;j<=n;++j){
              memset(cnt,0,sizeof(cnt));++cur;tot=0;
              for (a=point[i];a;a=next[a]){
                  if ((u=en[a])==j) continue;
                  for (b=point[j];b;b=next[b]){
                      if ((v=en[b])==u||v==i) continue;
                      for (k=ci[u][v].size()-1;k>=0;--k){
                        if ((t=ci[u][v][k])==i||t==j) continue;
                        if (pal[u][v]==cur)++cal[u][v];
                      else{cal[u][v]=1;pal[u][v]=cur;}
                      if (p1[u][t]==cur) ++c1[u][t];
                      else{c1[u][t]=1;p1[u][t]=cur;}
                      if (p1[v][t]==cur) ++c1[v][t];
                      else{c1[v][t]=1;p1[v][t]=cur;}
                      ++cnt[u];++cnt[v];++cnt[t];++tot;
                      }
                  }
              }for (a=point[i];a;a=next[a]){
                  if ((u=en[a])==j) continue;
                  for (b=point[j];b;b=next[b]){
                      if ((v=en[b])==u||v==i) continue;
                      if (tot-cnt[u]-cnt[v]+(pal[u][v]==cur ? cal[u][v] : 0)+
                          (p1[u][v]==cur ? c1[u][v] : 0)+(p1[v][u]==cur ? c1[v][u] : 0)>0)
                        li[u][v]=li[v][u]=true;
                  }
              }
          }
    }
    void work(int k){
        if (k==2) work2();
        if (k==3) work3();
        if (k==4) work4();
        if (k==5) work5();
        if (k==6) work6();
        if (k==7) work7();}
    int main(){
        int t,i,j,m,u,v,k;t=in();
        while(t--){
            memset(point,0,sizeof(point));
            memset(mi,0,sizeof(mi));
            memset(li,0,sizeof(li));
            n=in();m=in();k=in();tot=0;
            for (i=1;i<=m;++i){u=in();v=in();add(u,v);}
            for (work(k),i=1;i<=n;++i){
                for (j=1;j<=n;++j) putchar(li[i][j] ? 'Y' : 'N');
                printf("
    ");
            }
        }
    }
    View Code

    bzoj1567 Blue Mary的战役地图

    题目大意:求两个矩阵的最大公共正方形的边长。

    思路:二分+hash。用容斥求出一个矩阵的hash,用map存一下hash值(可以用unsigned long long,自然溢出)就可以了。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<map>
    #include<cstdlib>
    #define N 55
    #define LL long long
    #define UL unsigned long long
    using namespace std;
    int n;LL aa[N][N],bb[N][N];
    UL ai[N][N],bi[N][N],ma[N],mb[N];
    map<UL,int> cnt;
    bool judge(int x){
        int i,j,a,b;cnt.clear();
        for (i=x;i<=n;++i)
          for (j=x;j<=n;++j){
              a=i-x+1;b=j-x+1;
              cnt[ma[n-i+1]*mb[n-j+1]*(ai[i][j]-ai[i][b-1]-ai[a-1][j]+ai[a-1][b-1])]=1;
          }
        for (i=x;i<=n;++i)
          for (j=x;j<=n;++j){
              a=i-x+1;b=j-x+1;
              if (cnt[ma[n-i+1]*mb[n-j+1]*(bi[i][j]-bi[i][b-1]-bi[a-1][j]+bi[a-1][b-1])])
                  return true;
          }return false;}
    int main(){
        int i,j,l,r,mid,ans=0;
        UL a,b;scanf("%d",&n);
        a=(UL)3931;b=(UL)9907;ma[0]=mb[0]=1LL;
        for (i=1;i<=n;++i){ma[i]=ma[i-1]*a;mb[i]=mb[i-1]*b;}
        for (i=1;i<=n;++i)
          for (j=1;j<=n;++j){
              scanf("%I64d",&aa[i][j]);
              ai[i][j]=(UL)aa[i][j]*ma[i]*mb[j];
              ai[i][j]+=ai[i-1][j]+ai[i][j-1]-ai[i-1][j-1];}
        for (i=1;i<=n;++i)
          for (j=1;j<=n;++j){
            scanf("%I64d",&bb[i][j]);
            bi[i][j]=(UL)bb[i][j]*ma[i]*mb[j];
            bi[i][j]+=bi[i-1][j]+bi[i][j-1]-bi[i-1][j-1];}
        l=0;r=n;
        while(l<=r){
            mid=l+r>>1;
            if (judge(mid)){ans=mid;l=mid+1;}
            else r=mid-1;
        }printf("%d
    ",ans);
    }
    View Code

    乞巧

    题目大意:求n个字符串中恰好有k个字母不同的字符串对(字符串长度为4)。

    思路:hash一下,然后容斥。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define maxnode 500005
    #define p 99991
    #define LL unsigned long long
    #define ll long long
    using namespace std;
    struct use{
        LL ha[20];
    }ai[maxnode]={0};
    char ss[maxnode][10];
    LL mi[10]={0}; 
    ll ans[5]={0},get[5]={0};
    int n,k,k1,k2;
    int cmp(const use &x,const use &y){return x.ha[k1]<y.ha[k1];}
    int idx(char ch)
    {
        if (ch>='0'&&ch<='9') return ch-'0';
        else return ch-'a'+10;
    }
    void pre()
    {
        int i,j;
        for (i=1;i<=n;++i)
        {
            for (j=0;j<4;++j) ai[i].ha[0]+=mi[j]*idx(ss[i][j]);
            ai[i].ha[1]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][1])+mi[2]*idx(ss[i][2]);
            ai[i].ha[2]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][1])+mi[2]*idx(ss[i][3]);
            ai[i].ha[3]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][2])+mi[2]*idx(ss[i][3]);
            ai[i].ha[4]=mi[0]*idx(ss[i][1])+mi[1]*idx(ss[i][2])+mi[2]*idx(ss[i][3]);
            ai[i].ha[5]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][1]);
            ai[i].ha[6]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][2]);
            ai[i].ha[7]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][3]);
            ai[i].ha[8]=mi[0]*idx(ss[i][1])+mi[1]*idx(ss[i][2]);
            ai[i].ha[9]=mi[0]*idx(ss[i][1])+mi[1]*idx(ss[i][3]);
            ai[i].ha[10]=mi[0]*idx(ss[i][2])+mi[1]*idx(ss[i][3]);
            for (j=0;j<4;++j) ai[i].ha[j+11]=idx(ss[i][j]);
        }
    }
    int main()
    {
        freopen("b.in","r",stdin);
        freopen("b.out","w",stdout);
        
        int i,j,t;
        scanf("%d%d",&n,&k);mi[0]=1;
        for (i=1;i<=4;++i) mi[i]=mi[i-1]*p;
        for (i=1;i<=n;++i) scanf("%s",&ss[i]);
        pre();k1=0;i=1;
        sort(ai+1,ai+n+1,cmp);
        while(i<=n)
        {
            j=i;
            while(ai[j].ha[k1]==ai[j+1].ha[k1]&&j<n) ++j;
            get[0]+=(ll)(j-i+1)*(ll)(j-i)/2;i=j+1;
        }
        ans[0]=get[0];
        for (k2=1;k2<=4;++k2)
        {
          ++k1;sort(ai+1,ai+n+1,cmp);i=1;
          while(i<=n)
          {
            j=i;
            while(ai[j].ha[k1]==ai[j+1].ha[k1]&&j<n) ++j;
            get[1]+=(ll)(j-i+1)*(ll)(j-i)/2;i=j+1;
          }
        }
        ans[1]=get[1]-4*ans[0];
        if (k>=2)
        {
          for (k2=1;k2<=6;++k2)
          {
            ++k1;sort(ai+1,ai+n+1,cmp);i=1;
            while(i<=n)
            {
              j=i;
              while(ai[j].ha[k1]==ai[j+1].ha[k1]&&j<n) ++j;
              get[2]+=(ll)(j-i+1)*(ll)(j-i)/2;i=j+1;
            }
          }
          ans[2]=get[2]-3*ans[1]-6*ans[0];
          if (k>=3)
          {
            for (k2=1;k2<=4;++k2)
            {
              ++k1;sort(ai+1,ai+n+1,cmp);i=1;
              while(i<=n)
              {
                 j=i;
                while(ai[j].ha[k1]==ai[j+1].ha[k1]&&j<n) ++j;
                get[3]+=(ll)(j-i+1)*(ll)(j-i)/2;i=j+1;
              }
            }
            ans[3]=get[3]-2*ans[2]-3*ans[1]-4*ans[0];
            get[4]=(ll)n*(ll)(n-1)/2;
            ans[4]=get[4]-ans[0]-ans[1]-ans[2]-ans[3];
          }
        }
        printf("%I64d
    ",ans[k]);
        
        fclose(stdin);
        fclose(stdout);
    }
    View Code

    bzoj1879 Bill的挑战

    题目大意:给定n个字符串(带有通配符?,且只有小写字母和?),求恰好和k个字符串匹配(两字符串相同,通配符可以代替任意一个字符)且没有?的字符串的个数。(n<=15,lenth<=50)

    思路:dfs+容斥。如果dfs,搜出k个之后计数原理算一下可能的字符串种数,会有很多字符串算很多遍,所以容斥一下,系数可以通过组合数之类的算出来。

    这个容斥的系数比较复杂,所以可以先把每一次dfs的答案的表达式写出来,然后用相应的数学技巧算出系数,可以当作一类方法。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 20
    #define L 55
    #define p 1000003
    #define msz 26
    using namespace std;
    char ss[N][L];
    int n,k,kk,l,ci[N],ans=0,c[N][N];
    int calc(){
        int i,j,cc,cnt=1;char ch;
        for (i=0;i<l;++i){
            cc=0;ch='?';
            for (j=1;j<=k;++j)
                if (ss[ci[j]][i]!=ch&&ss[ci[j]][i]!='?'){
                    ch=ss[ci[j]][i];++cc;}
            if (cc>1) return 0;
            if (!cc) cnt=cnt*26%p;
        }return cnt%p;}
    void dfs(int ii,int la,int kk){
        if (ii>k){ans=((ans+kk*calc())%p+p)%p;return;}
        for (int i=la+1;i+k-ii<=n;++i) {
            ci[ii]=i;dfs(ii+1,i,kk);
        }}
    int main(){
        int i,j,t,cc;scanf("%d",&t);
        for (i=1;i<=15;++i){
            c[i][0]=c[i][i]=1;
            for (j=1;j<i;++j) c[i][j]=c[i-1][j-1]+c[i-1][j];
        }while(t--){
          scanf("%d%d",&n,&k);ans=0;
          for (i=1;i<=n;++i) scanf("%s",ss[i]);
          l=strlen(ss[1]);kk=k;
          for(j=1;k<=n;++k,j=-j) dfs(1,0,j*c[k][kk]);
          printf("%d
    ",ans);
        }
    }
    View Code
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 20
    #define L 55
    #define p 1000003
    #define msz 26
    using namespace std;
    char ss[N][L];
    int n,k,kk,l,ci[N],ans=0,c[N][N],ki[N];
    int calc(){
        int i,j,cc,cnt=1;char ch;
        for (i=0;i<l;++i){
            cc=0;ch='?';
            for (j=1;j<=k;++j)
                if (ss[ci[j]][i]!=ch&&ss[ci[j]][i]!='?'){
                    ch=ss[ci[j]][i];++cc;}
            if (cc>1) return 0;
            if (!cc) cnt=cnt*26%p;
        }return cnt%p;}
    void dfs(int ii,int la,int kk){
        if (ii>k){ans=((ans+kk*calc())%p+p)%p;return;}
        for (int i=la+1;i+k-ii<=n;++i) {
            ci[ii]=i;dfs(ii+1,i,kk);
        }}
    int main(){
        int i,j,t,cc;scanf("%d",&t);
        for (i=1;i<=15;++i){
            c[i][0]=c[i][i]=1;
            for (j=1;j<i;++j) c[i][j]=c[i-1][j-1]+c[i-1][j];
        }while(t--){
          scanf("%d%d",&n,&k);ans=0;
          for (i=1;i<=n;++i) scanf("%s",ss[i]);
          l=strlen(ss[1]);kk=k;
          for (i=k;i<=n;++i) ki[i]=c[i][k];
          for (i=k+1;i<=n;++i){
              for (cc=-ki[i],j=i+1;j<=n;++j) ki[j]+=cc*c[j][i];
              ki[i]=-ki[i];
          }for(;k<=n;++k) dfs(1,0,ki[k]);
          printf("%d
    ",ans);
        }
    }
    View Code

    看网上大多数人都写的dp,fi[i][j]表示前i位匹配状态为j(二进制)的有多少种,转移一下就行了。

    bzoj3930 选数

    题目大意:从[L,R]中选出N个数(可重复排列),问N个数的最大公约数是K的方案数。

    思路:[L,R]/k之后,就是统计N个数互质的情况,类似bzoj2440,-1个质数的+2个质数的-...,这里的R-L<=10^5,所以循环到10^5之后的数就会使之前的区间中只有1个数,所以可以循环统计之前的数,后面的数是mu的一段和的形式,可以用杜教筛。也可以稍微变化一下,每次累加答案的时候不考虑全选相同的数(!!!),最后如果能全选k的话,再+1。这样对于只有一个数的区间就不用统计答案了,其他区间-(R-L+1),省去了求前缀和的部分。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 100005
    #define LL long long
    #define p 1000000007LL
    using namespace std;
    int prime[N]={0},mu[N];
    LL n,k,l,r;
    bool flag[N]={false};
    inline void shai(int m){
        int i,j;mu[1]=1;
        for (i=2;i<=m;++i){
            if (!flag[i]) mu[prime[++prime[0]]=i]=-1;
            for (j=1;j<=prime[0]&&i*prime[j]<=m;++j){
                flag[i*prime[j]]=true;
                if (i%prime[j]) mu[i*prime[j]]=-mu[i];
                else{mu[i*prime[j]]=0;break;}
            }
        }
    }
    inline LL mi(LL x,int y){
        LL a=1LL;
        for (;y;y>>=1){
            if (y&1) a=a*x%p;
            x=x*x%p;
        }return a;}
    inline LL calc(int m){
        int i,ll,rr;LL ans=0LL;
        l=(l+k-1)/k;r=r/k;
        for (i=1;i<=m;++i){
            ll=(l-1)/i;rr=r/i;
            ans=((ans+(LL)mu[i]*(mi((LL)(rr-ll),n)-(LL)(rr-ll)))%p+p)%p;
        }return (ans+(l==1LL))%p;}
    int main(){
        scanf("%d%d%d%d",&n,&k,&l,&r);
        shai(r-l+1);printf("%I64d
    ",calc(r-l+1));
    }
    View Code

    bzoj2669 局部最小值

    题目大意:给定一个n*m的网格,格子中的数是一个1~n*m的排列,其中有一些位置是极小值点(这个点的权值是它周围八个格子中最小的),求满足条件的网格种数。(n<=4,m<=7)

    思路:因为网格中最多有8个极小值点,所以可以状压。从小到大填数,预处理gi[i]表示极小值点填的状态是i的时候能填的位置(非极小值点)的个数。fi[i][j]表示填到第i个数、极小值点填的状况是j,fi[i+1][j]+=fi[i][j]*(gi[j]-(i-cc))(cc表示j中1的个数),fi[i+1][j|(1<<k)]+=fi[i][j]。但这样可能会使一些点成为极小值点,所以可以容斥:预处理出所有可能成为极小值点的位置,然后dfs,再dp,算答案。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 10
    #define up 30
    #define LL long long
    #define p 12345678LL 
    using namespace std;
    struct use{int x,y;}zh[N];
    int map[N][N],ci[N][N],n,m,zt=0,st=0,dx[8]={-1,-1,-1,0,0,1,1,1},dy[8]={-1,0,1,-1,1,-1,0,1};
    LL ans=0LL,fi[up][1<<N],gi[1<<N];
    bool cg[N][N]={false};
    int in(){
        char ch=getchar();
        while(ch!='X'&&ch!='.') ch=getchar();
        return (ch=='X');}
    void find(){
        int i,j,k,x,y;
        for (i=1;i<=n;++i)
            for (j=1;j<=m;++j){
                cg[i][j]=!map[i][j];
                for (k=0;k<8&&cg[i][j];++k){
                      x=i+dx[k];y=j+dy[k];
                      if (x<=0||x>n||y<=0||y>m) continue;
                      if (map[x][y]) cg[i][j]=false;
                }
            }
    }
    void add(LL &x,LL y){x=((x+y)%p+p)%p;}
    void dp(int cnt){
        int i,j,k,xx,yy,uu,cc;LL ff=(LL)(cnt%2 ? -1 : 1);
        memset(fi,0,sizeof(fi));
        memset(gi,0,sizeof(gi));
        for (i=0;i<(1<<zt);++i){
            memset(ci,0,sizeof(ci));
            for (j=0;j<zt;++j){
                ci[zh[j].x][zh[j].y]=1;
                 if (i&(1<<j)) continue;
                 for (k=0;k<8;++k){
                     xx=zh[j].x+dx[k];yy=zh[j].y+dy[k];
                     if (xx<=0||xx>n||yy<=0||yy>m) continue;
                     if (map[xx][yy]) return;
                     ci[xx][yy]=1;
                 }
            }for (j=1;j<=n;++j)
                for (k=1;k<=m;++k)
                    if (!ci[j][k]) ++gi[i];
        }fi[0][0]=1LL;
        for (uu=n*m,i=0;i<uu;++i){
            for (j=0;j<(1<<zt);++j){
                if (!fi[i][j]) continue;
                for (cc=k=0;k<zt;++k){
                    if (!(j&(1<<k))) add(fi[i+1][j|(1<<k)],fi[i][j]);
                    else ++cc;
                }if (i+1<cc) continue;
                if (gi[j]-(LL)(i-cc)<=0) continue;
                add(fi[i+1][j],fi[i][j]*(gi[j]-(LL)(i-cc))%p);
            }
        }add(ans,ff*fi[uu][(1<<zt)-1]);
    }
    void dfs(int x,int y,int cnt){
        if (x==n+1){dp(cnt);return;}
        int xx,yy;xx=x;yy=y+1;
        if (yy>m){++xx;yy=1;}
        dfs(xx,yy,cnt);
        if (cg[x][y]){
            map[x][y]=1;
            zh[zt++]=(use){x,y};
            dfs(xx,yy,cnt+1);
            map[x][y]=0;--zt;
        }
    }
    int main(){
        int i,j,tt=0;scanf("%d%d",&n,&m);
        for (i=1;i<=n;++i)
            for (j=1;j<=m;++j){
                map[i][j]=in();
                if (map[i][j]) zh[zt++]=(use){i,j};
            }
        find();dfs(1,1,0);
        printf("%I64d
    ",ans);
    }
    View Code

    bzoj4455 小星星(!!!

    题目大意:给定n个点,已知这n个点原来的连接状态和现在的(现在的是一棵树),问有多少种重新编号的方式使现在的符合原来的(即现在的边在原来的中都能找到)。

    思路:考虑n^3dp,fi[i][j]表示i和j对应,暴力转移。这样可能出现多个点和一个对应的情况,所以应该容斥一下,每次dp时对应相应的子集,然后更新答案。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 18
    #define M 500
    #define LL long long
    using namespace std;
    int ai[N][N],ci[N],n,point[N]={0},next[M],en[M],tot=0;
    LL fi[N][N];
    void add(int u,int v){
        next[++tot]=point[u];point[u]=tot;en[tot]=v;
        next[++tot]=point[v];point[v]=tot;en[tot]=u;}
    void dp(int u,int ff){
        int i,j,k,v;LL sm,cc;
        for (i=point[u];i;i=next[i]){
            if ((v=en[i])==ff) continue;
            dp(v,u);
        }for (i=1;i<=ci[0];++i){
            for (sm=1LL,j=point[u];j;j=next[j]){
                if ((v=en[j])==ff) continue;
                for (cc=0LL,k=1;k<=ci[0];++k){
                    if (!ai[ci[i]][ci[k]]) continue;
                    cc+=fi[v][ci[k]];
                }sm*=cc;
            }fi[u][ci[i]]=sm;
        }
    }
    int main(){
        int m,i,j,u,v;LL ans=0LL;
        scanf("%d%d",&n,&m);
        memset(ai,0,sizeof(ai));
        for (i=1;i<=m;++i){
            scanf("%d%d",&u,&v);
            ai[u][v]=ai[v][u]=1;
        }for (i=1;i<n;++i){
            scanf("%d%d",&u,&v);
            add(u,v);
        }for (i=0;i<(1<<n);++i){
            memset(fi,0,sizeof(fi));
            for (ci[0]=j=0;j<n;++j)
                if ((1<<j)&i) ci[++ci[0]]=j+1;
            dp(1,0);
            for (j=1;j<=ci[0];++j)
              ans+=(LL)(((n-ci[0])&1)? -1 : 1)*fi[1][ci[j]];
        }printf("%I64d
    ",ans);
    }
    View Code

    bzoj4596 黑暗前的幻想乡

    题目大意:给出一张无向图,有n-1种边,求每种边出现一次的树的个数。

    思路:类似上一题,容斥之后就是求只用j(二进制状态)的边的生成树的个数,可以用matrix-tree定理,关于取模,在原来/的时候用逆元就可以了。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #define N 18
    #define LL long long
    #define p 1000000007LL
    using namespace std;
    struct use{int u,v;}ed[N][N*N];
    LL ai[N][N];
    int n,ci[N],cho[N];
    LL mi(LL x,LL y){
        LL a=1LL;
        for (;y;y>>=1LL){
            if (y&1LL) a=a*x%p;
            x=x*x%p;
        }return a;}
    LL calc(){
        int i,j,k,u,v;LL kk,cc=1LL;
        memset(ai,0,sizeof(ai));
        for (i=1;i<=cho[0];++i)
            for (j=1;j<=ci[cho[i]];++j){
                u=ed[cho[i]][j].u;v=ed[cho[i]][j].v;
                ++ai[u][u];++ai[v][v];
                --ai[u][v];--ai[v][u];
            }
        for (i=1;i<n;++i){
            if (ai[i][i]==0){
                for (j=i+1;j<n;++j)
                    if (ai[j][i]!=0){
                        for (k=i;k<n;++k) swap(ai[i][k],ai[j][k]);
                        break;
                    }
                if (!ai[i][i]) return 0LL;
            }for (j=i+1;j<n;++j){
                kk=ai[j][i]*mi(ai[i][i],p-2LL)%p;
                for (k=i;k<n;++k)
                    ai[j][k]=((ai[j][k]-ai[i][k]*kk)%p+p)%p;
            }
        }for (i=1;i<n;++i) cc=cc*ai[i][i]%p;
        return (cc%p+p)%p;
    }
    int main(){
        int i,j,u,v;LL ans=0LL;scanf("%d",&n);
        for (i=1;i<n;++i){
            scanf("%d",&ci[i]);
            for (j=1;j<=ci[i];++j){
                scanf("%d%d",&u,&v);
                ed[i][j]=(use){u,v};
            }
        }for (i=1;i<(1<<(n-1));++i){
            cho[0]=0;
            for (j=1;j<n;++j)
                if ((i>>(j-1))&1) cho[++cho[0]]=j;
            if ((n-cho[0])%2==0) ans=((ans-calc())%p+p)%p;
            else ans=(ans+calc())%p;
        }printf("%I64d
    ",ans);
    }
    View Code

    bzoj4635 数论小测验

    题目大意:已知一个长度为n的数列a,1<=ai<=m,有两问:1)求gcd(a1,...,an)=k的数列个数;2)求k|lcm(a1,...,an)的数列个数。其中k∈[l,r]。

    思路:第一问比较简单,用mu的容斥,ans=sigma(k=l~r)sigma(i=1~m/k)mu[i]*(m/k/i)^n,两个simga都可以√n的统计;第二问对k和ai分解质因数,k=∏pi^yi,ai=∏pi^xi,max(xi)>=yi。比较好求的是max(xi)<yi(!!),可以预处理gi[i][j]表示1~i中和j互质的数的个数,令x=∏pi,满足条件的数列个数是(sigma(d|(k/x))gi[m/d][x])^n,这里的gi并不会重复统计,因为gi中的数都没有x的质因子,而乘上k/x的约数d之后是互不相同的。可以枚举k,dfs哪些pi是<的,算出kk=∏pi^xi的答案,然后通过容斥算出总答案。注意到m、n和kk确定的时候,满足max(xi)<yi的数列是可以预处理出来的bi。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long
    #define N 10000005
    #define M 1005
    #define p 1000000007LL
    using namespace std;
    int prime[N]={0},mu[N],sm[N]={0},gi[M][M],pr[M][2],pt;
    LL bi[M];
    bool flag[N]={false};
    void shai(int up){
        int i,j;mu[1]=1;
        for (i=2;i<up;++i){
            if (!flag[i]){prime[++prime[0]]=i;mu[i]=-1;}
            for (j=1;j<=prime[0]&&i*prime[j]<up;++j){
                flag[i*prime[j]]=true;
                if (i%prime[j]) mu[i*prime[j]]=-mu[i];
                else{mu[i*prime[j]]=0;break;}
            }
        }for (i=1;i<up;++i) sm[i]=sm[i-1]+mu[i];
    }
    LL mi(LL x,LL y){
        LL a=1LL;
        for (;y;y>>=1){
            if (y&1LL) a=a*x%p;
            x=x*x%p;
        }return a;}
    void add(LL &x,LL y){x+=(y%p+p)%p;if (x>=p) x-=p;}
    LL calc1(){
        LL n,m,ll,rr,i,li,j,lj,up,ans=0LL,ci;
        scanf("%I64d%I64d%I64d%I64d",&n,&m,&ll,&rr);
        for (i=ll;i<=rr;i=li+1){
            li=min(rr,m/(m/i));ci=0LL;
            for (up=m/i,j=1LL;j<=up;j=lj+1){
                lj=up/(up/j);
                add(ci,mi(up/j,n)*(sm[lj]-sm[j-1]));
            }add(ans,ci*(li-i+1));
        }return ans;
    }
    int gcd(int a,int b){return (!b ? a : gcd(b,a%b));}
    LL ans;
    int n,m;
    void dfs(int i,int x,int y,int z){
        if (i>pt){
            add(ans,bi[z]*x);
            return;
        }dfs(i+1,x,y,z);
        dfs(i+1,-x,y*pr[i][0],z*pr[i][1]);
    }
    LL calc2(){
        int ll,rr,i,j,k,up;ans=0LL;
        scanf("%d%d%d%d",&n,&m,&ll,&rr);
        for (i=1;i<=m;++i){
            bi[i]=0LL;
            for (k=1,j=1,up=i;j<=prime[0]&&prime[j]*prime[j]<=i;++j)
                if (up%prime[j]==0){
                    k*=prime[j];
                    for (;up%prime[j]==0;up/=prime[j]);
                }
            if (up!=1) k*=up;
            for (up=i/k,j=1;j*j<=up;++j){
                if (up%j) continue;
                add(bi[i],gi[m/j][k]);
                if (j*j!=up) add(bi[i],gi[m/(up/j)][k]);
            }bi[i]=mi(bi[i],(LL)n);
        }for (i=ll;i<=rr;++i){
            for (k=i,pt=0,j=1;j<=prime[0]&&i!=1;++j)
                if (k%prime[j]==0){
                    pr[++pt][0]=prime[j];
                    pr[pt][1]=1;
                    for (;k%prime[j]==0;k/=prime[j],pr[pt][1]*=prime[j]);
                }
            dfs(1,1,1,1);
        }return ans;
    }
    int main(){
        int t,ty,i,j;
        scanf("%d%d",&t,&ty);
        if (ty==1) shai(N);
        else{
            shai(M);
            memset(gi,0,sizeof(gi));
            for (i=1;i<M;++i)
                for (j=1;j<M;++j) gi[i][j]=gi[i-1][j]+(gcd(i,j)==1);
        }while(t--){
            if (ty==1) printf("%I64d
    ",calc1());
            else printf("%I64d
    ",calc2());
        }
    }
    View Code
  • 相关阅读:
    mysql小记
    mysql多实例安装
    源码编译安装mysql
    url监控
    ping命令的用法大全!
    JSON结构
    <a href="onclick="javascript:goSearch(this)" class="click" name="Java">Java</a>为什么a标签的父节点获取不到
    处理jquery版本之间冲突
    C# 语言如何获取json格式的数据,不用javascript用c#实现。。。
    在C#用HttpWebRequest中发送GET/HTTP/HTTPS请求【转载】
  • 原文地址:https://www.cnblogs.com/Rivendell/p/5336233.html
Copyright © 2011-2022 走看看