犹豫了许久还是决定试试始终学不会的状压 dp。(上一次学这东西可能还是两年前的网课,显然当时在摸鱼一句都没听/kk

果然还是太菜。

例题1.种植方案

设 \(f_{i,j}\) 表示第 \(i\) 行状态为 \(j\) 时的方案数。转移时判断一下满不满足情况。

\(f_{i,j}=\Sigma f_{i-1,k}\) 。

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.最短路径

设 \(f_i,j\) 表示当前在第 \(i\) 个点且走过的状态为 \(j\) 时的最短路径。

枚举上一步的位置 \(k\) 进行转移。

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 出每种状态,开一个数组存起来。

预处理每两种状态能否出现在相邻的两行。

第 \(K\) 行的上面和下面分别 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.炮兵阵地

感觉这题是种植方案+互不侵犯套在一起(?

设 \(f_{i,j,k}\) 表示第 \(i\) 行状态为 \(j\),上一行状态为 \(k\) 时最多能放置的炮兵数。平原和山地的问题参照例题 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.最优组队

设 \(f_i\) 表示当前分组的状态为 \(i\) 时最大和谐度。每一位 \(0/1\) 表示这个位置上的人是否已经被分组。

枚举 \(i\) 的子集进行转移。

枚举子集方法:

for(int j=i;j;j=(j-1)&i){
int k=j^i;
}

则 \(j\) 是 \(i\) 的子集,\(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.图的计数

真·摆了一天。

设 \(f_{i,j}\) 表示第 \(i\) 行的状态(每个点的奇偶性)为 \(j\) 时的最小反转数。

反转边时使用异或转移。

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;
}

YbtOJ 「动态规划」第5章 状压DP的更多相关文章

  1. loj2540 「PKUWC2018」随机算法 【状压dp】

    题目链接 loj2540 题解 有一个朴素三进制状压\(dp\),考虑当前点三种状态:没考虑过,被选入集合,被排除 就有了\(O(n3^{n})\)的转移 但这样不优,我们考虑优化状态 设\(f[i] ...

  2. BZOJ1688 「USACO05OPEN」Disease Manangement 背包+状压DP

    问题描述 BZOJ1688 题解 背包,在转移过程中使用状压. \(\mathrm{Code}\) #include<bits/stdc++.h> using namespace std; ...

  3. LOJ#6433. 「PKUSC2018」最大前缀和 状压dp

    原文链接https://www.cnblogs.com/zhouzhendong/p/LOJ6433.html 题解 枚举一个集合 S ,表示最大前缀和中包含的元素集为 S ,然后求出有多少个排列是这 ...

  4. LOJ 6433 「PKUSC2018」最大前缀和——状压DP

    题目:https://loj.ac/problem/6433 想到一个方案中没有被选的后缀满足 “该后缀的任一前缀和 <=0 ”. 于是令 dp[ S ] 表示选了点集 S ,满足任一前缀和 & ...

  5. 「SCOI2005」互不侵犯 (状压DP)

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

  6. loj #6177. 「美团 CodeM 初赛 Round B」送外卖2 状压dp floyd

    LINK:#6177.美团 送外卖2 一道比较传统的状压dp题目. 完成任务 需要知道自己在哪 已经完成的任务集合 自己已经接到的任务集合. 考虑这个dp记录什么 由于存在时间的限制 考虑记录最短时间 ...

  7. 动态规划专题(一)——状压DP

    前言 最近,决定好好恶补一下我最不擅长的\(DP\). 动态规划的种类还是很多的,我就从 状压\(DP\) 开始讲起吧. 简介 状压\(DP\)应该是一个比较玄学的东西. 由于它的时间复杂度是指数级的 ...

  8. 【状压DP】bzoj1087 互不侵犯king

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

  9. 「状压DP」「暴力搜索」排列perm

    「状压DP」「暴力搜索」排列 题目描述: 题目描述 给一个数字串 s 和正整数 d, 统计 sss 有多少种不同的排列能被 d 整除(可以有前导 0).例如 123434 有 90 种排列能被 2 整 ...

  10. 「PKUSC2018」最大前缀和(状压dp)

    前言 考试被\(hyj\)吊着打... Solution 考虑一下如果前缀和如果在某一个位置的后面的任意一个前缀和都<=0,肯定这就是最大的. 然后这样子就考虑左右两边的状压dp,然后就好了. ...

随机推荐

  1. 跟着 GPT-4 从0到1学习 Golang 并发机制(三)

    目录 一.前言 二.开聊 2.1 关于 goroutine 泄露问题 2.2 内存模型 2.3 Race Detector 检测数据竞争 三.总结 一.前言 话接上回<跟着 GPT-4 从0到1 ...

  2. ZEGO全新语音聊天解决方案,4步搭建爆火的语音聊天室

    最近,国外一款语音聊天软件成功火出圈. 与此同时,该类产品也引发了国内互联网的关注,除了争相下载试用之外,不少社交.泛娱乐行业从业者也表示要跟进对应玩法. 据了解,不少泛娱乐玩家已经在加班加点抢占先机 ...

  3. Hexo博客Next主题站内搜索模块相关,解决搜索无效、一直loading的问题

    站内搜索配置 设置方法: 首先安装hexo-generator-searchdb插件 npm install hexo-generator-searchdb --save 编辑博客根目录下的博客本地目 ...

  4. Hugging News #0717: 开源大模型榜单更新、音频 Transformers 课程完成发布!

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  5. 面霸的自我修养:Java线程专题

    王有志,一个分享硬核Java技术的互金摸鱼侠加入Java人的提桶跑路群:共同富裕的Java人 平时我在网上冲浪的时候,收集了不少八股文和面试文,内容虽然多,但质量上良莠不齐,主打一个不假思索的互相抄, ...

  6. zabbix 使用监控项原型(自动发现规则)

    以kafka为例,需要先对 topic-parttion 做发现,脚本如下 cat topic_parttion_discovery.py #!/usr/bin/env python import j ...

  7. cesium加载gif图片(cesium篇.43)

    https://blog.csdn.net/QQ98281642/article/details/120214325

  8. noip2022保龄记

    第一次参加noip,写第一篇游记纪念一下 天还挺热,今天就穿了个秋衣加卫衣,本来还打算穿袄子来着,但是爸妈没让 到了八中才发现好像没带水,然后我的老父亲跑到不知道哪里去给买了一瓶(八中门口看不到有小卖 ...

  9. javascript事件循环机制及面试题详解

    javascript是单线程执行的程序,也就是它只有一条主线,所有的程序都是逐行"排队"执行,在这种情况下可能存在一些问题,比如说setTimeout.ajax等待执行的时间较长, ...

  10. 【算法】用c#实现德州扑克卡牌游戏规则

    德州扑克是一种牌类游戏,可多人参与,它的玩法是,玩家每人发两张底牌,桌面依次发5张公共牌,玩家用自己的两张底牌和5张公共牌自由组合,按大小决定胜负. 使用c#完成功能Hand()以返回手牌类型和按重要 ...