0x55~0x66
0x56 状压DP
a.[√] Mondriaan's Dream
sol:
注意到数据范围十分的小,考虑状态压缩。
依次考虑每一行,可以注意到这一行的方块分成两种:
① 横着填的方块中一块。
② 竖着填的方块中一块。
分析一下当前行对下一行的影响:当且仅当本行存在 竖着填的那种方块 中的上面那一块 才会对下一行有影响。
所以把满足上面情况的位置记为1,其余位置记为0。
另外为了保证合法,必须保证除了竖着填占了的,剩下的连续的每一段0都是偶数。
这可以提前处理好,设合法集合位G。
那么转移为:
code:
#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;
const int N=13;
const int M=2049;
int n,m,ans,g[M];
LL f[N][M];
int main()
{
RG int i,j,k,fl,cnt,all;
while(scanf("%d%d",&n,&m)!=EOF&&n&&m) {
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
f[0][0]=1,all=1<<m;
for(i=0;i<all;++i) {
fl=cnt=0;
for(j=0;j<m;++j)
if((i>>j)&1) fl|=cnt,cnt=0;
else cnt^=1;
g[i]=!(fl|cnt);
}
for(i=1;i<=n;++i)
for(j=0;j<all;++j)
for(k=0;k<all;++k)
if(!(j&k)&&g[j|k])
f[i][j]+=f[i-1][k];
printf("%lld
",f[n][0]);
}
return 0;
}
b.[√]炮兵阵地
sol:
首先可以把一行行的合法情况预处理出来,记为G。
然后把最大的数据输进去测一下发现最多也就只有60种情况,比起1048小了很多。
接着考虑竖直上的,发现影响了上(下)两行,那么记录连续两行即可。
接下来是无脑转移了。
那么复杂度(O(n*G^3))。
code:
#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;
const int N=103;
const int M=1025;
char s[N];
int n,m,tot,ans,g[M],a[N],b[N],f[N][N][N];
IL bool check(int x) {
if((x>>1)&x||(x>>2)&x||(x<<1)&x||(x<<2)&x) return 0;
return 1;
}
IL int calc(int x) {
RG int cnt=0;
for(;x;x>>=1) cnt+=x&1;
return cnt;
}
int main()
{
RG int i,j,k,p,all;
scanf("%d%d",&n,&m),all=1<<m;
for(i=0;i<all;++i)
if(check(i)) g[++tot]=i;
for(i=1;i<=tot;++i) b[i]=calc(g[i]);
for(i=1;i<=n;++i) {
scanf("%s",s);
for(j=0;j<strlen(s);++j)
if(s[j]=='H') a[i]|=1<<j;
}
for(i=1;i<=tot;++i) f[1][i][1]=b[i];
for(i=2;i<=n;++i)
for(j=1;j<=tot;++j)
if(!(g[j]&a[i]))
for(k=1;k<=tot;++k)
if(!(g[k]&a[i-1])&&!(g[j]&g[k]))
for(p=1;p<=tot;++p)
if(!(g[p]&a[i-2])&&!(g[j]&g[p])&&!(g[k]&g[p]))
f[i][j][k]=max(f[i][j][k],f[i-1][k][p]+b[j]);
for(i=1;i<=tot;++i)
for(j=1;j<=tot;++j) ans=max(ans,f[n][i][j]);
printf("%d
",ans);
return 0;
}
0x55 有后效性的DP处理
c.[√]Naptime
sol:
不难考虑设(f[i,j,0/1])表示前i个小时,睡了j小时,第i小时不在(在)睡的最大体力值。
那么转移应该为:
但是由于这个时间是连续的,所以转移似乎找不到一个起点。
所以不妨钦定一个起点开始转移,若定为1,则第1个小时必然不能获得体力。
所以初始化(f[1,0,0]=f[1,1,1]=0),进行转移,最后(ans1=max{f[n,B,0],f[n,B,1]})。
但是这样的话就强制了第1个小时没休息,所以还需要补上n点到1点间休息且第1个小时也休息的情况。
此时初始化(f[1,1,1]=a[i])即可,最后(ans2=f[n,B,1])。
二者取较大值即为最后的答案。
code:
#include<bits/stdc++.h>
#define RG register
#define IL inline
#define LL long long
using namespace std;
IL int gi() {
RG int x=0,w=0; char ch=0;
while(ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return w?-x:x;
}
const int N=4010;
int n,B,ans,rt[N],f[N][N][2];
int main ()
{
RG int i,j,T=gi();
while(T--) {
n=gi(),B=gi();
for(i=1;i<=n;++i) rt[i]=gi();
memset(f,0xcf,sizeof(f));
f[1][1][1]=f[1][0][0]=0;
for(i=2;i<=n;++i)
for(j=0;j<=i;++j) {
f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]);
if(j>0) f[i][j][1]=max(f[i-1][j-1][0],f[i-1][j-1][1]+rt[i]);
}
ans=max(f[n][B][0],f[n][B][1]);
memset(f,0xcf,sizeof(f));
f[1][1][1]=rt[1];
for(i=2;i<=n;++i)
for(j=0;j<=i;++j) {
f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]);
if(j>0) f[i][j][1]=max(f[i-1][j-1][0],f[i-1][j-1][1]+rt[i]);
}
printf("%d
",max(ans,f[n][B][1]));
}
return 0;
}
d[√].Broken Robot
sol:
设(f[i,j])表示从i,j走到最后一行,所需要的步数的期望。
由于存在四种操作,不难列出转移:
对于边界条件特殊考虑,此处不列出。
可以发现这个转移存在有后效性,是无法进行的。
但是行与行之间却是满足无后效性,只能由i+1行转移到i行。
所以可以由n到1一行行考虑,然后再具体解每一行:
首先对于当前行的每一个位置,都有一个方程,共m个。
然后由于当前行的下一行已经求解完毕,所以总共也只有m个未知量。
所以可以用高斯消元把DP值直接求解出来。
这样就只需要对转移式子进行移项得到系数即可。
code:
#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;
IL int gi() {
RG int x=0,w=0; char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
return w?-x:x;
}
const int N=1003;
int n,m,x,y;
DB f[N][N],g[N][N];
IL void build(int i) {
if(m==1) {
g[1][1]=-0.5,g[1][2]=-0.5*f[i+1][1]-1;
return;
}
RG int k;
g[1][1]=g[m][m]=-2.0/3.0,g[1][2]=g[m][m-1]=1.0/3.0;
g[1][m+1]=-1.0/3.0*f[i+1][1]-1,g[m][m+1]=-1.0/3.0*f[i+1][m]-1;
for(k=2;k<m;++k)
g[k][k-1]=g[k][k+1]=0.25,g[k][k]=-0.75,g[k][m+1]=-0.25*f[i+1][k]-1;
}
IL void gauss(int R) {
RG int i;
for(i=1;i<m;i++) {
double RT=g[i+1][i]/g[i][i];
g[i+1][i]-=RT*g[i][i];
g[i+1][i+1]-=RT*g[i][i+1];
g[i+1][m+1]-=RT*g[i][m+1];
}
f[R][m]=g[m][m+1]/g[m][m];
for (i=m-1;i>=1;i--) f[R][i]=(g[i][m+1]-f[R][i+1]*g[i][i+1])/g[i][i];
}
int main()
{
RG int i;
n=gi(),m=gi(),x=gi(),y=gi();
for(i=n-1;i>=x;--i) build(i),gauss(i);
printf("%.10lf
",f[x][y]);
return 0;
}
//高斯消元解局部有后效性的DP