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

    首发于摸鱼世界


    状压DP,即状态压缩DP,它的精髓在于把DP过程中的一个“状态”用一个二进制数巧妙的表示出来。接下来就从一些入门的状压题目来感受一下状压的魅力吧~

    洛谷P5911 [POI2004]PRZ

    大致题意:

    (n)个人过最大承载(W)重量的桥,每个人有重量(w_i)与过桥时间(t_i),多人一组时间取(Max)。求最短过桥时间。

    注意数据范围:(100≤W≤400,1≤t≤50,10≤w≤100,1≤n≤16)

    有没有注意到(n)的数据范围很耀眼。没错,这就是我们的突破口。

    先想暴力记忆化搜索,每个状态记录某些人过桥后的最小时间花费。为了记录状态,我们需要一个(bool)数组,甚至还可以瞎**回溯。

    一通乱搞发现,盲目搜索就是过不了


    于是来优化吧!

    每个状态我们除了用一个数组记录每个人是否加入,还有什么办法吗?

    状!态!压!缩!

    因为人数比较少,我们可以用一个(n)位的二进制数来记录一个状态。所以这个数转成十进制最大只有(2^{16})。可以存在数组的一维中。

    考虑用(dp[i])表示状态为(i)下的最少时间花费。它可以由什么状态推导得到呢?

    假设当前状态是(0111),我们可以把它拆成({0100,0011},{0011,0100})另外两个状态。

    那么如何枚举呢?这就需要熟练掌握(C++)中的位运算符。很明显,我们要先找到所有被状态(i)包含的状态(j),包含即(j)中所含的1在(i)中同一个位置一定也是1,但反过来就不一定了。

    比如(1010)可以延伸到:(1010,1000,0010,0000)四个状态。

    代码怎么实现呢?非常的amazing啊:for(int j=i;j>=0;j=(j-1)&i)

    先给状态(j)减去一个1,再和(i)进行按位与,同时保证了不会漏枚和(j)(i)包含。

    那另外一个状态呢?进行 按位异或 就好了。比如上面的(0111⊕0100=0011),就可以把状态(i)拆成(j)(i⊕j)两种了。

    于是就有了:

    (dp[i]=min(dp[i],dp[j]+t[i⊕j])(w[i]<=W))

    其中(t[i])预处理为状态(i)时的时间花费,(w[i])预处理为状态(i)时的重量和,初始值(dp[i]=INF)

    于是这道题就完成了。

    单一个一维表示状态的题,应该还不算难

    (CODE:)

    #include<bits/stdc++.h>
    #define INF 0x3f3f3f3f
    using namespace std;
    const int N=(1<<16)+1;
    int dp[N],ti[N],we[N];
    int t[17],c[17];
    int main()
    {
    	int w,n;
    	cin>>w>>n;
    	for(int i=1;i<=n;i++)
    		scanf("%d%d",&t[i],&c[i]);
    	for(int i=0;i<1<<n;i++)
    	{
    		int d=i,cnt=0;
    		while(d)
    		{
    			cnt++;
    			if(d&1)
    				ti[i]=max(ti[i],t[cnt]),
    				we[i]+=c[cnt];
    			d>>=1;
    		}
    		dp[i]=INF;
    	}
    	dp[0]=0;
    	for(int i=0;i<1<<n;i++)
    		for(int j=i;j>=0;j=(j-1)&i)
    		{
    			if(we[i^j]<=w)dp[i]=min(dp[i],ti[i^j]+dp[j]);
    			if(j==0)break;
    		}
    	printf("%d
    ",dp[(1<<n)-1]);
    	return 0;
    }
    

    POJ-2441 Arrange the bulls

    大致题意:

    (N)头牛和(M)个谷仓,每头牛只会选择自己喜欢的谷仓,问有多少种方案可以让每头牛都在自己喜欢的谷仓中。

    其中,(1≤N,M≤20)

    一道简单的状压DP但是好像可能会MLE。但是只是练习状压DP的话是一道好题。

    考虑用(dp[i][j])表示状态(i)下,有(j)头牛都选择了合适位置的方案数。状态存储谷仓的选择情况。

    所以初值为(dp[0][0]=1)。枚举每一头牛(j),在(i)状态下如果他喜欢的谷仓(v)被占用了((i&1<<(v-1))),那么我们就(dp[i][j]+=dp[ihat{}1<<(v-1)][j-1]),即把原有的那头牛移开并把自己放进去,所以把原来的方案数累加上来。

    最后(ans=sum_{i=1}^{2^M} dp[i][n])

    (CODE:)

    #include<bits/stdc++.h>
    using namespace std;
    int dp[1<<20][21];
    int a[21];
    int s[21][21];
    int main()
    {
    	int n,m;
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    		for(int j=1;j<=a[i];j++)
    			scanf("%d",&s[i][j]);
    	}
    	dp[0][0]=1;
    	for(int j=1;j<=n;j++)
    		for(int i=1;i<1<<m;i++)
    			for(int k=1;k<=a[j];k++)
    				if(i&1<<(s[j][k]-1))dp[i][j]+=dp[i^1<<(s[j][k]-1)][j-1];
    	int ans=0;
    	for(int i=1;i<=1<<m;i++)
    		ans+=dp[i][n];
    	printf("%d
    ",ans);
    	return 0;
    }
    

    洛谷 P1278 单词游戏

    大致题意:

    给定(N(N≤16))个单词,任意选择其中单词,要求为后一个单词的开头必须与前一个单词的末尾字符相同。求最大能选择的单词总长度。

    依然状压呗。

    (dp[i][j])表示(i)状态下以字符(j)结尾的最大单词总长度。其中状态(i)压入了这(N)个单词的选择情况。

    当然,首尾字符因为只有五个,我们一一映射到1-5或者0-4是没有问题的,我为了方便,就直接把它们减去'A'作为标识就行了。记(a[j],b[j])分别标识第(j)个单词的首尾标识,(l[j])记录第(j)个单词的长度,即对答案的贡献。

    再看转移方程。如果当前第(j)个单词没有被选入枚举的状态(i),那么

    (dp[i+2^j][b[j]]=Max{dp[i+2^j][b[j]],dp[i][a[j]]+l[j] })

    其中初始值为(dp[2^i][b[i]=l[i])

    注意对于每一个(dp)值,都去对(ans)取个(Max)就好了。

    (CODE:)

    #include<bits/stdc++.h>
    using namespace std;
    const int N=20;
    string x;
    int a[N],b[N],l[N];
    int f[1<<17][27];
    int n;
    int main()
    {
    	scanf("%d",&n);
    	int ans=-1;
    	for(int i=1;i<=n;i++)
    	{
    		cin>>x;
    		a[i]=x[0]-'A',b[i]=x[x.size()-1]-'A',l[i]=x.length();
    		f[1<<i][b[i]]=l[i],ans=max(ans,f[1<<i][b[i]]);
    	}
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			for(int s=0;s<=1<<16;s++)
    				if(!(s&1<<j))
    					ans=max(ans,f[s+(1<<j)][b[j]]=max(f[s+(1<<j)][b[j]],f[s][a[j]]+l[j]));
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    oldboy_python_bankSystem practice
    【HCIE-RS复习】- PPP
    【HCIE-RS】PPP详解
    【HCIE-RS】考试说明
    【HCIE-RS】杭州考场(个人考试心得体会)
    DataWorks功能实践速览 05——循环与遍历
    Serverless 工程实践 | 零基础上手 Knative 应用
    前后端、多语言、跨云部署,全链路追踪到底有多难?
    多任务多目标CTR预估技术
    开放搜索查询分析服务架构解读
  • 原文地址:https://www.cnblogs.com/moyujiang/p/13383475.html
Copyright © 2011-2022 走看看