状态压缩题目题解

状压题目详解

P3475 [POI2008]POD-Subdivision of Kingdom

本题是让我们求出来点集合,看数据范围,\(n\leq 26\)

我们枚举所有点存在的情况,普通的状压肯定不行( \(26\) 还是挺大的).

我们因此考虑搜索:因为 \((^{26}_{13}) \approx 10^7\)

因此直接搜索出所有情况即可。

处理方式:加入一个点时,通过位运算算出贡献:

#include<bits/stdc++.h>
using namespace std;
const int N=26,M=1<<13;
int n,m;
unsigned int lk[N];
int cnt[M],ans,ansu;
int Calc(unsigned int x){
    return cnt[x&M-1]+cnt[x>>13];
}
void dfs(int x,unsigned int u1,unsigned int u2,int c1,int c2,int sum){
    if(x==n){
        if(sum>ans) ans=sum,ansu=u1;
        return;
    }
    if(c1<m)dfs(x+1,u1+(1<<x),u2,c1+1,c2,sum+Calc(lk[x]&u1));
    if(c2<m)dfs(x+1,u1,u2+(1<<x),c1,c2+1,sum+Calc(lk[x]&u2));
}

int main(){
    cin>>n>>m;
    if(n==2){ puts("1"); return 0;}
    while(m--){ int x,y;
        scanf("%d%d",&x,&y);
        --x;--y;
        lk[x]|=1<<y;lk[y]|=1<<x;
    }
    for(int i=1;i<M;i++) cnt[i]=cnt[i-(i&-i)]+1;
    ans=-1;m=n/2;
    dfs(1,1,0,1,0,0);
    for(int i=1;i<=n;i++) if(ansu&(1<<i-1)) printf("%d ",i);
    system("pause");
    return 0;
}

P2167 [SDOI2009]Bill的挑战

这种题目,需要转移,肯定是 \(dp\) ,而且从数据范围看是状压。

我们设 \(dp[i][S]\) 表示当前正在 \(dp\)\(i\) 位,\(S\) 表示有哪些串已经不合法了(合法为 \(1\),不合法为 \(0\))

转移的话枚举一个字母,然后填上去,判断一下现在有那些串不合法就可以了

// P2167 [SDOI2009]Bill的挑战
#include<bits/stdc++.h>
#define mod 1000003
#define ll long long
using namespace std;
char a[100][100];
ll dp[52][(1<<17)+1];int n,K,len;
int getnum(int S){
    int num=0;
    while(S!=0){
        if(S%2==1) num++;
        S/=2;
    }
    return num;
}
void work(){
    cin>>n>>K;
    for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
    len=strlen(a[1]+1);
    memset(dp,0,sizeof(dp));
    dp[1][(1<<n)-1]=1;
    for( int i=1;i<=len;i++)//通过第一个字符串
        for( int S=(1<<n)-1;S>0;S--)
            if(dp[i][S]!=0){
                if(getnum(S)<K) continue;
                for(int now=0;now<=25;now++){
                    int toS=S;
                    for(int k=1;k<=n;k++)
                        if(a[k][i]!='?'&&a[k][i]!='a'+now)
                            if(S&(1<<(k-1))) toS-=(1<<(k-1));
                    dp[i+1][toS]+=dp[i][S],dp[i+1][toS]%=mod;
                }
            }
    ll ans=0;
    for(int S=(1<<n)-1;S>0;S--)
        if(getnum(S)==K) ans+=dp[len+1][S],ans%=mod;
    printf("%lld\n",ans);
}

int main()
{
    int t;cin>>t;
    while(t--) work();
    system("pause");
    return 0;
}

P2465 [SDOI2008] 山贼集团

我们设置 \(P\) 作为状压,表示每个分部门是否选择。

我们发现,题目给出的一个关键地方为,不同分部门之间存在影响

既然如此,我们不妨思考一下,对于每一个状态,他们肯定有一个权值.

而这个权值,必然就是若干个分部门同时选择,产生的价值.

我们设 \(f[i][S]\) 表示在以 \(i\) 为根的子树里,分部门选择状态为 \(S\) 的最大收益

状态转移,其实就类似于树上背包一样。

//本题目的算法为,背包DP+树型DP+状压DP,是一道DP的优秀题目
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) ((x)&(-x))//x的最靠右边的1 
const int N=110,S=(1<<12)+3;
int n,p,M,t,a[N][N],f[N][S],val[S];
vector<int> g[N];
inline int tot(int x)//统计x二进制表示下的位数
{
	int cnt=0;
	while(x){
		cnt++;
		x>>=1;
	}
	return cnt;
}
void dfs(int x,int fa)
{
	for(int y:g[x]){
		if (y==fa) continue;
		dfs(y,x);
		for(int s=M;s;s--)//遍历所有集合,类似于背包问题的j,从M~1.
			for(int t=s;t;t=t=(t-1)&s)//从子集转移过来
				f[x][s]=max(f[x][s],f[x][s^t]+f[y][t]);//相当于加入第y个物品,它的体积是t
		//也可以认为是 t->s的一种转移
	}
	for(int s=0;s<=M;s++)//遍历所有集合
		f[x][s]+=val[s];//加上这个状态,分部门该有的影响
}
inline void init()
{
	scanf("%d%d",&n,&p);
	M=(1<<p)-1;//所有分部门都安排好该有的状态表示
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		g[u].push_back(v),g[v].push_back(u);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=p;j++)	scanf("%d",&a[i][j]);
		for(int j=1;j<=M;j++)	f[i][j]=f[i][j ^ lowbit(j)]-a[i][tot(lowbit(j))];
		//在这里lowbit(j)为j的第一个是1的位置.tot(lowbit(j))这个1所在的位置
	}
	scanf("%d",&t);
	while(t--){
		int v,c,x=0,s=0;
		scanf("%d%d",&v,&c);
		for(int i=1;i<=c;i++)
			scanf("%d",&x),s|=(1<<(x-1));//s表示本次操作收到影响分部门集合
		int p=s^M;//取出所有不受影响的位置
		for(int t=p;t;t=(t-1)&p) val[(t|s)]+=v;//所有包含s的集合,都要+v的影响
		val[s]+=v;//完全包含也要+v
	}
	dfs(1,0);
	printf("%d\n",f[1][M]);//1是根节点,M为所有分部门都安排好了,这就是最后的答案
}
signed main()
{
	init();
    system("pause");
	return 0;
}

posted @ 2021-09-17 19:41  Evitagen  阅读(35)  评论(0编辑  收藏  举报