zoukankan      html  css  js  c++  java
  • 状压DP

    状压

    一般看到数据范围是16左右的时候想状压dp

    解决两类问题:

    集合问题

    棋盘问题

    O((n^4))

    for(int s=0;s<(1<<n);s++)
        for(int t=0;t<(1<<n);t++)
            if(t&s==t)
    

    O((n^3))

    for(int s=0;s<(1<<n);s++)
        for(int t=s;t>0;t=(t-1)&s)
    

    方格取数

    #include <bits/stdc++.h>
    using namespace std;
    int a[19][1 << 17];
    int f[19][1<<17];
    int tot[1<<17];
    int calc(int i,int x){
        int cnt = 1,res = 0;
        while(x){
            if(x & 1)  res += a[i][cnt];
            x /= 2;cnt++;
        }
        return res;
    }
    int main()
    {
        ios::sync_with_stdio(false);
        int n;
        while(cin >> n){
            int cnt = 0,ans = 0;
            for(int i = 0;i < (1 << n);i++)   if((i & (i >> 1)) == 0) tot[++cnt] = i;
            for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) cin >> a[i][j];
            for(int i = 1;i <= n;i++){
                for(int k = 1;k <= cnt;k++){
                    int val = calc(i,tot[k]);
                    for(int j = 1;j <= cnt;j++){
                        if((tot[j] & tot[k]) == 0){
                            f[i][k] = max(f[i][k],f[i - 1][j] + val);
                        }
                    }
                }
            }
            for(int j = 1;j <= cnt;j++)  ans = max(ans,f[n][j]);
            cout << ans << endl;
        }
        return 0;
    
    }
    

    例1:互不侵犯

    在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

    ( 1 <=N <=9, 0 <= K <= N * N)

    f{i}{j}{k}第 i 行、此行放什么状态(用 j 表示)、包括这一行 已经使用了的国王数 k 。

    我们预先处理出每一个状态(sit[x])其中包含二进制下1的个数,以及此状态下这一行放的国王个数(gs[x])

    #include<iostream> 
    #include<cstdio> 
    #include<cstring>
    using namespace std;
    typedef long long LL;
    LL f[10][1<<10][100],sum;
    int n,k,cnt;
    int pre[1<<10],num[1<<10]; 
    int lowbit(int x){//处理出每行的国王数
    	int ans=0;
    	while(x){
    		ans+=(x&1);
    		x>>=1;
    	}
    	return num[cnt]=ans;
    }
    int main(){
    	scanf("%d%d",&n,&k);
    	for(int i=0;i<(1<<n);i++){//预处理第一行
    		if(!(i&(i<<1))&&!(i&(i>>1)))
    		pre[++cnt]=i,f[1][cnt][lowbit(i)]=1;
    	}
    	for(int i=2;i<=n;i++){
    		for(int j=1;j<=cnt;j++){//枚举当前行
    			int x=pre[j];
    			for(int s=1;s<=cnt;s++){//枚举上一行
    				int y=pre[s];
    				if((x&y) || (x&(y<<1)) || (x&(y>>1)))continue;
    				for(int u=0;num[j]+u<=k;u++)
    					f[i][j][num[j]+u]+=f[i-1][s][u];
    			}
    		}
    	}
    	for(int i=1;i<=cnt;i++)
    		sum+=f[n][i][k];
    	printf("%lld",sum);
    	return 0;
    } 
    

    例2:Corn Fields G

    #include<iostream> 
    #include<cstring> 
    #include<cstdio> 
    using namespace std;
    const int mod=100000000;
    int n,m,ans=0;
    int a[15][15];
    int G[13];
    int f[13][1<<17];
    int h[1<<17];//判断合法 
    int main(){
    	scanf("%d%d",&m,&n);
    	for(int i=1;i<=m;i++)
    		for(int j=1;j<=n;j++){
    			scanf("%d",&a[i][j]);
    			G[i]=(G[i]<<1)+a[i][j];
    		}
    	for(int i=0;i<(1<<n);i++)
    		h[i]=(!((i<<1)&i) && !((i>>1)&i));
    	f[0][0]=1;
    	for(int i=1;i<=m;i++)
    		for(int j=0;j<(1<<n);j++)
    			if(h[j]&&((j&G[i])==j))
    				for(int k=0;k<(1<<n);k++)
    					if(!(k&j))f[i][j]=(f[i][j]+f[i-1][k])%mod;
    	for(int i=0;i<(1<<n);i++)
    		ans=(ans+f[m][i])%mod;
    	printf("%d
    ",ans);
    	return 0;
    }
    

    例3:[USACO08NOV] Mixed Up Cows G

    有N头奶牛(4 <= N <= 16),第i头奶牛的编号是Si,每头奶牛的编号都是唯一的。她们在挤奶的时候一定要排成混乱的队伍。在一只混乱的队 伍中,相邻奶牛的编号之差均超过K。比如当K = 1时,1, 3, 5, 2, 6, 4就是一支混乱的队伍, 而1, 3, 6, 5, 2, 4不是,因为6和5只差1。请数一数,有多少种队形是混乱的呢?

    解: f {i}{j}表示以第i只奶牛为结尾的状态为j的队伍混乱的方案数是多少

    然后枚举最后一头牛

    #include<cstdio> 
    #include<iostream> 
    #include<cmath> 
    using namespace std;
    #define LL long long
    LL n,m,a[18],f[18][1<<18],ans;
    int main(){
    	scanf("%lld%lld",&n,&m);
    	for(LL i=1;i<=n;i++)scanf("%lld",&a[i]);
    	for(LL i=1;i<=n;i++)
    		f[i][1<<(n-i)]=1;
    	for(LL j=1;j<(1<<n);j++)//注意:先枚举状态 
    		for(LL i=1;i<=n;i++){
    			if(!(j&(1<<(n-i))))continue;
    			for(LL k=1;k<=n;k++){
    				if(j&(1<<(n-k)))continue;
    				if(abs(a[k]-a[i])>m)f[k][(1<<(n-k))|j]+=f[i][j];
    			}
    		}
    	for(LL i=1;i<=n;i++)
    		ans+=f[i][(1<<n)-1];
    	printf("%lld",ans);
    	return 0;
    }
    

    例4:题目

    给出n个物品,体积为w[i],现把其分成若干组,要求每组总体积<=W,问最小分组。(n<=18)

    #include<iostream> 
    #include<cstdio> 
    #include<cstring>
    using namespace std;
    typedef long long LL;
    int n,m,w[20],f[1<<19],g[1<<19];
    //f[i]就代表当前状态为i时最小组数 ,g[]来记录当前状态下剩余的体积
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    		scanf("%d",&w[i]);
    	memset(f,63,sizeof(f));
    	f[0]=1;
    	g[0]=m;
    	for(int i=0;i<(1<<n);i++){
    		for(int j=1;j<=n;j++){
    			if(i&(1<<(j-1)))continue;
    			if(g[i]>=w[j]){
    				if(f[i|(1<<(j-1))]>f[i]){
    					f[i|(1<<(j-1))]=f[i];g[i|(1<<(j-1))]=g[i]-w[j];
    				}
    				else if(f[i|(1<<(j-1))]==f[i])
    				g[i|(1<<(j-1))]=max(g[i|(1<<(j-1))],g[i]-w[j]);
    			}else if(g[i]<w[j]){
    				if(f[i|(1<<(j-1))]>f[i]+1){
    					f[i|(1<<(j-1))]=f[i]+1;
    					g[i|(1<<(j-1))]=m-w[j];
    				}
    				else if(f[i|(1<<(j-1))]==f[i]+1) 
    				g[i|(1<<(j-1))]=max(g[i|(1<<(j-1))],m-w[j]);
    			}
    		}
    	}
    	printf("%d",f[(1<<n)-1]);
    	return 0;
    } 
    

    例5:排列

    给一个数字串 s和正整数 d, 统计 s 有多少种不同的排列能被 d 整除(可以有前导 0)。

    100% 的数据满足:s 的长度不超过 10,1≤d≤1000,1≤T≤15。(多组数据)

    解:

    状压 f { i }{ j }第一维表示状态,第二维表示余数

    /*
    最关键:f[i|(1<<(j-1))][(k*10+a[j])%d]+=f[i][k];
    细节:如1223序列在状态枚举时
    0101 和 0011统计了两次,但其实都是一种排序(排序重复不计)所以用vis来去重(每次重置0)
    */
    #include<iostream> 
    #include<cstdio> 
    #include<cstring>
    using namespace std;
    typedef long long LL;
    bool vis[15];
    int T,d,f[1<<11][1005],a[15];
    char s[15];
    int main(){
    	scanf("%d",&T);
    	while(T--){
    		memset(f,0,sizeof(f));
    		scanf("%s%d",s+1,&d);
    		int n=strlen(s+1);
    		for(int i=1;i<=n;i++)a[i]=s[i]-'0';
    		f[0][0]=1;
    		for(int i=0;i<(1<<n);i++){
    			memset(vis,0,sizeof(vis));
    			for(int j=1;j<=n;j++){
    				if(i&(1<<(j-1)))continue;
    				if(!vis[a[j]]){
    					vis[a[j]]=1;
    					for(int k=0;k<d;k++)
    						f[i|(1<<(j-1))][(k*10+a[j])%d]+=f[i][k];
    				}
    			}
    		}
    		printf("%d
    ",f[(1<<n)-1][0]);
    	}
    	
    	return 0;
    }
    

    例6: 奖励关

    #include <cstdio>
    #include <iostream>
    using namespace std;
    int k,n,s[1<<16],p[20],ss;
    double f[105][1<<16];
    inline int read() {
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int main() {
    	k=read();n=read();	
    	for(int i=1;i<=n;i++) {
    		p[i]=read();
    		while(1) {
    			ss=read();
    			if(ss==0)break;
    			s[i]|=(1<<(ss-1));
    		} 
    	}
    	for(int i=k;i>=1;i--){
    		for(int state=0;state<(1<<n);state++) {
    			for(int j=1;j<=n;j++) {
    				if((s[j]&state)!=s[j]) f[i][state]+=f[i+1][state]; //不满足
    				else  f[i][state]+=max(f[i+1][state],f[i+1][state|1<<(j-1)]+p[j]);
    			}
    			f[i][state]/=n;
    		}
    	}
    	printf("%.6lf
    ",f[1][0]);		
    	return 0;
    }
    

    例7:奇怪的道路

    好吧看到一开始想是状压,然后看到n<=30放弃了,,,反正考试5选3就去调别的了。。。

    好了看题解正解

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int Mod = 1000000007;
    int n,m,k;
    long long f[35][35][1<<10][10];
    signed main(){
    	scanf("%d%d%d",&n,&m,&k);
    	f[1][0][0][0]=1;
    	for(int i=1;i<n;i++){
    		for(int j=0;j<=m;j++){
    			for(int s=0;s<(1<<(k+1));s++){
    				for(int p=0;p<k;p++){
    					if(!f[i][j][s][p]) continue;
    					(f[i][j][s][p+1]+=f[i][j][s][p])%=Mod;//不连边
    					if (j<m && i-k+1+p>0) (f[i][j+1][s^(1<<k)^(1<<p)][p]+=f[i][j][s][p])%=Mod;//连边,影响k和p奇偶性
    					if ((s&1)==0) (f[i+1][j][s>>1][0]+=f[i][j][s][k])%=Mod;//当前点连的是奇数条边,下一个点向当前连边
    				}
    			}
    		}
    	}
    	printf("%lld",f[n][m][0][0]);
    	return 0;
    }
    
  • 相关阅读:
    并发编程学习笔记(八、volitile)
    MySQL调优学习笔记(六、SQL查询优化)
    MySQL调优学习笔记(五、高性能索引)
    MySQL调优学习笔记(四、索引)
    MySQL调优学习笔记(三、数据库优化)
    MySQL调优学习笔记(二、MySQL调优基础)
    MySQL调优学习笔记(一、MySQL基础)
    密码-简单加密
    密码-这不是摩斯密码
    密码-聪明的小羊
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13509571.html
Copyright © 2011-2022 走看看