CodeForces 645E Intellectual Inquiry(构造+贪心+dp)

时间:2021-09-02 19:10:41

题意:给出一个字符串t,现在可以在这个字符串后面加n个k以下的字母,询问一种方案,使得加了n个字母后的字符串中不相同的子串(子串不一定连续)最多。

思路:首先先看给定的字符串t,要解决的第一个问题是要如何求字符串t中不相同的子串数量。

这一步可以dp来做,用dp[i]表示位置i之前的子串数量,假设当前已经考虑到了第i个位置,也就是说之前的dp[0]~dp[i-1]已经求了出来,

再用pos[j]表示字符j上次出现的位置

对于当前位置i来说,因为比上一次多了一个字符,所以会增加dp[I-1]个以当前字符结尾的新子串,但是这些子串中有重复的,也就是以当前字符上次位置作为结束位置的子串数量。

所以可以得到转移方程dp[I] = dp[I-1] * 2 - dp[ pos[t[I] - 1] ];

剩下的问题就是向这个字符串后面加字符串了,怎么加才能使最后的字符串子串数量最多呢,方法是每加一个位置都取当前的最优解,也就是局部的最优解就是整体的最优解

可以贪心的来想,考虑字符串后的第一个位置,肯定是加之前所有pos[j]中最小的,然后就确定了这一步的位置,然后每一步都这样取最优解

但这样是否能保证最后的解一定是最优解呢?答案是肯定的,因为根据上面的转移方程其实最后的子串数量等于

(dp[len]<<n) - (pos1<<(n-1)) - (pos2<<(n-2)) - ......posn (posk为第k个位置选取的位置)

可以看到每一项的权值都大于后面的所有项的和,所以局部最优可以保证全局最优。

#include<bits/stdc++.h>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
#define pb push_back
#define mp make_pair
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int MAXN = 1000100;
const int MOD = 1e9+7;
int n, k;
LL dp[MAXN];
int pos[30];
char t[MAXN];
int main()
{
    freopen("input.txt", "r", stdin);
	scanf("%d%d%s", &n, &k, t+1);
	int len = strlen(t+1);
	dp[0] = 1;
	for (int i = 1; i <= len; i++) {
		dp[i] = (dp[i-1]<<1); 
		if (pos[t[i]-'a'] > 0)
			dp[i] -= dp[pos[t[i]-'a']-1];
		dp[i] %= MOD;
		pos[t[i]-'a'] = i;
	}
	for (int i = len+1; i <= len+n; i++) {
		dp[i] = (dp[i-1]<<1); 
		int id, p = i;
		for (int j = 0; j < k; j++) {
			if (pos[j] < p) {
				p = pos[j];
				id = j;
			}
		}
		if (p > 0)
			dp[i] -= dp[p-1];
		dp[i] %= MOD;
		pos[id] = i;
	}
	printf("%d", (dp[len+n]+MOD)%MOD);
    return 0;
}