【做題記錄】csp2025-狀壓dp
A. [USACO13NOV] No Change G
設 \(dp[S]\) 表示取的硬幣狀態為 \(S\) 時最多買多少東西。給 \(n\) 個物品做前綴和,轉移二分即可。時間復雜度 \(O(2^kk\log n)\)。
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 int ll
#define uprb upper_bound
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5,maxm=(1<<16)+5;
int n,m,a[20],b[maxn],dp[maxm];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
read(m)read(n);
for(int i=1;i<=m;i++){
read(a[i]);
}
for(int i=1;i<=n;i++){
read(b[i]);
b[i]+=b[i-1];
}
int ans=-1;
for(int S=0;S<1<<m;S++){
int tmp=0;
for(int i=1;i<=m;i++){
if(S>>(i-1)&1){
continue;
}
tmp+=a[i];
int nS=S|1<<(i-1);
int p=uprb(b+1,b+n+1,b[dp[S]]+a[i])-b-1;
dp[nS]=max(dp[nS],p);
}
if(dp[S]==n){
ans=max(ans,tmp);
}
}
printf("%lld",ans);
return 0;
}
}
signed main(){return asbt::main();}
B. [HAOI2007] 修筑綠化帶
好好好,根本不是狀壓DP……
思路像理想的正方形。考慮對于一個固定的 \(A\times B\) 的矩形,答案一定是矩形的和減去它里面最小的 \(C\times D\) 的矩形的和。前者二維前綴和即可,后者需要類似于那道題搞兩次單調隊列。
具體地,設 \(B[i][j]\) 表示以 \((i,j)\) 為右下角的 \(C\times D\) 的矩形的和,設 \(P[i][j]\) 表示以 \((i,j)\) 為右下角的 \(A\times B\) 的矩形中,右下角和 \((i,j)\) 在同一行的 \(C\times D\) 的所有矩形中最小的一個。這需要跑一次單調隊列。然后再對 \(P\) 類似地縱向求一次單調隊列得到 \(Q\),\(Q\) 即為所求。
需要注意這樣一句話:
同時在花壇四周修建一片綠化帶,讓花壇被綠化帶圍起來。
也就是說,花壇是不能貼著綠化帶的邊的。
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=1e3+5,inf=0x3f3f3f3f;
typedef int juz[maxn][maxn];
int n,m,a,b,c,d,q[maxn];
juz f,A,B,P,Q;
il int sum(int x1,int y1,int x2,int y2){
return f[x2][y2]-f[x1-1][y2]-f[x2][y1-1]+f[x1-1][y1-1];
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n)read(m)read(a)read(b)read(c)read(d);
memset(A,-0x3f,sizeof A);
memset(B,0x3f,sizeof B);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
read(f[i][j]);
f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
if(i>=a&&j>=b){
A[i][j]=sum(i-a+1,j-b+1,i,j);
}
if(i>=c&&j>=d){
B[i][j]=sum(i-c+1,j-d+1,i,j);
}
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<=m;j++){
// cout<<A[i][j]<<" ";
// }
// puts("");
// }
// for(int i=1;i<=n;i++){
// for(int j=1;j<=m;j++){
// cout<<B[i][j]<<" ";
// }
// puts("");
// }
memset(P,0x3f,sizeof P);
for(int i=1,hd,tl;i<=n;i++){
hd=1,tl=0;
q[1]=0;
for(int j=1;j<=m;j++){
while(hd<=tl&&q[hd]<j-b+d+1){
hd++;
}
P[i][j]=B[i][q[hd]];
while(hd<=tl&&B[i][q[tl]]>B[i][j]){
tl--;
}
q[++tl]=j;
}
}
memset(Q,0x3f,sizeof Q);
for(int j=1,hd,tl;j<=m;j++){
hd=1,tl=0;
q[1]=0;
for(int i=1;i<=n;i++){
while(hd<=tl&&q[hd]<i-a+c+1){
hd++;
}
Q[i][j]=P[q[hd]][j];
while(hd<=tl&&P[q[tl]][j]>P[i][j]){
tl--;
}
q[++tl]=i;
}
}
int ans=-inf;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
ans=max(ans,A[i][j]-Q[i][j]);
}
}
printf("%d",ans);
return 0;
}
}
int main(){return asbt::main();}
C. [COCI2016-2017#1] Vje?tica
6,其實很簡單。
考慮將兩個字符串集合合并時,新的集合的答案,其實就是兩個集合的答案之和再減去公共的字符數量。
原因是新的公共字符構成的集合絕對是舊的公共字符集合的子集,換句話說一定能覆蓋。于是轉移是簡單的。
轉移一定要枚舉每個子集,原因是簡單地加入一個字符串是沒有覆蓋所有情況的。
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=1e6+5,maxm=(1<<16)+5,inf=0x3f3f3f3f;
int n,cnt[20][30],dp[maxm],hp[30];
char s[maxn];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n);
memset(dp,0x3f,sizeof dp);
for(int i=1;i<=n;i++){
scanf(" %s",s+1);
int S=1<<(i-1);
dp[S]=1;
for(int j=1;s[j]!='\0';j++){
cnt[i][s[j]-'a']++;
dp[S]++;
}
}
for(int S=1;S<1<<n;S++){
for(int i=0;i<=25;i++){
hp[i]=inf;
}
for(int i=1;i<=n;i++){
if(S>>(i-1)&1){
for(int j=0;j<=25;j++){
hp[j]=min(hp[j],cnt[i][j]);
}
}
}
int tmp=1;
for(int i=0;i<=25;i++){
tmp+=hp[i];
}
for(int nS=S&(S-1);nS;nS=(nS-1)&S){
dp[S]=min(dp[S],dp[nS]+dp[S^nS]-tmp);
}
}
printf("%d",dp[(1<<n)-1]);
return 0;
}
}
int main(){return asbt::main();}
D. New Year Tree
一眼線段樹。
具體地,先跑一遍dfn,然后維護區間顏色集合。
顯然它又不是狀壓DP
記得1ll
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 pb push_back
#define popcntll __builtin_popcountll
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=4e5+5;
int n,m,a[maxn],cnt;
int dfn[maxn],sz[maxn],zhan[maxn];
vector<int> e[maxn];
il void dfs(int u,int fa){
dfn[u]=++cnt;
zhan[cnt]=u;
sz[u]=1;
for(int v:e[u]){
if(v==fa){
continue;
}
dfs(v,u);
sz[u]+=sz[v];
}
}
struct stree{
int tag[maxn<<2];
ll zhi[maxn<<2];
il void pushup(int id){
zhi[id]=zhi[lid]|zhi[rid];
}
il void pushdown(int id){
if(tag[id]){
tag[lid]=tag[rid]=tag[id];
zhi[lid]=zhi[rid]=1ll<<(tag[id]-1);
tag[id]=0;
}
}
il void build(int id,int l,int r){
if(l==r){
zhi[id]=1ll<<(a[zhan[l]]-1);
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
pushup(id);
}
il void upd(int id,int L,int R,int l,int r,int val){
if(L>=l&&R<=r){
tag[id]=val;
zhi[id]=1ll<<(val-1);
return ;
}
pushdown(id);
int mid=(L+R)>>1;
if(l<=mid){
upd(lid,L,mid,l,r,val);
}
if(r>mid){
upd(rid,mid+1,R,l,r,val);
}
pushup(id);
}
il ll query(int id,int L,int R,int l,int r){
if(L>=l&&R<=r){
return zhi[id];
}
pushdown(id);
int mid=(L+R)>>1;
ll res=0;
if(l<=mid){
res|=query(lid,L,mid,l,r);
}
if(r>mid){
res|=query(rid,mid+1,R,l,r);
}
return res;
}
}SG;
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++){
read(a[i]);
}
for(int i=1,u,v;i<n;i++){
read(u)read(v);
e[u].pb(v),e[v].pb(u);
}
dfs(1,0);
SG.build(1,1,n);
while(m--){
int opt,u,x;
read(opt)read(u);
switch(opt){
case 1:{
read(x);
SG.upd(1,1,n,dfn[u],dfn[u]+sz[u]-1,x);
break;
}
default:{
printf("%d\n",popcntll(SG.query(1,1,n,dfn[u],dfn[u]+sz[u]-1)));
break;
}
}
}
return 0;
}
}
int main(){return asbt::main();}
E. Yet Another Substring Reverse
因為字符不重復,因此不用考慮它們的排列順序,即翻轉子串就是將兩個不交的子串連到一塊。這里不交既指位置不交又指字符集不交。但顯然字符集不交則一定位置不交。因此只用考慮對每一個字符集處理最大長度。高維前綴最大值即可。
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=1e6+5,maxm=(1<<20)+5;
int n,dp[maxm];
char s[maxn];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
scanf(" %s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++){
for(int j=i,S=0;j;j--){
if(S>>(s[j]-'a')&1){
break;
}
S|=1<<(s[j]-'a');
dp[S]=i-j+1;
}
}
for(int i=1;i<=20;i++){
for(int S=0;S<1<<20;S++){
if(S>>(i-1)&1){
continue;
}
dp[S|1<<(i-1)]=max(dp[S|1<<(i-1)],dp[S]);
}
}
int ans=0;
for(int S=1;S<=(1<<20)-2;S++){
ans=max(ans,dp[S]+dp[((1<<20)-1)^S]);
}
printf("%d",ans);
return 0;
}
}
int main(){return asbt::main();}
F. [TJOI2015]棋盤
重中之重:
編號從 \(0\) 開始,即第 \(1\) 行指的是中間那行。
設 \(dp[i][S]\) 表示第 \(i\) 行放置棋子狀態為 \(S\) 的方案數。發現時間和空間都無法接受,容易想到矩陣加速。代碼十分好敲。對 \(2^{32}\) 取模這件事情,直接開 unsigned int 自然溢出就行了。時間復雜度 \(O(2^{3m}\log n)\)。
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 ui unsigned int
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<6)+5;
int n,m,p,q,a[5][10];
int sta[maxn],stn;
struct juz{
ui mat[maxn][maxn];
juz(){
for(int i=0;i<1<<m;i++){
for(int j=0;j<1<<m;j++){
mat[i][j]=0;
}
}
}
il ui*operator[](const int &x){
return mat[x];
}
il juz operator*(juz x)const{
juz res;
for(int i=0;i<1<<m;i++){
for(int j=0;j<1<<m;j++){
for(int k=0;k<1<<m;k++){
res[i][j]+=mat[i][k]*x[k][j];
}
}
}
return res;
}
}dp,bas,fnl;
il juz qpow(juz x,int y){
juz res;
for(int i=0;i<1<<m;i++){
res[i][i]=1;
}
while(y){
if(y&1){
res=res*x;
}
x=x*x,y>>=1;
}
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
read(n)read(m)read(p)read(q);
q++;
for(int i=1;i<=3;i++){
for(int j=1;j<=p;j++){
read(a[i][j]);
}
}
for(int S=0;S<1<<m;S++){
for(int i=1;i<=m;i++){
if(S>>(i-1)&1){
for(int j=1,k;j<=p;j++){
if(j==q||!a[2][j]){
continue;
}
k=i-q+j;
if(k>=1&&k<=m&&(S>>(k-1)&1)){
goto togo1;
}
}
}
}
sta[++stn]=S;
dp[0][S]=1;
togo1:;
}
// cout<<stn<<"\n";
// for(int i=1;i<=stn;i++){
// cout<<bitset<6>(sta[i])<<"\n";
// }
for(int x=1;x<=stn;x++){
for(int y=1;y<=stn;y++){
for(int i=1;i<=m;i++){
if(sta[x]>>(i-1)&1){
for(int j=1,k;j<=p;j++){
if(!a[3][j]){
continue;
}
k=i-q+j;
if(k>=1&&k<=m&&(sta[y]>>(k-1)&1)){
goto togo2;
}
}
}
if(sta[y]>>(i-1)&1){
for(int j=1,k;j<=p;j++){
if(!a[1][j]){
continue;
}
k=i-q+j;
if(k>=1&&k<=m&&(sta[x]>>(k-1)&1)){
goto togo2;
}
}
}
}
bas[sta[x]][sta[y]]=1;
togo2:;
}
}
for(int S=0;S<1<<m;S++){
fnl[S][0]=1;
}
printf("%u",(dp*qpow(bas,n-1)*fnl)[0][0]);
return 0;
}
}
int main(){return asbt::main();}

浙公網安備 33010602011771號