两道序列DP

(以下来自Luogu Voldermod 的10.21 Luogu个人赛的T1题面):

最后的战役打响了。

哈利被宣告“死亡”,伏地魔带着他的部下准备进攻霍格沃茨。但是霍格沃茨有古老的魔法保护,他们必须先摧毁这些保护。魔法保护一共有nnn层,每一层保护有两个参数:k,p。其中k表示魔法的类型,p表示能量的大小。

伏地魔每秒都会穿过一层保护,他在第 i秒(到达了第 i 层)他有以下选择:

1.收集 [1,i] 层魔法中魔法类型为 xii 的魔法能量

2.收集 [1,i]层中魔法能量最大那层的魔法能量

3.使用加倍魔法

对于上面三个选择,他每秒可以可以选择一个,并可能获得能量,对于不同的选择,获得的能量也不同:

对于1.获得[1,i]层中所有魔法类型为xi魔法能量

对于2.获得[1,i]中魔法能量最大的那一层的魔法能量

对于3.这一秒总共收集的能量不变(也就是这一秒不收集新的能量),但是下一秒获得的能量翻倍。但是他不能连续使用加倍魔法,而且他最多只能使用m次,对于每一层的能量他都可以重复获取

只有他通过了这n层保护,并获得了最大的魔法能量才有可能彻底摧毁霍格沃茨的魔法防御,可是巫师又是不擅长计算的。

于是,伏地魔找到了你,而你,作为精通计算机技术的麻瓜程序员,现在需要做的就是设计一个程序帮助伏地魔计算出他可以获得的最大的魔法能量的值。

最终的决战已经展开,魔法界的历史又翻过了一页……

显然DP,容易得到状态:设f[i][j][0/1]表示到了第i层,用了j次魔法,本次用(1)还是没用(0)。

那么状态转移方程就是:

res=max (k[PreM[i]], PreS[Map[x[i]]]); (Map是离散化用的,因为值域到了109

那么f[i][j][0]=max (f[j][0]+res, f[j][1]+res*2), f[i-1][j][1]=f[i-1][j-1][0];

特别的 f[i][0][0]=f[i-1][0][0]+res。

但是注意到内存只有256MB,而f数组要开long long,算一下有300多MB。空间炸了??

这个时候,注意到第i层只能有第i-1层转移过来,所以滚掉一维,倒序枚举就OK了。

#include <bits/stdc++.h>
using namespace std;
inline int gi () {
    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;
}

#define LL long long
#define RG register

const int M=510;
const int N=5e4+10;

map <int,int> Map;
LL Ans,f[M][2];
int n,m,tot,ty[N],b[N],x[N],k[N],PreM[N],PreS[N];

int main ()
{
    RG int i,j,res;
    n=gi (), m=gi ();
    for (i=1;i<=n;++i) {
        ty[i]=gi (), k[i]=gi ();
        if (Map.find (ty[i])==Map.end ()) Map[ty[i]]=++tot;
    }
    for (i=1;i<=n;++i) x[i]=gi ();
    for (i=1;i<=n;++i) 
        if (k[i]>k[PreM[i-1]]) PreM[i]=i;
        else PreM[i]=PreM[i-1];
    //for (i=1;i<=n;++i) cout << ty[i] << ' ';
    //cout << '
';
    //for (i=1;i<=n;++i) cout << PreM[i] << ' ';
    // cout << '
';1
    f[0][0]=k[1], PreS[Map[ty[1]]]=k[1], Ans=k[1];
    for (i=2;i<=n;++i) {
        PreS[Map[ty[i]]]+=k[i];
        // cout << "PreSum:" << PreS[i] << '
';
        res=max (k[PreM[i]], PreS[Map[x[i]]]);
        // cout << "res:" << res << '
';
        for (j=min (m,i);j>=1;--j) {
            //    cout << "j=" << j << '
';
            f[j][0]=max (f[j][0]+res, f[j][1]+res*2);
            //cout << f[j][0] << '
';
            f[j][1]=f[j-1][0];
            //cout << f[j][1] << '
';
            //cout << "now:" << Ans << '
';
            Ans=max (Ans, max (f[j][0], f[j][1]));
            //cout << "now:" << Ans << '
';
        }
        f[0][0]+=res;
        //cout << f[0][0] << '
';
        Ans=max (Ans, f[0][0]);
    }
    printf ("%lld
", Ans);
    return 0;
}

考场上还Debug了好一阵。哎。。

另外一道是Luogu10月月赛二的T2。LuoguP4933.

看完题目一眼,和LIS什么的挺像,DP??

n<=1000,v<=20000, O(nv)都随便过。。

那么比较老套路一点的,设f[i][j]表示 前i个数中 以i结尾的 等差为j的 等差数列的方案数。

转移的话,枚举[1,i-1],直接转移就好了。那么的话,f[i][j]=(f[i][j]+f[k][j]+1)%mod, k属于[1, i-1];

answer 的话就是所有的状态的加起来。

但是我们注意到,等差数列是可以递增或递减的,那么可以值域扩大两倍,或再反着DP一次就好了。

我的是正反各DP一次,值得注意的是,如果有相等的数只需考虑一次就好了,否则就得减掉一半。

#include <bits/stdc++.h>
using namespace std;
inline int gi () {
    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;
}

#define RG register

const int N=1100;
const int V=20010;
const int Mod=998244353;

int n,Ans,h[N],zf[N][V],ff[N][V];

int main ()
{
    // BY 薄荷凉了夏
    RG int i,j,dc;
    n=gi ();
    for (i=1;i<=n;++i) h[i]=gi ();
    for (i=1;i<=n;++i)
        for (j=1;j<i;++j) {
            dc=h[i]-h[j];
            if (dc<0) continue;
            zf[i][dc]=(zf[i][dc]+zf[j][dc]+1)%Mod;     
        }
    for (i=n;i>=1;--i)
        for (j=i+1;j<=n;++j) {
            dc=h[i]-h[j];
            if (dc<=0) continue;
            ff[i][dc]=(ff[i][dc]+ff[j][dc]+1)%Mod; 
        }
    for (i=1;i<=n;++i)
        for (j=0;j<=20000;++j)
            Ans=(Ans+zf[i][j])%Mod;
    for (i=n;i>=1;--i)
        for (j=0;j<=20000;++j)
            Ans=(Ans+ff[i][j])%Mod;
    printf ("%d
", Ans+n);
    return 0;
}
原文地址:https://www.cnblogs.com/Bhllx/p/9827481.html