YbtOJ 「动态规划」第5章 状压DP
犹豫了许久还是决定试试始终学不会的状压 dp。(上一次学这东西可能还是两年前的网课,显然当时在摸鱼一句都没听/kk
果然还是太菜。
例题1.种植方案
设 表示第 行状态为 时的方案数。转移时判断一下满不满足情况。
。
code
#include<bits/stdc++.h>
using namespace std;
const int mod=1e8;
int m,n,sta[4100],cnt;
int f[15][4100],a[15];
int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
{
for(int j=1,x;j<=n;j++)
{
scanf("%d",&x);
a[i]=(a[i]<<1)|(!x);
}
}
for(int i=0;i<(1<<n);i++)
{
if(i&(i<<1)) continue;
sta[++cnt]=i;
}
f[0][1]=1;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=cnt;j++)
{
if(a[i]&sta[j]) continue;
for(int k=1;k<=cnt;k++)
{
if((a[i-1]&sta[k])||(sta[j]&sta[k])) continue;
f[i][j]=(f[i][j]+f[i-1][k])%mod;
}
}
}
int ans=0;
for(int i=1;i<=cnt;i++) ans=(ans+f[m][i])%mod;
cout<<ans<<endl;
return 0;
}
例题2.最短路径
设 表示当前在第 个点且走过的状态为 时的最短路径。
枚举上一步的位置 进行转移。
code
#include<bits/stdc++.h>
using namespace std;
const int inf=2e9;
int n,mp[25][25];
int f[25][1100000];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&mp[i][j]);
for(int i=0;i<n;i++)
for(int j=0;j<(1<<n);j++) f[i][j]=inf;
f[0][1]=0;
for(int j=2;j<(1<<n);j++)
{
for(int i=1;i<n;i++)
{
if(!(j&(1<<i))) continue;
for(int k=0;k<n;k++)
{
if(i==k) continue;
if(!(j&(1<<k))) continue;
int now=j&(~(1<<i));
f[i][j]=min(f[i][j],f[k][now]+mp[k][i]);
}
}
}
cout<<f[n-1][(1<<n)-1]<<endl;
return 0;
}
例题3.涂抹果酱
三进制的状压。先 dfs 出每种状态,开一个数组存起来。
预处理每两种状态能否出现在相邻的两行。
第 行的上面和下面分别 dp。根据乘法原理,最后的答案是上下两部分乘起来。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e6;
int n,m,cnt,K,sta[305][10];
bool flag[305][305];
int f[10005][305],g[10005][305];
int a[10],kk[10],num;
void dfs(int now)
{
if(now==m+1)
{
cnt++;
for(int i=1;i<=m;i++) sta[cnt][i]=a[i];
return;
}
for(int i=1;i<=3;i++)
{
if(i==a[now-1]) continue;
a[now]=i;dfs(now+1);
}
}
void init()
{
dfs(1);
for(int i=1;i<=cnt;i++)
{
for(int j=1;j<=cnt;j++)
{
if(i==j) continue;
bool qwq=0;
for(int k=1;k<=m;k++)
if(sta[i][k]==sta[j][k]) qwq=1;
flag[i][j]=(!qwq);
//cout<<i<<" "<<j<<" "<<flag[i][j]<<endl;
}
}
}
signed main()
{
scanf("%lld%lld",&n,&m);init();
scanf("%lld",&K);
for(int i=1;i<=m;i++) scanf("%lld",&kk[i]);
for(int i=1;i<=cnt;i++)
{
int qwq=0;
for(int j=1;j<=m;j++)
if(sta[i][j]!=kk[j]) qwq=1;
if(!qwq) num=i;
}
f[K][num]=1;
for(int i=K-1;i;i--)
for(int j=1;j<=cnt;j++)
for(int k=1;k<=cnt;k++)
{
if(!flag[j][k]) continue;
f[i][j]=(f[i][j]+f[i+1][k])%mod;
}
int up=0;
if(K==1) up=1;
else for(int i=1;i<=cnt;i++) up=(up+f[1][i])%mod;
for(int i=K+1;i<=n;i++)
for(int j=1;j<=cnt;j++)
for(int k=1;k<=cnt;k++)
{
if(!flag[j][k]) continue;
f[i][j]=(f[i][j]+f[i-1][k])%mod;
}
int down=0;
if(K==n) down=1;
else for(int i=1;i<=cnt;i++) down=(down+f[n][i])%mod;
cout<<up*down%mod<<endl;
return 0;
}
例题4.炮兵阵地
感觉这题是种植方案+互不侵犯套在一起(?
设 表示第 行状态为 ,上一行状态为 时最多能放置的炮兵数。平原和山地的问题参照例题 1。
因为当前行的状态与前两行都有关,所以转移同时分别枚举前两行的状态。
code
#include<bits/stdc++.h>
using namespace std;
int n,m,cnt;
int mp[105],tot[95];
int sta[105],f[105][95][95];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<(1<<m);i++)
{
if((i&(i<<1))||(i&(i<<2))) continue;
sta[++cnt]=i;
for(int j=0;j<m;j++) if((i>>j)&1) tot[cnt]++;
}
for(int i=1;i<=n;i++)
for(int j=0;j<m;j++)
{
char qwq;cin>>qwq;
mp[i]=(mp[i]<<1)|(qwq=='H');
}
for(int i=1;i<=cnt;i++) f[1][i][1]=tot[i];
for(int i=2;i<=n;i++)
{
for(int j=1;j<=cnt;j++)
{
if(sta[j]&mp[i]) continue;
for(int k=1;k<=cnt;k++)
{
if((sta[k]&mp[i-1])||(sta[k]&sta[j])) continue;
for(int l=1;l<=cnt;l++)
{
if((sta[l]&mp[i-2])||(sta[l]&sta[k])||(sta[l]&sta[j])) continue;
f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+tot[j]);
}
}
}
}
int ans=0;
for(int i=1;i<=cnt;i++)
for(int j=1;j<=cnt;j++)
ans=max(ans,f[n][i][j]);
cout<<ans<<endl;
return 0;
}
1.最优组队
设 表示当前分组的状态为 时最大和谐度。每一位 表示这个位置上的人是否已经被分组。
枚举 的子集进行转移。
枚举子集方法:
for(int j=i;j;j=(j-1)&i){
int k=j^i;
}
则 是 的子集, 是 在 内的补集。
code
#include<bits/stdc++.h>
using namespace std;
const int N=66000;
int n,s[N];
int f[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<(1<<n);i++) scanf("%d",&s[i]);
for(int i=1;i<(1<<n);i++)
{
f[i]=s[i];
for(int j=i;j;j=(j-1)&i)
{
int k=j^i;
//cout<<i<<" "<<j<<" "<<k<<endl;
f[i]=max(f[i],f[j]+s[k]);
}
}
cout<<f[(1<<n)-1]<<endl;
return 0;
}
2.最短路径
只需要知道标记点到其他点的距离。对起点、终点、每个标记点跑一遍 dijkstra。
转移可以仿照例题 2 的思路。
code
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
const int N=1e5+5;
const int inf=1e18;
int n,m,K,s,t;
int a[15];
int head[N],cnt;
struct node{
int nxt,to,w;
}e[N];
void add(int u,int v,int w){
e[++cnt]={head[u],v,w};head[u]=cnt;
}
int dis[15][N],f[15][4100];
void dij(int s,int id)
{
priority_queue<pii,vector<pii>,greater<pii> > q;
for(int i=0;i<n;i++) dis[id][i]=inf;
dis[id][s]=0;q.push(pii(0,s));
while(!q.empty())
{
int u=q.top().se;q.pop();
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dis[id][u]+e[i].w<dis[id][v])
{
dis[id][v]=dis[id][u]+e[i].w;
q.push(pii(dis[id][v],v));
}
}
}
}
signed main()
{
scanf("%lld%lld%lld%lld%lld",&n,&m,&K,&s,&t);
s--;t--;
for(int i=1,u,v,w;i<=m;i++)
{
scanf("%lld%lld%lld",&u,&v,&w);u--;v--;
add(u,v,w);
}
dij(s,0),dij(t,K+1);a[0]=s,a[K+1]=t;
for(int i=1;i<=K;i++) scanf("%lld",&a[i]),a[i]--,dij(a[i],i);
for(int i=0;i<=K+1;i++) for(int j=0;j<(1<<(K+2));j++) f[i][j]=inf;
f[0][1]=0;K+=2;
for(int j=2;j<(1<<K);j++)
{
for(int i=0;i<K;i++)
{
if(((1<<i)&j)==0) continue;
for(int k=0;k<K;k++)
{
if(((1<<k)&j)==0||i==k) continue;
int l=j&(~(1<<i));
//cout<<i<<" "<<j<<" "<<k<<" "<<l<<endl;
f[i][j]=min(f[i][j],f[k][l]+dis[k][a[i]]);
//cout<<f[i][j]<<" "<<f[k][l]<<" "<<dis[k][a[i]]<<endl;
}
}
}
if(f[K-1][(1<<K)-1]==inf) cout<<-1<<endl;
else cout<<f[K-1][(1<<K)-1]<<endl;
return 0;
}
3.小绿小蓝
状态设为点集,则要求边权和最大。
最后枚举状态统计答案。
code
#include<bits/stdc++.h>
using namespace std;
const int N=20,M=1.4e5,inf=2e9;
int n,m,mp[N][N];
int a[N],s,t;
int f[N][M],sa[M];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
for(int i=1,u,v,w;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
mp[u-1][v-1]=max(mp[u-1][v-1],w);
}
scanf("%d%d",&s,&t);s--;t--;
for(int i=0;i<(1<<n);i++)
for(int j=0;j<n;j++) sa[i]+=((i>>j)&1)*a[j];
for(int i=0;i<n;i++) for(int j=0;j<(1<<n);j++) f[i][j]=-inf;
f[s][1<<s]=0;
for(int j=0;j<(1<<n);j++)
{
for(int i=0;i<n;i++)
{
if((j&(1<<i))==0) continue;
for(int k=0;k<n;k++)
{
if((j&(1<<k))==0||k==i) continue;
if(!mp[k][i]) continue;
int l=j&(~(1<<i));
f[i][j]=max(f[i][j],f[k][l]+mp[k][i]);
}
}
}
double minn=inf;
for(int i=0;i<(1<<n);i++)
{
if(f[t][i]<0) continue;
minn=min(sa[i]*1.0/f[t][i],minn);
}
printf("%.3lf\n",minn);
return 0;
}
4.擦除序列
预处理每个状态是不是回文串。
code
#include<bits/stdc++.h>
using namespace std;
const int inf=2e9,M=70000,N=20;
int n,f[M],ch[M];
char s[N];
int main()
{
scanf("%s",s);n=strlen(s);
for(int i=0;i<(1<<n);i++)
{
int now[N],cnt=0;
for(int j=0;j<n;j++)
{
if(i&(1<<j)) now[++cnt]=j;
}
bool flag=0;
for(int j=1;j<=cnt;j++)
{
int k=cnt-j+1;
if(s[now[j]]!=s[now[k]]) flag=1;
}
ch[i]=(!flag);
}
for(int i=0;i<(1<<n);i++) f[i]=inf;
f[0]=0;
for(int i=1;i<(1<<n);i++)
{
if(ch[i]) f[i]=1;
for(int j=i;j;j=(j-1)&i)
{
int k=j^i;
if(!ch[j]) continue;
f[i]=min(f[i],f[k]+1);
}
}
cout<<f[(1<<n)-1]<<endl;
return 0;
}
5.图的计数
真·摆了一天。
设 表示第 行的状态(每个点的奇偶性)为 时的最小反转数。
反转边时使用异或转移。
code
#include<bits/stdc++.h>
using namespace std;
const int inf=2e9,mod=998244353;
const int N=1e4+5,M=(1<<10)+5;
int n,m,a[N][M],b[N][M];
int f[N][M];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0,x;i<m;i++)
{
scanf("%d",&x);
a[0][0]+=x*(1<<i);
}
f[2][a[0][0]]=1;
for(int i=2;i<=n-2;i++)
{
for(int k=1;k<=m;k++)
{
for(int p=1,x;p<=m;p++)
{
scanf("%d",&x);
a[i][k]+=x*(1<<(p-1));
b[i][p]+=x*(1<<(k-1));
}
}
}
for(int i=3;i<=n-1;i++)
{
for(int j=0;j<(1<<m);j++)
{
if(f[i-1][j])
{
int s1=0,s2=0;
for(int k=1;k<=m;k++)
{
if(j&1<<(k-1)) s1^=a[i-1][k],s2^=b[i-1][k];
}
//(f[i][s1]+=f[i-1][j])%=mod;
f[i][s1]=(f[i][s1]+f[i-1][j])%mod;
f[i][s2]=(f[i][s2]+f[i-1][j])%mod;
}
}
}
for(int i=1,x;i<=m;i++)
{
scanf("%d",&x);
a[n-1][0]+=x*(1<<(i-1));
}
int ans=0;
for(int j=0;j<(1<<m);j++)
{
int tot=0;
for(int i=1;i<=m;i++)
{
if((a[n-1][0]&(1<<(i-1)))&&(j&(1<<(i-1)))) tot^=1;
}
if(!tot) ans=(ans+f[n-1][j])%mod;
}
cout<<ans<<endl;
return 0;
}
本文来自博客园,作者:樱雪喵,转载请注明原文链接:https://www.cnblogs.com/ying-xue/p/16598585.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!