状态压缩dp
相关技巧
- 枚举子集:如果一个集合状态
由其所有子集 转移得到,这样转移的时间复杂度为
for(int S0 = S; S0; S0 = (S0 - 1) & S) {
{
...
}
- 高维前缀和
考虑二维前缀和还可以怎么计算:对于每一维,枚举剩下所有维的所有可能,计算关于该维的前缀和。
高维前缀和就是这样的原理。通常情况下每个维度大小为
for(int d=0;d<n;d++)
for(int i=0;i<1<<n;i++)
if((i>>d)&1)f[i]+=f[i^(1<<d)];
时间复杂度
HDU 4628 Pieces (子序列状压的一般套路)
题目
给定一个长度为
题解
设
HDU 6149 Valley Number II (图上状压一般思路)
题目
给定一个
一个山谷定义为一个三元组
如果一个点只能在一个山谷中,求这张图最多有多少个山谷
题解
首先,注意到
设
假设我们考虑到第
P3959 [NOIP2017 提高组] 宝藏 (分层法确定DP顺序)
题目
给定一个
题解
考虑根据离根的深度(从当前到根经过的点数),一层一层进行DP
设
转移为
其中
时间复杂度为
P6622 [省选联考 2020 A/B 卷] 信号传递 (状态的设计及优化)
题解
按题意,我们暴力枚举这
容易发现,题目给出的这个长度为
对于任意
这样我们就干掉了
接下来我们有两种状压DP的状态设计
- 按位置考虑:设
表示考虑完前 个数,填了集合 中位置 - 按值考虑:设
表示考虑完前 个位置,填了集合 中的数
这是状压DP中两个极为常见的状态设计,而本题适用于第二种状态设计
为了方便转移,我们将点对的贡献拆解为单点的贡献,具体来说,假设我们考虑到第
- 对于一个前面的数
,从 到 产生的代价是: 。 - 对于一个前面的数
,从 到 产生的代价是: 。 - 对于一个后面的数
,从 到 产生的代价是: 。 - 对于一个后面的数
,从 到 产生的代价是: 。
转移方程就是
如果枚举
我们继续优化。考虑只枚举
考虑预处理
我们考虑,
这样转移是
发现
对每个
可以采取滚动数组的方式来实现,这样空间复杂度也符合要求了,可以通过本题
P1777 帮助 (普通状压dp)
题目
定义一个长度为
题解
首先离散化一下,得到一个新的数组
设
- 如果第
个位置没有被删
- 如果第
个位置被删
时间复杂度
[BZOJ 1231] mixup2 (普通状压dp)
题目
如果一个队伍里所有两头相邻的奶牛的编号相差超过
题解
设
转移时,如果当前枚举的
P5933 [清华集训2012]串珠子 (连通图中的状压dp)
题目
给定
题解
直接维护连通是是困难的,那么正难则反
设
显然
关于
有
于是我们枚举子集转移即可
P2704 [NOI2001] 炮兵阵地 (多行影响的状压dp)
题目
在
求在炮兵部队之间不能互相攻击的前提下,最多能部署多少炮兵
部队。
题解
考虑到
- 列数很小,可以状压。
- 每个炮兵可以向上影响两行,状压一行是不够的。
- 每个炮兵会向左右影响两个,每列放炮兵的方案不多。
于是我们可以状压两行,设
直接转移就行
CF453B Little Pony and Harmony Chest (根据题目性质减少压缩状态的大小)
题目
给定长为
输出
题解
若存在某
所以
用状态
枚举上一个的状态
互质 (状压dp与背包的结合)
题目
有
题解
一个
记一个状态
-
对于任意一个数,只有
以内的质因数:这样的话,直接暴力转移即可 -
有
以上的质因数:将拥有相同的、大于 的质因数的数存成一组,分组背包转移
复杂度
BZOJ 5180 Cities (最小斯坦纳树)
题目
给定
现在要你选定一些边来构成一个图,要使得
题解
首先,答案子图一定是一棵树,因为如果有环,那么一定可以断掉一条边使答案更优且保持图的联通
那么我们设
- 当点
度数为 ,设与它联通的点是 ,那么有转移
- 当点
的度数大于 时,我们考虑枚举 ,那么有转移
上面那个转移很像最短路算法的三角不等式,使用
下面那个转移可以采取枚举子集的方法进行转移
与
每次松弛操作从
所以这个转移是正确,使用
[ATC 2230] Water Distribution (状压与最小生成树)
题目
在一个二维平面上有N个城市, 第
现在你可以从一个城市向另一个城市运送任意数量的水, 但水在运输过程中会有损耗, 具体而言如果从
你要使最终水量最少的城市水量尽量多, 求这个值,精度误差不超过
题解
枚举所有
然后对整个点集枚举子集的子集进行合并转移,时间复杂度是
总时间复杂度是
[AGC 012E] Camel and Oases (根据题目性质巧妙选取压缩状态)
题目
数轴上有
对于
题解
首先
考虑一下用什么样的方式统计答案,我们可以考虑枚举第一层的每条线段
此时那个分层线段有一个性质:对于不同层之间的线段,它们相互之间要么上面的完全包含下面的,要么完全不相交
所以这时我们可以考虑维护
通过状压每一层是否被选过,我们向中间选取连续的线段。假设我们现在要放第
那么转移如下
处理完这个,如上文所言,我们就直接暴力枚举第一层的线段,然后
[SRM 713] DFSCount (树上状压)
题目
给定一个
题解
设
考虑
那么对于状态
CF1342F Make It Ascending (交换状态与值域)
题目
给定一个长度为
求将原序列变成严格单调上升的最少操作次数。
题解
先将每个集合及其元素和预处理出来
设
转移时枚举集合
这道题涉及到方案的输出,因此给一个代码
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
const int N=16,inf=0x3f3f3f3f;
int T,n,a[N],f[N][N][1<<N],id[N];
struct node{
int i,p,s;
}las[N][N][1<<N];
int sum[1<<N];
inline void update(node u,node v,int k)
{
f[v.i][v.p][v.s]=min(f[v.i][v.p][v.s],k);
if(f[v.i][v.p][v.s]==k) las[v.i][v.p][v.s]=u;
}
inline void solve()
{
n=read();
for(int i=0;i<n;++i) a[i]=read();
for(int s=0;s<(1<<n);++s)//预处理处每个集合中数的和
{
sum[s]=0;
for(int i=0;i<n;++i)
if(s&(1<<i)) sum[s]+=a[i];
}
for(int i=0;i<=n;++i)
for(int p=0;p<=n;++p)
for(int s=0;s<(1<<n);++s)
f[i][p][s]=inf;
f[0][0][0]=0;
for(int i=0;i<n;++i)
for(int p=0;p<n;++p)
for(int s=0;s<(1<<n);++s)
{
if(f[i][p][s]==inf) continue;
node u=(node){i,p,s};
int ns=((1<<n)-1)^s;//求s的补集
for(int s0=ns;s0;s0=(s0-1)&ns)//枚举s的补集
{
if(sum[s0]>f[i][p][s]&&(s0>>p)!=0)//s0的数的和大于上一个集合,并且有在p之后的数
{
node v=(node){i+1,p+1+__builtin_ctz(s0>>p),s|s0};
update(u,v,sum[s0]);//记录dp路径,方便输出答案
}
}
}
node ans=(node){-1,-1,-1};
for(int i=n;i>=1;--i)
{
for(int p=1;p<=n;++p)
if(f[i][p][(1<<n)-1]!=inf)
{
ans=(node){i,p,(1<<n)-1};
break;
}
if(ans.i!=-1) break;
}
printf("%d\n",n-ans.i);
for(int i=0;i<n;++i) id[i]=i+1;
while(ans.i!=0)//输出方案
{
node tmp=las[ans.i][ans.p][ans.s];
int s0=tmp.s^ans.s;
for(int i=0;i<n;++i)
{
if((s0&(1<<i))&&i!=ans.p-1)
{
printf("%d %d\n",id[i],id[ans.p-1]);
for(int j=i+1;j<n;++j) id[j]--;
}
}
ans=tmp;
}
}
int main()
{
T=read();
while(T--) solve();
return 0;
}
P2150 [NOI2015] 寿司晚宴 (根据质因子缩减范围)
题解
我们进行根号分治
以我们可以先用状压求出小于
具体来说,设
后面需要减去一个
时间复杂度
NOIP 四校联测 Day3 数字重组 (对极差的处理方法+增加维数)
题目
给一个长度为
题解
数据范围很小,又是个跟集合划分有关的问题,明显是个状压dp
先来考虑怎么处理极差。
众所周知极差
于是我们设
注意到题目中要求我们保证各个集合中没有重复元素,所以我们要增一维
然后特判+转移即可,详见代码
本题的复杂度证明是难点,注意到每个状态都能看作从
的折线,由此可知状态数为
所以总的时间复杂度是
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
const int N=110,M=60010,inf=1e18;
vector<int> S[M];
map<vector<int>,int> mp;
int n,k,tmp[N],cnt,sum[M],a[N];
int f[2][M][N];
inline void init(int x)
{
if(x>k)
{
++cnt;
for(int i=1;i<=k;++i)
S[cnt].push_back(tmp[i]),sum[cnt]+=tmp[i];
mp[S[cnt]]=cnt;
return;
}
for(int i=tmp[x-1];i<=n/k;++i)
{
tmp[x]=i;
init(x+1);
}
}
signed main()
{
freopen("num.in","r",stdin);
freopen("num.out","w",stdout);
n=read();k=read();
for(int i=1;i<=n;++i) a[i]=read();
sort(a+1,a+1+n);
init(1);//预处理选择的状态
memset(f,inf,sizeof(f));
f[0][1][0]=0;
for(int i=1;i<=n;++i)
{
for(int s=1;s<=cnt;++s)
{
if(sum[s]!=i) continue;
for(int j=0;j<=n/k;++j)
f[i&1][s][j]=inf;
}
for(int s=1;s<=cnt;++s)//s-->x
{
if(sum[s]!=i-1) continue;
for(int j=0;j<=n/k;++j)
{
for(int p=0;p<k;++p)
{
if(p<k-1&&S[s][p]==S[s][p+1]) continue;//先填后面
if(S[s][p]==n/k) continue;//当前位置已满
vector<int> nw=S[s];
++nw[p];//当前位置新增一员
if(a[i]==a[i-1])
{
if(S[s][p]>j) continue;
int x=mp[nw];//对应的状态编号
int val=0;
if(S[s][p]==n/k-1) val+=a[i];//最大的
if(S[s][p]==0) val-=a[i];//最小的
f[i&1][x][S[s][p]]=min(f[i&1][x][S[s][p]],f[(i&1)^1][s][j]+val);
}
else
{
int x=mp[nw];
int val=0;
if(S[s][p]==n/k-1) val+=a[i];
if(S[s][p]==0) val-=a[i];
f[i&1][x][S[s][p]]=min(f[i&1][x][S[s][p]],f[(i&1)^1][s][j]+val);
}
}
}
}
}
printf("%lld\n",f[n&1][cnt][n/k-1]);
return 0;
}
SDOI 一轮省集 哈密顿路 (交换答案与状态+无向图哈密顿路性质+lowbit优化转移)
题目
给定由
题解
设
我们来思考一下这个
注意到无向图哈密顿路的一个性质——对于图中一点
值得注意的是,我们在枚举经过
具体代码如下
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
const int N=24;
int n,m,ans[N],g[N][N];
char s[N];
int f[1<<N],nxt[1<<N];
inline int getS(int x)
{
return (1<<x);
}
inline int lowbit(int x)
{
return x&(-x);
}
int main()
{
//freopen("hamilton.in","r",stdin);
//freopen("hamilton.out","w",stdout);
n=read();m=getS(n-1);
for(int i=0;i<n;++i)
{
scanf("%s",s);
for(int j=0;j<n;++j)
if(s[j]=='1') nxt[getS(i)]^=getS(j);
}
for(int i=0;i<n;++i)
for(int mk=0;mk<(1<<n);++mk)
{
if((mk>>i)&1) nxt[mk]|=nxt[mk^getS(i)];
}
for(int i=nxt[getS(n-1)];i;i-=lowbit(i)) f[lowbit(i)]=lowbit(i);
for(int i=1;i<m;++i)
for(int j=0;j<n-1;++j)
{
if((i>>j)&1) continue;
if((nxt[f[i]]>>j)&1)
f[i|getS(j)]|=getS(j);
}
ans[n-1]=f[m-1];
for(int i=1;i<m;++i)
for(int j=f[i];j;j-=lowbit(j))
{
int cnt=__builtin_ctz(j);
ans[cnt]|=f[(m-1)^i];
}
for(int i=0;i<n-1;++i)
for(int j=i+1;j<n;++j)
if((ans[j]>>i)&1) g[i][j]=g[j][i]=1;
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
printf("%d",g[i][j]);
puts("");
}
return 0;
}
Atcoder Beginner Contest 319 F (贪心+图上状压)
题意
你在一棵有
对于第
- 如果这个点是怪物,那么如果你当前的战斗力不小于
,你就可以增加 的战斗力(注意这里比那个经典贪心简单,不用扣血) - 如果这个点是药剂,那么你当前的战斗力就会乘上
请判断你是否可以杀掉所有怪物
题解
假设没有药剂,那么考虑贪心,我们就开一个优先队列,每次走
而如果有药剂,我们发现,如果能加那就一定要先加,先加再乘一定比先乘后加更优
那么我们就每次按照第一个贪心先加,走不动了就找那个
吗?
考虑我们目前有两个可以使用的药剂,他们的回复量分别是
但是天无绝人之路,注意到原题中
所以考虑状压,设
时间复杂度
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=510;
ll f[1<<10];
int n,m;
struct Edge{
int v,nxt;
}edge[N];
int cnt,head[N];
inline void add_edge(int u,int v)
{
edge[++cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
int s[N],g[N],iq[N];
const ll inf=1e9+10;
struct node{
int u,w;
bool operator < (const node &x) const{return x.w<w;}
};
int main(){
memset(f,-1,sizeof(f));
scanf("%d",&n);
for(int i=2,fa,ty;i<=n;++i)
{
scanf("%d%d%d%d",&fa,&ty,&s[i],&g[i]);
add_edge(fa,i);
if(ty==2) iq[i]=++m;
}
priority_queue<node>q;
q.push((node){1,s[1]});
ll val=1;
while(!q.empty()&&val>=q.top().w){
int u=q.top().u;q.pop();
val+=g[u];
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(!iq[v])
q.push((node){v,s[v]});
}
}
f[0]=val;
for(int S=0;S<(1<<m);++S)
if(f[S]>=0)
{
f[S]=min(f[S],inf);
while(!q.empty())q.pop();
q.push((node){1,s[1]});
vector<int> tmp;
while(!q.empty()&&f[S]>=q.top().w)
{
int u=q.top().u;q.pop();
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(!iq[v]||((S>>(iq[v]-1))&1))
q.push((node){v,s[v]});
else tmp.emplace_back(v);
}
}
if(S==(1<<m)-1)
{
if(q.empty()) puts("Yes");
else puts("No");
return 0;
}
for(int x:tmp)
{
ll np=min(inf,f[S]*g[x]);
int T=S|(1<<(iq[x]-1));
priority_queue<node> q2=q;
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].v;
q2.push((node){v,s[v]});
}
while(!q2.empty()&&np>=q2.top().w)
{
int u=q2.top().u;q2.pop();
np+=g[u];
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(!iq[v]) q2.push((node){v,s[v]});
}
}
f[T]=max(f[T],np);
}
}
return puts("No"),0;
}
CF1886 E. I Wanna be the Team Leader (贪心+可行性转最优化)
题意
有
如果任务
题解
首先
考虑一个任务是否能够完成只与能力值最小的员工有关,因此我们可以先将员工按照能力值从小到大排序,那么每个任务都对应排序后
考虑转移,我们从小到大枚举每个员工是否是最小的,接着枚举我们现在要加入一个任务
后面的最值用 ST表 处理即可,时间复杂度
posted on 2023-10-28 15:38 star_road_xyz 阅读(26) 评论(0) 编辑 收藏 举报
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!