牛客练习赛28 D 随风飘(dp + 字符串哈希)
能用字符串哈希解决的问题,千万别用后缀数组、字典树什么的了……
这题有很多个询问,每次询问是从n个中拿走k个字符串,问拿走之后的答案。我们显然不能把所有拿走的方案枚举一遍,所以考虑计算每一个字符串的贡献。这里我的贡献指第i个字符串与它前面的字符串的贡献。而这个贡献就是计算当前串与前面所有串的lcp。这里千万不要看到lcp就去想后缀数组,这里是多个串的lcp,而不是一个串的lcp,所以后缀数组显然是麻烦了。字典树是一个好的选择,但是字符串哈希显然更简单。对于每个串不断的把它所有前缀的哈希值记录下来,然后每个串的贡献,就可以看它前缀出现的次数。
然后我们考虑dp。令dp[i][j]表示考虑前i个串,从中取走j个串的答案。那么可以分为第i个串取走或者不被取走,有:
前面部分表示第i个串取走,后面部分表示第i个串不取走。不取走的话就是前面i-1个串取走j个的答案,加上第i个串对前面产生的贡献。出现这种情况的次数是 ,第i个串对前面产生的单次总贡献是f[i],这样合起来就是
,但是由于有j个串被删除了所以得去掉一些。对于去掉的部分,我们再次考虑贡献。如果第k个串被删掉,那么多加的答案就是
那么总的就是:
所以说最后就是 。dp求解,然后对于每个询问O(1)输出即可。最后你会发现,答案其实就是dp[n][0]*C(n-2,j)。具体见代码:
#include<bits/stdc++.h>
#define LL long long
#define pb push_back
#define lb lower_bound
#define ub upper_bound
#define INF ((1LL<<31)-1)
#define PI 3.1415926535
#define sf(x) scanf("%d",&x)
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#include<ext/pb_ds/priority_queue.hpp>
#include<ext/pb_ds/assoc_container.hpp>
#define sc(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define clr(x,n) memset(x,0,sizeof(x[0])*(n+5))
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
using namespace std;
using namespace __gnu_pbds;
const int N = 4010;
const int mod1 = 100001651;
const int mod2 = 100001623;
const int mod = 1000000007;
LL dp[N][310],c[N][N],f[N];
gp_hash_table<LL,int> t;
char s[3000010];
int n,q;
LL add(LL pre1,LL pre2,int cur)
{
pre1=(pre1*111+cur)%mod1;
pre2=(pre2*100007+cur)%mod2;
return pre1<<31|pre2;
}
int main()
{
sf(n); sf(q);
for(int i=1;i<=n;i++)
{
LL now=0;
scanf("%s",s);
for(int j=0;s[j];j++)
{
now=add(now>>31,now&INF,s[j]-'a');
f[i]=(f[i]+t[now])%mod2; t[now]++;
}
}
for(int i=0;i<=n;i++) c[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
for(int i=1;i<=n;i++)
for(int j=0;j<=min(i,300);j++)
{
if (j) dp[i][j]=dp[i-1][j-1];
if (i-j>=2)
{
dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
dp[i][j]=(dp[i][j]+c[i-2][j]*f[i]%mod)%mod;
}
}
while(q--)
{
int x; sf(x);
printf("%lld\n",dp[n][x]);
}
return 0;
}