• 递推专题


    1:打砖块


    这道题的一个非常重要的细节是:只要子弹打光,就必须结束,无论是否还有可以打到的有奖励子弹的砖块。也就是说,有奖励子弹的砖块不等价于不耗费子弹就能获得分数。就是因为这个细节,我们需要双重递推。

    f[i][j]表示第i列打j下能获得的分数,g[i][j]表示第i列打j下且第j下不能接着打有奖励子弹的砖块能获得的分数。这个是可以在O(mk)时间内预处理出来的。

    然后进行DP。同样设d[i][j]表示前i列打j下能获得的最大分数,d0[i][j]表示前i列打j下且最后一下(不一定在第i列打第j下)不能重复打所能获得的最大分数。设在第i列打了p下,那么:

    d的转移非常显然:d[i][j]=max{ d[i-1][j-p] +f[i][p] | 0<=p<=j }

    d0的转移麻烦一些。

    先看p=0的情况:p=0时,不能把第j下放在第i列,所以 d0[i][j]=d0[i-1][j] + f[i][0]

    再看p=j的情况:p=j时,p-j=0,不能把第j下放在前i-1列,所以d0[i][j]=d[i-1][0] + g[i][j]

    最后看0<p<j的情况:d0[i][j]=max{ max(d0[i][j-p]+f[i][p] , d[i][j-p]+g[i][p] ) }

    最后的答案就是d0[m][k].(数据里没出现所有砖块打完后子弹还有剩余的情况,非常良心。。)

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
     typedef unsigned long long uLL;
    
     #define rep(i,a,b) for (int i=a; i<=b; i++)
     #define dep(i,a,b) for (int i=a; i>=b; i--)
     #define read(x) scanf("%d", &x)
    
     const int N=200+5, M=N, K=M;
    
     int n, m, k, a[N][M], s[N][M];
     uLL f[M][K], d[M][K];
     char c;
    
    int main()
    {
    	read(n); read(m); read(k);
    	rep(i,1,n) rep(j,1,m) {
    		read(a[i][j]);
    		scanf("%c", &c); scanf("%c", &c);
    		if (c=='N') s[i][j]=0; else s[i][j]=1; 
    	}
    	// 设f[i][tk]表示打第i列,用了tk发子弹所能获得的分值
    	rep(i,1,m) rep(j,1,k) f[i][j]=0;
    	rep(i,1,m) {
    		int tk=0;
    		f[i][0]=0;
    		dep(j,n,1) {
    			if (s[i][j]==0) { ++tk; if (tk>0) f[i][tk]=f[i][tk-1]; } 
    			if (tk>k) break;
    			f[i][tk]+=a[j][i];
    		}
    	}
    	// 设d[i][j]表示打到第i列,用了j发子弹所能获得的最大分值
    	rep(i,0,k) d[0][i]=0;
    	rep(i,1,m) rep(j,0,k) {
    		d[i][j]=0;
    		rep(p,0,j) d[i][j]=max(d[i][j], f[i][p]+d[i-1][j-p]);
    	}
    
    	printf("%lld
    ", d[m][k]);
    
    	return 0;
    }
    

    2NOIP2015 Day2T2字符串匹配

    题目链接:https://vijos.org/p/1982

    先来回顾一下和本题很像的两个经典问题:

    LCSd[i][j]表示字串A的前i位和字串B的前j位的LCS长度(不强制选a[i]b[j]),则有:

    d[i][j]=max{ d[i][j-1],d[i-1][j] | a[i]b[j] }, d[i][j]=d[i-1][j-1]+1(a[i]=b[j])

    LIS:设d[i]表示以a[i]结尾的LIS长度(即强制选i),则有:

    d[i]=max{ d[j] | a[i]>=a[j] } +1

    以上两个问题的状态设计是有其原因的。LCS中,后面的状态不会受到前面状态所依赖的原值的影响;而LIS会。

    再来看本题。本题的状态如果只设计一种,比如:

    d[i][j][k]表示考虑A串的前i位,匹配了B串的前j位,且已分成k段的方案数;

    f[i][j][k]表示考虑A串的前i位,匹配了B串的前j位,且强制选第i位,且已分成k段的方案数;

    以及我考场上傻逼出的各种奇葩的根本无法转移的状态。

    然而本题的后面状态和前面状态的关系比较含糊。以上这些都无法单独转移。d[i][j][k]会造成重复,而f[i][j][k]根本不能搞:没法断开,断开的话转移的时间代价太大。

    所以:两个状态互推!发现很容易转移了:

    如果a[i]=b[j],则   f[i][j][k] = f[i-1][j-1][k] +g[i-1][j-1][k-1] (连着a[i-1]-b[j-1]的方案数+不连着前面的方案数)

     g[i][j][k] = f[i][j][k] + g[i-1][j][k](选a[i]方案数 +不选a[i]方案数)

    如果a[i]b[j],则  f[i][j][k] = 0

     g[i][j][k]= g[i-1][j][k]

    但还有一个问题:这题卡空间。如果开满100分数组的话会MLE。所以需要采用滚动数组优化。根据这个递推的特点,我们可以优化掉第一维,也就是f[j][k]表示匹配到B串的位置j且已经分成k段的方案数,g亦然。然后按照jk降序的顺序逆推即可。详见代码(代码中d数组即为上述分析中的g)。

    更新:在某神犇的博客上看到了另一种状态设计,像是把上面这两种状态揉在一起了。。也可以很神奇地转移。。非常优美。。

    令f[i][j][k]表示当前第i位,匹配到母串第j个,已经匹配了k段的方案数。仅当b[i]=a[j]时f[i][j][k]!=0。然后f[i][j][k]=f[i-1][j-1][k]+Σ(x=1,j-1)f[i-1][x][k-1]。然后滚动一下空间ok,前缀和一下时间ok。”

    详见 http://blog.csdn.net/lych_cys/article/details/50094255

    // NOIP2015 Day2 T2
    
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
     typedef long long LL;
     typedef unsigned long long uLL;
     const int N=1000+5, M=200+5, K=M, INF=0x3f3f3f3f, mod=1000000007;
    
     #define rep(i,a,b) for (int i=a; i<=b; i++)
     #define dep(i,a,b) for (int i=a; i>=b; i--)
     #define read(x) scanf("%d", &x)
     #define fill(a,x) memset(a, x, sizeof(a))
    
     int n, m, k, f[M][K], d[M][K];
     char a[N], b[M];
     
    int main()
    {
    	read(n); read(m); read(k);
    	fill(d, 0); fill(f, 0);
    	scanf("%s
    %s", a, b);
    	d[0][0]=1; // 十分优美的初始化(雾。。
    	rep(i,1,n) 
    	  dep(j,min(i,m),1) 
    	    if (a[i-1]==b[j-1]) 
    	      dep(p,min(k,j),1) { 
    	    	f[j][p]=(f[j-1][p]+d[j-1][p-1])%mod;
    	    	d[j][p]=(f[j][p]+d[j][p])%mod;
    	      }
    	    else fill(f[j], 0);  // 两个字符不相同时,f数组全置为0,s数组不变 
    
    	printf("%d
    ", d[m][k]);
    	
    	return 0;
    }
    


  • 相关阅读:
    2.NET Core设定数据库种子
    1.ASP.NET Core 中向 Razor Pages 应用添加模型
    获取文件夹目录下的文件信息
    dataGridView读写文本
    C# winform 启动外部程序
    netcore访问本地磁盘
    c#利用定时器自动备份数据库(mysql)
    c#mysql数据库备份还原
    Linux之旅(二)
    Linux之旅
  • 原文地址:https://www.cnblogs.com/yearwhk/p/5119881.html
Copyright © 2020-2023  润新知