「CF76C Mutation」
题目大意
给出一个由大写字母构成的字符串,定义一个长度为 \(n\) 的字符串 \(s\) 的代价为 \(\sum\limits_{i=1}^{n}g_{s_i,s_{i+1}}\),其中 \(g\) 为一个长宽都为字符集大小的矩阵.现在有一种操作可以将某一种字符从字符串中删去,删除每一种字符有不同的代价,求有多少的删字符方案满足代价和小于 \(t\).
分析
最暴力做法显然是 \(\mathcal{O}(2^k\cdot n^2)\sim\mathcal{O}(2^k\cdot n)\) 而且非常不好优化,所以需要换一种思路去考虑.先不去考虑方案而是去考虑删去的字符集,对于一个字符,如果这个字符想要和自己前面的某一个字母连接并产生代价显然这两个字符之间的其他字符必须删除,而且可以与它产生代价的字符的位置必然是前面最后一次出现的位置,否则中间删除字符的时候也会被删去,也就是说对于一个位置他最多只有字符集大小种(\(\leq22\))不同的代价.现在知道了某次代价这连个字符之间需要删去的字符集为 \(S\),产生的代价为 \(c=g_{s_x,s_y}\),那么对于所有的 \(T\) 满足 \(S\subseteq T,s_x\not\in T,s_y\not\in T\) 的集合 \(T\) 都要加上这个代价,其中 \(s_x\not\in T,s_y\not\in T\) 这两个条件显然可以直接容斥后去除.于是问题转化成了形如「给出一个集合 \(S\),要给所有包含 \(S\) 的集合 \(T\) 都加上一个代价 \(c\)」这样的问题,可以直接用高维前缀和解决(在一个 \(k\) 维,每维只有 \(0,1\) 的数组上做的 \(k\) 维前缀和(显然任意的一个集合 \(S\) 都可以在这个数组中找到一个对应的位置),显然如果某一维中代价在 \(0\) 处,那么对于这维为 \(0,1\) 的集合都可能产生代价.反之,如果代价在 \(1\) 处,那么只要当这维为 \(1\) 的集合才可能得到代价,这与转化后的问题相同).
时间复杂度 \(\mathcal{O}(nk\log_2k+2^k\cdot k)\sim\mathcal{O}(nk+2^k\cdot k)\).
代码
#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;last<=i;--i)
namespace IO
//快读模板
using namespace IO;
using namespace std;
const int MAXN=2e5+5;
int n,k,t;
int s[MAXN];
int val[MAXN];
int v[26][26];
bool app[26];
LL sum_=0;
int answer=0;
LL link[5000005];
bool Check(int mask)
{
LL sum=0;
REP(i,0,k-1)
{
if(!app[i]&&(mask&(1<<i)))
{
return 0;
}
if(mask&(1<<i))
{
sum+=val[i];
}
}
if(sum==sum_)//不能全部删去
{
return 0;
}
return sum+link[mask]<=t;//将字符串的代价和删除的代价相加与 t 比较
}
int last[MAXN];
int main()
{
Read(n,k,t);
char ch;
REP(i,1,n)
{
Read(ch);
app[ch-'A']=1;
s[i]=ch-'A';
}
REP(i,0,k-1)
{
Read(val[i]);
}
REP(i,0,k-1)
{
if(app[i])
{
sum_+=val[i];
}
}
REP(i,0,k-1)
{
REP(j,0,k-1)
{
Read(v[i][j]);
}
}
PII sor[23];
REP(i,1,n)
{
REP(j,0,k-1)
{
sor[j]=make_pair(last[j],j);
}
sort(sor,sor+k);
int mask=0;
DOW(j,k-1,0)//一次枚举这个可以与 s[i] 产生贡献的在 i 前面的字符
{
if(!sor[j].first||sor[j].first<last[s[i]])
{
break;
}
link[mask]+=v[sor[j].second][s[i]];//容斥部分
link[mask|1<<sor[j].second]-=v[sor[j].second][s[i]];
link[mask|1<<s[i]]-=v[sor[j].second][s[i]];
link[mask|1<<sor[j].second|1<<s[i]]+=v[sor[j].second][s[i]];
mask|=1<<sor[j].second;
}
last[s[i]]=i;
}
int top=(1<<k)-1;
REP(i,0,k-1)//高维前缀和部分
{
REP(j,0,top)
{
if(j&(1<<i))
{
link[j]+=link[j^(1<<i)];
}
}
}
REP(i,0,top)//最后依次判断每一种删字符方案即可
{
answer+=Check(i);
}
Writeln(answer);
return 0;
}