【比赛记录】状态压缩专题测试
A. [CCO2015] 路短最
设 \(dp[i][S]\) 表示走到 \(i\) 点,经过的点集为 \(S\) 的最长路,用类似于 spfa
的方式转移即可。
复杂度是一个 bfs
,具体不太会证。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
char ch;\
int fu=1;\
while(!isdigit(ch=getchar()))\
fu-=(ch=='-')<<1;\
x=ch&15;\
while(isdigit(ch=getchar()))\
x=(x<<1)+(x<<3)+(ch&15);\
x*=fu;\
}
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<18)+5;
const int inf=0x3f3f3f3f;
int n,m,dp[25][maxn];
vector<pii> e[25];
queue<pii> q;
bitset<maxn> vis[25];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n)read(m);
for(int i=1,u,v,w;i<=m;i++){
read(u)read(v)read(w);
e[u].pb(mp(v,w));
}
memset(dp,-0x3f,sizeof dp);
dp[0][1]=0;
q.push(mp(0,1));
vis[0][1]=1;
while(q.size()){
int u=q.front().fir,S=q.front().sec;
q.pop(),vis[u][S]=0;
for(pii i:e[u]){
int v=i.fir,w=i.sec;
if(S>>v&1){
continue;
}
int nS=S|1<<v;
if(dp[v][nS]<dp[u][S]+w){
dp[v][nS]=dp[u][S]+w;
if(!vis[v][nS]){
q.push(mp(v,nS));
vis[v][nS]=1;
}
}
}
}
int ans=-inf;
for(int S=0;S<1<<n;S++){
ans=max(ans,dp[n-1][S]);
}
printf("%d",ans);
return 0;
}
}
int main(){return asbt::main();}
B. [GDOI2014] 拯救莫莉斯
因为 \(n\times m\le50\),并且 \(m\le n\),所以 \(m\) 最大为 \(7\)。于是可以状压。
设 \(f[i][S1][S2]\) 表示第 \(i-1\) 行建造油库的状态为 \(S1\),第 \(i\) 行的状态为 \(S2\),且第 \(1\) 到 \(i-1\) 行都已合法的最小花费,\(g[i][S1][S2]\) 为相应的最小油库数。转移时枚举上一行、这一行、下一行的状态 \(S1\),\(S2\),\(S3\) 即可。
判断转移条件:
((S1|S2|S3|S2<<1|S2>>1)&uS)==uS
其中 \(uS\) 为全集。
时间复杂度 \(O(n8^m)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
char ch;\
int fu=1;\
while(!isdigit(ch=getchar()))\
fu-=(ch=='-')<<1;\
x=ch&15;\
while(isdigit(ch=getchar()))\
x=(x<<1)+(x<<3)+(ch&15);\
x*=fu;\
}
#define popcnt __builtin_popcount
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<7)+5;
const int inf=0x3f3f3f3f;
int n,m,a[55][15];
int sum[55][maxn];
int f[55][maxn][maxn];
int g[55][maxn][maxn];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n)read(m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
read(a[i][j]);
}
}
for(int i=1;i<=n;i++){
for(int S=0;S<1<<m;S++){
for(int j=1;j<=m;j++){
if(S>>(j-1)&1){
sum[i][S]+=a[i][j];
}
}
}
}
memset(f,0x3f,sizeof f);
memset(g,0x3f,sizeof g);
int uS=(1<<m)-1;
for(int S=0;S<=uS;S++){
f[1][0][S]=sum[1][S];
g[1][0][S]=popcnt(S);
}
for(int i=1;i<n;i++){
for(int S1=0;S1<=uS;S1++){
for(int S2=0;S2<=uS;S2++){
if(f[i][S1][S2]>=inf){
continue;
}
for(int S3=0;S3<=uS;S3++){
if(((S1|S2|S3|S2<<1|S2>>1)&uS)==uS){
if(f[i+1][S2][S3]>f[i][S1][S2]+sum[i+1][S3]){
f[i+1][S2][S3]=f[i][S1][S2]+sum[i+1][S3];
g[i+1][S2][S3]=g[i][S1][S2]+popcnt(S3);
}
else if(f[i+1][S2][S3]==f[i][S1][S2]+sum[i+1][S3]){
g[i+1][S2][S3]=min(g[i+1][S2][S3],g[i][S1][S2]+popcnt(S3));
}
}
}
}
}
}
int ans1=inf,ans2=inf;
for(int S1=0;S1<=uS;S1++){
for(int S2=0;S2<=uS;S2++){
if(((S1|S2|S2<<1|S2>>1)&uS)==uS){
if(ans1>f[n][S1][S2]){
ans1=f[n][S1][S2];
ans2=g[n][S1][S2];
}
else if(ans1==f[n][S1][S2]){
ans2=min(ans2,g[n][S1][S2]);
}
}
}
}
printf("%d %d",ans2,ans1);
return 0;
}
}
int main(){return asbt::main();}
C. 萃香抱西瓜
设 \(dp[t][x][y][S]\) 表示 \(t\) 时刻,坐标为 \((x,y)\),拿到的小西瓜为 \(S\) 的最小移动次数。刷表法转移就行了。
时间复杂度 \(O(hwTn2^m)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
char ch;\
int fu=1;\
while(!isdigit(ch=getchar()))\
fu-=(ch=='-')<<1;\
x=ch&15;\
while(isdigit(ch=getchar()))\
x=(x<<1)+(x<<3)+(ch&15);\
x*=fu;\
}
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int inf=0x3f3f3f3f;
const int dx[]={0,0,-1,1};
const int dy[]={-1,1,0,0};
int h,w,tot,sx,sy,n,m,hao[25];
int px[25][105],py[25][105];
int f[105][10][10][(1<<10)+5];
il void upd(int &x,int y){
x=min(x,y);
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
// freopen("P3786_2.in","r",stdin);
// freopen("P3786_2.ans","w",stdout);
read(h)read(w)read(tot);
read(sx)read(sy)read(n)read(m);
m=0;
for(int i=1,opt,t1,t2;i<=n;i++){
read(t1)read(t2)read(opt);
if(opt){
hao[i]=++m;
}
while(t1<t2){
read(px[i][t1])read(py[i][t1]);
t1++;
}
}
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++){
if(px[i][1]==sx&&py[i][1]==sy){
if(hao[i]){
f[1][sx][sy][1<<(hao[i]-1)]=0;
}
goto togo1;
}
}
f[1][sx][sy][0]=0;
togo1:;
for(int t=1;t<tot;t++){
for(int x=1;x<=w;x++){
for(int y=1;y<=h;y++){
for(int S=0;S<1<<m;S++){
if(f[t][x][y][S]>=inf){
continue;
}
for(int i=1;i<=n;i++){
if(px[i][t+1]==x&&py[i][t+1]==y){
if(hao[i]&&(S>>(hao[i]-1)&1)==0){
upd(f[t+1][x][y][S|1<<(hao[i]-1)],f[t][x][y][S]);
goto togo2;
}
else if(!hao[i]){
goto togo2;
}
}
}
upd(f[t+1][x][y][S],f[t][x][y][S]);
togo2:;
for(int j=0,nx,ny;j<=3;j++){
nx=x+dx[j],ny=y+dy[j];
if(nx<1||nx>w||ny<1||ny>h){
continue;
}
for(int i=1;i<=n;i++){
if(px[i][t+1]==nx&&py[i][t+1]==ny){
if(hao[i]&&(S>>(hao[i]-1)&1)==0){
upd(f[t+1][nx][ny][S|1<<(hao[i]-1)],f[t][x][y][S]+1);
goto togo3;
}
else if(!hao[i]){
goto togo3;
}
}
}
upd(f[t+1][nx][ny][S],f[t][x][y][S]+1);
togo3:;
}
}
}
}
}
// for(int t=1;t<=tot;t++){
// for(int x=1;x<=w;x++){
// for(int y=1;y<=w;y++){
// for(int S=0;S<1<<m;S++){
// printf("%2d %d %d ",t,x,y);
// cout<<bitset<10>(S)<<" ";
// printf("%10d\n",f[t][x][y][S]);
// }
// }
// }
// }
int ans=inf;
for(int x=1;x<=w;x++){
for(int y=1;y<=h;y++){
ans=min(ans,f[tot][x][y][(1<<m)-1]);
}
}
printf("%d",ans>=inf?-1:ans);
return 0;
}
}
int main(){return asbt::main();}
D. [COCI2020-2021#3] Selotejp
我也是打上轮廓线DP了。
设 \(f[x][y][S]\) 表示当前在 \((x,y)\) 格子,前 \(m\) 个格子的状态为 \(S\) 时的最小花费。
这里的状态是指,这一格竖着覆盖为 \(1\),横着覆盖或本来就不用覆盖为 \(0\)。
这里的前 \(m\) 个格子如下图所示,假设 \(m=4\),当前在 \((2,2)\),方格内的数表示在 \(S\) 中从低到高的下标(从 \(0\) 开始):
它就是一个逐行遍历矩阵的顺序,注意是包括 \((x,y)\) 这一格的。
为什么要这样记录呢,因为存在竖着覆盖,如刚才的例子,\((2,2)\) 的下一个为 \((2,3)\),它的上面是 \((1,3)\),刚好被记录了状态。
于是转移其实不难想:
- 下一位 \((nx,ny)\) 为
#
- 横着覆盖,判断左边有没有点,且这个点是不是横着覆盖的,即 \(f[nx][ny][S>>1]\) 从 \(f[x][y][S]\) 或 \(f[x][y][S]+1\) 转移。
- 竖着覆盖,判断上面的点是不是竖着覆盖的,即 \(f[nx][ny][S>>1\mid 1<<(m-1)]\) 从 \(f[x][y][S]\) 或 \(f[x][y][S]+1\) 转移。
- 下一位为
.
,直接让 \(f[nx][ny][S>>1]\) 从 \(f[x][y][S]\) 转移。
复杂度 \(O(nm2^m)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
char ch;\
int fu=1;\
while(!isdigit(ch=getchar()))\
fu-=(ch=='-')<<1;\
x=ch&15;\
while(isdigit(ch=getchar()))\
x=(x<<1)+(x<<3)+(ch&15);\
x*=fu;\
}
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<10)+5;
const int inf=0x3f3f3f3f;
int n,m,f[maxn][15][maxn];
char s[maxn][15];
il void upd(int &x,int y){
x=min(x,y);
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n)read(m);
for(int i=1;i<=n;i++){
scanf(" %s",s[i]+1);
}
memset(f,0x3f,sizeof f);
if(s[1][1]=='#'){
f[1][1][0]=f[1][1][1<<(m-1)]=1;
}
else{
f[1][1][0]=0;
}
for(int x=1;x<=n;x++){
for(int y=1;y<=m;y++){
for(int S=0,nx,ny;S<1<<m;S++){
if(f[x][y][S]>=inf){
continue;
}
nx=x+y/m;
ny=y%m+1;
if(s[nx][ny]=='#'){
if(ny>1&&(S>>(m-1)&1)==0&&s[x][y]=='#'){
upd(f[nx][ny][S>>1],f[x][y][S]);
}
else{
upd(f[nx][ny][S>>1],f[x][y][S]+1);
}
if(nx>1&&(S&1)){
upd(f[nx][ny][S>>1|1<<(m-1)],f[x][y][S]);
}
else{
upd(f[nx][ny][S>>1|1<<(m-1)],f[x][y][S]+1);
}
}
else{
upd(f[nx][ny][S>>1],f[x][y][S]);
}
}
}
}
int ans=inf;
for(int S=0;S<1<<m;S++){
ans=min(ans,f[n][m][S]);
}
printf("%d",ans);
return 0;
}
}
int main(){return asbt::main();}