容斥

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\n",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\n",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("\n");
        }
    }
}
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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",calc1());
        else printf("%I64d\n",calc2());
    }
}
View Code

 

posted @ 2016-03-30 10:55  Rivendell  阅读(756)  评论(0编辑  收藏  举报