计数DP

T1 NS

某道没做出来的题的加强版。
然后这个序列要构造出来,
定义 Fi,j 为长度为 i j结尾的符合要求的序列个数。
发现将整个序列大于等于某个数的一部分增加1,
是仍然符合这个序列之前的要求的,
所以我们可以写一个 O(n3) 的DP,
然后差分优化。

代码
#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define Mod 1000000007

#define M 1004
#define Add(a,b) (a+=b)%=Mod
int n;
char S[M];
int F[M][M];
int main(){
    while(scanf("%s",S+1)!=EOF){
        n=strlen(S+1);
        memset(F,0,sizeof(F));
        F[0][1]=1;
        REP(i,1,n+1){
            int Val=0;
            REP(j,1,i+1){
                Add(Val,F[i-1][j]);
                if(S[i]=='D') Add(F[i][1],Val),Add(F[i][j+1],Mod-Val);
                else if(S[i]=='I') Add(F[i][j+1],Val);
                else Add(F[i][1],Val);
            }
        }

        int Ans=0,Val=0;
        REP(i,1,n+2){
            Add(Val,F[n][i]);
            Add(Ans,Val);
        }

        printf("%d\n",Ans);
    }
    return 0;
}

T2 HJ

比较正常的计数题。
用到一种方法将连着的相同颜色的边不做处理。
令颜色相同的边连续地加入,即可将颜色一维转换为时间维。
即可分为两种情况讨论。
然后算DP就比较正常了。
容斥的地方写错调了一会。

代码
#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define LL long long

#define M 300004
#define N 100004

int n,Val[M];
int Next[M<<1],V[M<<1],C[M<<1],Head[M],tot;
void Add_Edge(int u,int v,int c){
    Next[++tot]=Head[u],V[Head[u]=tot]=v,C[tot]=c;
}
#define LREP(i,A) for(int i=Head[A];i;i=Next[i])
//定义F[A]为由A向上的合法路径的条数。
//定义G[A]为由A向上的合法路径的权值之和。
//考虑每一条路径经过A。 
LL G[M],F[M],Ans; 
void DFS(int A,int f,int fc){
    int B;

    LL Fp=0,Gp=0;
    int Lastc=0;

    F[A]=1;
    G[A]=Val[A];

    LREP(i,A)if((B=V[i])!=f){
        int c=C[i];
        DFS(B,A,c);
        if(c!=Lastc) G[A]+=Gp,F[A]+=Fp,Gp=Fp=0,Lastc=c;
        Ans+=G[A]*F[B]+G[B]*F[A];
        Gp+=G[B]+F[B]*Val[A],Fp+=F[B];
    }
    G[A]+=Gp,F[A]+=Fp;

    LREP(i,A)if((B=V[i])!=f){
        int c=C[i];
        if(c==fc)F[A]-=F[B],G[A]-=G[B]+F[B]*Val[A];
    }
}
vector<int>X[N],Y[N];
#define pb push_back
#define SZ(a) ((int)(a).size())
int main(){
    while(scanf("%d",&n)!=EOF){
        memset(Head,tot=0,sizeof(Head));

        REP(i,1,N)X[i].clear(),Y[i].clear();

        REP(i,1,n+1)scanf("%d",&Val[i]);
        REP(i,1,n){
            int u,v,c;
            scanf("%d %d %d",&u,&v,&c);
            X[c].pb(u),Y[c].pb(v);
        }

        REP(c,1,N) REP(i,0,SZ(X[c])){
            int u=X[c][i],v=Y[c][i];
            Add_Edge(u,v,c);
            Add_Edge(v,u,c);
        }

        memset(F,0,sizeof(F));
        memset(G,0,sizeof(G));

        Ans=0;
        DFS(1,0,0);

        printf("%lld\n",Ans);
    }
    return 0;
}

T3 TD

ACM比赛里只有8人弄出来的题,RT
枚举放两个重塔的行和列,那么被删掉的行列都是可知的。
然后排列组合算出这样的方案数为:
对于每行: Cn,iCm,2i(2i)!2i
然后在删掉这些行列基础上算列:
Cni,2jCm2i,j(2j)!2j
那么问题就转换为了只放“轻塔”的情况。
然后枚举放了 k 个这样的塔,
那么方案数是Cni2j,kCm2ij,kk!
然而重塔和轻塔此时还是没区分开,
考虑此时重塔最少和最多出现的个数。
这部分的方案数是一个累计的组合数,前缀和处理即可。

代码
#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define LL long long

#define M 204
#define Mod 1000000007
#define Add(a,b) (a+=b)%=Mod
#define Mul(a,b) (1ll*(a)*(b)%Mod)
#define C2(a) ((a)*((a)-1)>>1) 
int T,n,m,q,p;
int C[M][M],Sum[M][M],Fac[M<<1],Inv[M];
int Pow(int x,int p){
    int res=1,k=x;
    for(;p;k=Mul(k,k),p>>=1)if(p&1)res=Mul(res,k);
    return res;
}
int main(){
    REP(i,0,M-1){
        C[i][0]=Sum[i][0]=1;
        REP(j,1,i+1)
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%Mod;
        REP(j,1,M) Sum[i][j]=(Sum[i][j-1]+C[i][j])%Mod;
    }

    Fac[0]=Inv[0]=1;
    REP(i,1,M<<1)Fac[i]=Mul(i,Fac[i-1]);
    REP(i,1,M)Inv[i]=(Inv[i-1]<<1)%Mod;
    REP(i,1,M)Inv[i]=Pow(Inv[i],Mod-2);

    scanf("%d",&T);
    while(T--){
        scanf("%d %d %d %d",&n,&m,&p,&q);
        int Val=1,Ans=0;

        REP(i,0,n+1) REP(j,0,m+1){
            int i2=i<<1,j2=j<<1;
            if(i2+j2>p || i+j2>n || i2+j>m)break;
            int Val,x=n-i-j2,y=m-i2-j;
            Val=Mul(Mul(C[n][i],C[m][i2]),Mul(Fac[i2],Inv[i]) );
            Val=Mul(Val,Mul(Mul(C[m-i2][j],C[n-i][j2]),Mul(Fac[j2],Inv[j])));

            int Res=0;
            REP(k,0,p+q-i2-j2+1){
                if(k>x || k>y)break;
                int minp=max(0,k-q),maxp=min(k,p-i2-j2);
                Add(Res,Mul(Mul(Fac[k],Mul(C[x][k],C[y][k])),
                (Sum[k][maxp]+Mod-(minp?Sum[k][minp-1]:0))%Mod));
            }
            Add(Ans,Mul(Val,Res));
        }
        Add(Ans,Mod-1);
        printf("%d\n",Ans);
    }
    return 0;
}

T4 K

状压DP以及枚举子集。
这里看起来有一个图的生成树计数。
不过可以直接用状压枚举子集来容斥写,
注意一下重复,严格包含某个元素即可
其实复杂度比那个生成树计数要小
然后就是把几个连通块拼在一起得到结果,
排列数目重复,除去 k! 即可。

代码
#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define LL long long

#define Mod 1000000009
#define M 24
#define Add(a,b) ((a+=b)%=Mod)
#define Mul(a,b) (1ll*(a)*(b)%Mod)

int T,E[M],n,m,K;
int F[(1<<14)+4][15];
int P[(1<<14)+4],Cnt[(1<<14)+4],C[M][M];
int Inv[M];
int Pow(int x,int p){
    int res=1,k=x;
    for(;p;k=Mul(k,k),p>>=1)if(p&1)res=Mul(res,k);
    return res;
}
int main(){
    REP(i,0,M-1){
        C[i][0]=1;
        REP(j,1,i+1)C[i][j]=(C[i-1][j-1]+C[i-1][j])%Mod;
    }
    Inv[0]=1;
    REP(i,1,M)Inv[i]=1ll*Inv[i-1]*i%Mod;
    REP(i,2,M)Inv[i]=Pow(Inv[i],Mod-2);

    scanf("%d",&T);
    REP(Case,1,T+1){
        scanf("%d %d %d",&n,&m,&K);

        memset(E,0,sizeof(E));

        while(m--){
            int u,v;
            scanf("%d%d",&u,&v);
            u--,v--;
            E[u]|=1<<v;
            E[v]|=1<<u;
        }

        memset(F,0,sizeof(F));
        memset(P,0,sizeof(P));
        memset(Cnt,0,sizeof(Cnt));

        int Mxt=(1<<n)-1;
        REP(i,1,Mxt+1){
            Cnt[i]=1;
            REP(j,0,n)REP(k,j,n)if( (i&(1<<j)) && (i&E[j]&(1<<k)) ) Add(Cnt[i],Cnt[i]);
            P[i]=Cnt[i];

            int p;
            REP(j,0,n)if(i&(1<<j)){p=j;break;}

            for(int j=(i-1)&i;j;j=(j-1)&i)if(j&(1<<p))
                Add(P[i],Mul(Cnt[i^j],Mod-P[j]));
        }

        int Val;
        F[0][0]=1;
        REP(k,0,K) REP(i,0,Mxt)if(Val=F[i][k])
            for(int p=i^Mxt,j=p;j;j=(j-1)&p)
                Add(F[i|j][k+1],Mul(Val,P[j]));

        printf("Case #%d:\n%lld\n",Case,Mul(F[Mxt][K],Inv[K]));
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值