0x55~0x56

0x55~0x66

0x56 状压DP

a.[√] Mondriaan's Dream

题目传送

sol:

注意到数据范围十分的小,考虑状态压缩。

依次考虑每一行,可以注意到这一行的方块分成两种:

① 横着填的方块中一块。

② 竖着填的方块中一块。

分析一下当前行对下一行的影响:当且仅当本行存在 竖着填的那种方块 中的上面那一块 才会对下一行有影响。

所以把满足上面情况的位置记为1,其余位置记为0。

另外为了保证合法,必须保证除了竖着填占了的,剩下的连续的每一段0都是偶数。

这可以提前处理好,设合法集合位G。

那么转移为:
\[ f[i,j]=\sum_{j\&k\ 并且\ j|k\in G}{f[i-1,k]} \]
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\n",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\n",ans);
    return 0;
}

0x55 有后效性的DP处理

c.[√]Naptime

题目传送

sol:

不难考虑设\(f[i,j,0/1]\)表示前i个小时,睡了j小时,第i小时不在(在)睡的最大体力值。

那么转移应该为:
\[ f[i,j,0]=max\{f[i-1,j,0],f[i-1,j,1]\}\\ f[i,j,1]=max\{f[i-1,j-1,0],f[i-1,j-1,1]+a[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\n",max(ans,f[n][B][1]));
    }
    return 0;
}
d[√].Broken Robot

题目传送

sol:

\(f[i,j]\)表示从i,j走到最后一行,所需要的步数的期望。

由于存在四种操作,不难列出转移:
\[ f[i,j]=\frac{1}{4}\{f[i,j-1]+f[i,j+1]+f[i,j]+f[i+1,j]\}+1 \]
对于边界条件特殊考虑,此处不列出。

可以发现这个转移存在有后效性,是无法进行的。

但是行与行之间却是满足无后效性,只能由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\n",f[x][y]);
    return 0;
}
//高斯消元解局部有后效性的DP

转载于:https://www.cnblogs.com/Bhllx/p/10975778.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值