前言
从前,我有两篇题解的坑还没有填,一道是DP,另一道也是DP。
BZOJ 2958: 序列染色
Description
给出一个长度为N由B、W、X三种字符组成的字符串S,你需要把每一个X染成B或W中的一个。
对于给出的K,问有多少种染色方式使得存在整数a,b,c,d使得:
1<=a<=b
<
<script type="math/tex" id="MathJax-Element-1"><</script>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
位,状态为
很明显,如果当前这位不是W的话
不是B也一样。
这里是没有那么多的XJB转移,在这里不符合状态的那部分的方案也暂且记住,比如j=0的状态在乱转K位后包含了1,1包含了2等等。
然后如果当前不是W且连续K个都没有W的话,我们可以转移
然后将前面乱转移多的那部分减去 (连续K个累计的一齐减去)
这里可以称作 补偿转移,或者就是 容斥原理。
连续一段都是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 1000010
using 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)+j−i 个,用乘法原理合并。
第二问就比较麻烦了,里面有个组合数学的DP,还有个前缀和优化(好像只优化了空间)。
照样把高度相同的一起考虑,高度相同按关键值升序排。然后根据乘法原理乘起来。在就是把x个一样的球放入y个不同位置的方案,就是不管顺序,取组合数。记
dp[i][j]
为前
i
个球放入前
然后发现可以滚动,变成 f[j]+=f[j−1]。(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 2011
using 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深似海,从此智商是路人。
我已和魔女签订契约,是不能成为神的朋友的。