BZOJ 2958: 序列染色 && BZOJ 3193: [JLOI2013]地形生成 —— 肆虐的DP

来源:互联网 发布:淘宝pv和uv是什么意思 编辑:程序博客网 时间:2024/04/29 05:13

前言

从前,我有两篇题解的坑还没有填,一道是DP,另一道也是DP。


BZOJ 2958: 序列染色

Description

  给出一个长度为N由B、W、X三种字符组成的字符串S,你需要把每一个X染成B或W中的一个。
  对于给出的K,问有多少种染色方式使得存在整数a,b,c,d使得:
  1<=a<=b<c<=d<=N
  Sa,Sa+1,…,Sb均为B
  Sc,Sc+1,…,Sd均为W
  其中b=a+K-1,d=c+K-1
  由于方法可能很多,因此只需要输出最后的答案对109+7取模的结果。


Input

  第一行两个正整数N,K
  第二行一个长度为N的字符串S
 


Output

  一行一个整数表示答案%(10^9+7)。


Sample Input

5 2
XXXXX


Sample Output

4

数据约定

  对于20%的数据,N<=20

  对于50%的数据,N<=2000

  对于100%的数据,1<=N<=10^6,1<=K<=10^6


解题思路(DP+补偿转移)

暴力就不说了,超时or难算,优雅的正解就在下面。

我们考虑记f[i][j][s]代表当前第i位,状态为j,0代表没有连续K个B或W,1代表有连续K个B,2代表既有连续K个B,又有连续K个W,最后的一个字符记为s,0代表B,1代表W(X就都可以)。

很明显,如果当前这位不是W的话

f[i][j][0]=f[i1][j][0]+f[i1][j][1]

不是B也一样。

这里是没有那么多的XJB转移,在这里不符合状态的那部分的方案也暂且记住,比如j=0的状态在乱转K位后包含了1,1包含了2等等。

然后如果当前不是W且连续K个都没有W的话,我们可以转移

f[i][1][0]=f[i][1][0]+f[iK][0][1]

然后将前面乱转移多的那部分减去 (连续K个累计的一齐减去)
f[i][0][0]=f[i][0][0]f[iK][0][1]

这里可以称作补偿转移,或者就是容斥原理

连续一段都是W的转移也一样,将f[i][1][1]多余部分减去。

炒鸡棒的DP,先乱转移一段,“欲擒故纵”,最后在特定的时候一齐减去多余的。

一开始就让第0个位置放W,最后的答案就是f[n][2][0]+f[n][2][1]

时间复杂度:O(n)

做这种题要么算出重复的减掉,要么直接算不重复的。往往前者要用容斥原理,后者要用巧妙的DP。

嫉妒使我补偿转移。


代码

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <cstdlib>#include <cmath>#define Mod 1000000007#define N 1000010using namespace std;int n, K, sumB[N], sumW[N], f[N][3][2];char s[N];int main(){    scanf("%d%d", &n, &K);    scanf("%s", &s);    f[0][0][1] = 1;    for(int i = 1; i <= n; i++){        sumB[i] = sumB[i-1] + (s[i-1] == 'B');        sumW[i] = sumW[i-1] + (s[i-1] == 'W');        if(s[i-1] != 'W')  for(int j = 0; j < 3; j++)  f[i][j][0] = (f[i-1][j][0] + f[i-1][j][1]) % Mod;        if(s[i-1] != 'B')  for(int j = 0; j < 3; j++)  f[i][j][1] = (f[i-1][j][0] + f[i-1][j][1]) % Mod;        if(i < K)  continue;        if(s[i-1] != 'W' && sumW[i] == sumW[i-K]){              f[i][1][0] = (f[i][1][0] + f[i-K][0][1]) % Mod;            f[i][0][0] = (f[i][0][0] - f[i-K][0][1] + Mod) % Mod;        }        if(s[i-1] != 'B' && sumB[i] == sumB[i-K]){              f[i][2][1] = (f[i][2][1] + f[i-K][1][0]) % Mod;            f[i][1][1] = (f[i][1][1] - f[i-K][1][0] + Mod) % Mod;        }    }    printf("%d\n", (f[n][2][0] + f[n][2][1]) % Mod);    return 0;}

BZOJ 3193: [JLOI2013]地形生成

Description

最近IK正在做关于地形建模的工作。其中一个工作阶段就是把一些山排列成一行。每座山都有各不相同的标号和高度。为了遵从一些设计上的要求,每座山都设置了一个关键数字,要求对于每座山,比它高且排列在它前面的其它山的数目必须少于它的关键数字。
显然满足要求的排列会有很多个。对于每一个可能的排列,IK生成一个对应的标号序列和等高线序列。标号序列就是按顺序写下每座山的标号。等高线序列就是按顺序写下它们的高度。例如有两座山,这两座山的一个合法排列的第一座山的标号和高度为1和3,而第二座山的标号和高度分别为2和4,那么这个排列的标号序列就是1 2,而等高线序列就是3 4.
现在问题就是,给出所有山的信息,IK希望知道一共有多少种不同的符合条件的标号序列和等高线序列。


Input

输入第一行给出山的个数N。接下来N行每行有两个整数,按照标号从1到N的顺序分别给出一座山的高度和关键数。


Output

输出两个用空格分隔开的数,第一个数是不同的标号序列的个数,第二个数是不同的等高线序列的个数。这两个答案都应该对2011取模,即输出两个答案除以2011取余数的结果


Sample Input

2
1 2
2 2


Sample Output

2 2


HINT

对于所有的数据,有1<=N<=1000,所有的数字都是不大于10^9的正整数。


解题思路(DP+组合数学)

这道题我考试时是不会做的,过了好长时间了,又有点忘了。

考虑第一问:将山按高度从大到小排序插入,因为有相等的高度,所以排在第i的数可以插入的位置有min(i,b[i]+1)+ji个,用乘法原理合并。

第二问就比较麻烦了,里面有个组合数学的DP,还有个前缀和优化(好像只优化了空间)。

照样把高度相同的一起考虑,高度相同按关键值升序排。然后根据乘法原理乘起来。在就是把x个一样的球放入y个不同位置的方案,就是不管顺序,取组合数。记dp[i][j]为前i个球放入前j个位置的方案。第i个放在j或不放:

dp[i][j]=dp[i1][j]+dp[i][j1]

然后发现可以滚动,变成f[j]+=f[j1](1<=j<=b[i])

注意这里不插入的方案是有1个的,用f[0]=1去代表它,并使它加入转移(这里有点懵B)。

当前高度下的答案是f[j], 注意边界的处理,放的位置不能进入同样高度区间里(前面算出装不下有剩余的已经包含此状态),也不能达到最后一个的关键值。

时间复杂度O(nlogn+n2)

DP要消除顺序的限制,一般要排序,强制规定顺序,使其能够正确转移,与排列组合有关的DP的固定套路要掌握,比如枚举取走的与剩下的状态是一一对应的(简单数学都不会就完了)。


代码

#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <cmath>#include <iostream>#define N 1010#define M 2011using namespace std;int n, ans1 = 1, ans2 = 1, f[N];struct Data{    int h, num;    bool operator < (const Data& Q)const {return h > Q.h || (h == Q.h && num < Q.num);}}mt[N];int main(){    scanf("%d", &n);    for(int i = 1; i <= n; i++)  scanf("%d%d", &mt[i].h, &mt[i].num), mt[i].num --;    sort(mt+1, mt+n+1);    for(int i = 1, t = 1; i <= n; i = ++t){        while(t < n && mt[t].h == mt[t+1].h)  ++ t;        memset(f, 0, sizeof(f));        f[0] = 1;        for(int j = i; j <= t; j++){            ans1 = (ans1 * (min(i, mt[j].num+1) + j - i)) % M;            for(int k = 1; k <= min(i-1, mt[j].num); k++)  f[k] = (f[k] + f[k-1]) % M;         }        int temp = 0;        for(int j = 0; j <= min(i-1, mt[t].num); j++)  temp = (temp + f[j]) % M;        ans2 = (ans2 * temp) % M;    }    printf("%d %d\n", ans1, ans2);    return 0;}

总结

一入DP深似海,从此智商是路人。


这里写图片描述

我已和魔女签订契约,是不能成为神的朋友的。