前言
又强行凑了一波...
今天我们来讲区间和状压
区间DP的水题
已经不打算写任何概论了XD
(1)压缩(SCOI2007)
题面见链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1068
对于每一个区间(l,r)我们都可以进行如下操作:
如果这个区间可以在i处被分成两段进行压缩,那么我们更新的结果就是f[l][i]+f[i+1][r]+1(1为M)
反之,则只压缩其中一段
如果该区间有两个完全相同的字符串,则直接合并一个串为R
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 111
char a[stan];
int f[stan][stan][stan],len;
bool check(int l,int r){
int sze=r-l+1;
if(sze&1) return false;
for(int i=l;i<=(l+r)/2;++i)
if(a[i]!=a[i+sze/2]) return 0;
return true;
}
int dp(int l,int r,bool tag){
int sze=r-l+1;
if(sze==1) return 1;
if(f[l][r][tag]!=-1) return f[l][r][tag];
if(tag)
for(int i=l;i<r;++i)
sze=min(sze,dp(l,i,1)+dp(i+1,r,1)+1);
for(int i=l;i<r;++i)
sze=min(sze,dp(l,i,tag)+r-i);
if(check(l,r))
sze=min(sze,dp(l,(l+r)/2,0)+1);
return f[l][r][tag]=sze;
}
signed main(){
scanf("%s",a+1);
len=strlen(a+1);
memset(f,255,sizeof(f));
write(dp(1,len,1));
return 0;
}
(2)Dire Wolf(HDU5115)
题面见链接http://acm.hdu.edu.cn/showproblem.php?pid=5115
枚举区间(i,j),表示消灭除i,j外的所有点
枚举(i,j)中的k点,表示消灭k点时k与i,j相邻
然后这道套路就出来了
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 222
int n,a[stan],ts,b[stan],f[stan][stan];
signed main(){
int T=read();
while(scanf("%d",&n)!=EOF){
++ts;
memset(b,0,sizeof(b));
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=1;i<=n;++i)
b[i]=read();
memset(f,127,sizeof(f));
for(int i=0;i<=n+1;++i)
f[i][i]=f[i][i+1]=0;
for(int i=n-1;i>=0;--i)
for(int j=i+2;j<=n+1;++j)
for(int k=i+1;k<j;++k)
f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[k]+b[i]+b[j]);
printf("Case #%d: ",ts);write(f[0][n+1]);puts("");
}
return 0;
}
(3)祖玛(JSOI2007)
都知道数据有误
题面见链接http://www.lydsy.com/JudgeOnline/problem.php?id=1032
这次我们依然枚举区间(i,j),但这次我们先预处理为颜色的联通块(500不压会T得很惨)
同时此次枚举表示处理包括i,j在内该区间的所有联通块
状态还是比较好想的
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 555
int n,co[stan],lef[stan],righ[stan],f[stan][stan],cnt,x;
signed main(){
n=read();
for(int i=1;i<=n;++i){
x=read();
if(i==1||x!=co[cnt]){
co[++cnt]=x;
lef[cnt]=i;
righ[cnt-1]=i-1;
}
}
memset(f,127,sizeof(f));
righ[cnt]=n;
for(int i=1;i<=cnt;++i)
f[i][i]=(lef[i]==righ[i])?2:1;
n=cnt;
for(int i=n-1;i;--i)
for(int j=i+1;j<=n;++j){
for(int k=i;k<j;++k)
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
if(co[i]==co[j])
if(lef[i]==righ[i]&&lef[j]==righ[j]) f[i][j]=min(f[i][j],f[i+1][j-1]+1);
else f[i][j]=min(f[i][j],f[i+1][j-1]);
}
write(f[1][n]);
return 0;
}
状态压缩DP的水题
现在我们来水做状态压缩DP
(1)互不侵犯(SCOI2005)
题面依旧见链接http://www.lydsy.com/JudgeOnline/problem.php?id=1087
这个题我们要做的就是用二进制表示状态
然后我们只需要对相邻行状态进行比较即可
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#define int long long
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 512
#define sten 11
#define stin 33
int n,k,f[sten][stan][stin],cntking[stan],ans,t;
signed main(){
n=read();k=read();
if(k>25||k>=n*n){
puts("0");
return 0;
}else{
t=1<<n;
f[0][0][0]=1;
cntking[0]=0;
for(int i=1;i<t;++i)
cntking[i]=cntking[i>>1]+(i&1);
for(int i=1;i<=n;++i)
for(int j=0;j<t;++j)
if(cntking[j]<=k&&!(j&(j>>1)))
for(int l=0;l<t;++l)
if(cntking[l]<=k&&!(l&(l>>1))&&!(l&j)&&!(l&(j>>1))&&!(l&(j<<1)))
for(int o=cntking[j]+cntking[l];o<=k;++o)
f[i][j][o]+=f[i-1][l][o-cntking[j]];
for(int i=0;i<t;++i)
if(cntking[i]<=k)
ans+=f[n][i][k];
write(ans);
}
return 0;
}
(2)奖励关(SCOI2008)
题面见链接点击打开链接
首先我们先明确本步期望=(上一步期望+本步得分)/k
然后对于其前置状态,只需要以状态压缩进行check即可
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<1)+(i<<3)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 111
#define sten 1<<16
int pos[stan],k,n,val[stan],tmp,need[stan];
double f[stan][sten];
signed main(){
for(int i=1;i<=16;++i)
pos[i]=1<<(i-1);
k=read();n=read();
for(int i=1;i<=n;++i){
val[i]=read();
tmp=read();
while(tmp){
need[i]+=pos[tmp];
tmp=read();
}
}
for(int i=k;i;--i)
for(int j=0;j<pos[n+1];++j){
for(int l=1;l<=n;++l){
if((need[l]&j)==need[l])
f[i][j]+=max(f[i+1][j],f[i+1][pos[l]|j]+val[l]);
else
f[i][j]+=f[i+1][j];
}
f[i][j]/=n;
}
printf("%.6lf",f[1][0]);
return 0;
}