状态压缩题目题解
状压题目详解
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;
}
不关注的有难了😠😠😠https://b23.tv/hoXKV9