CodeForces 76 C.Mutation(状压DP+容斥原理+高维前缀和)

256 篇文章 0 订阅
190 篇文章 1 订阅

Description

给出一个由前 k k 个大写字母组成的字符串,每次可以花费ti代价删去该字符串中所有的第 i i 个大写字母,一个字符串的代价为该字符串每对相邻字符的代价,给出一k×k的矩阵表示两个相邻的大写字母的代价矩阵,问有多少种删除方案使得删除的代价以及剩余字符串的代价之和不超过 T T ,注意剩余字符串需非空

Input

第一行三个整数n,k,T表示字符串长度,所用字母种类以及代价上限,之后输入一个由前 k k 个大写字母组成的长度为n的字符串,以及 k k 个整数ti表示删去第 i i 种字符的代价,最后输入一个k×k的矩阵 m[x][y] m [ x ] [ y ] 表示相邻两字母的代价矩阵

(1n2105,1k22,1T2109,1ti,m[x][y]109) ( 1 ≤ n ≤ 2 ⋅ 10 5 , 1 ≤ k ≤ 22 , 1 ≤ T ≤ 2 ⋅ 10 9 , 1 ≤ t i , m [ x ] [ y ] ≤ 10 9 )

Output

输出满足条件的方案数

Sample Input

5 3 13
BACAC
4 1 2
1 2 3
2 3 4
3 4 10

Sample Output

5

Solution

k k 01表示每种字母是否需要删除, 1 1 表示要删去该种字符

考虑i,j位置的两个字符 x,y x , y ,假设两个位置之间出现字符的状态为 S S ,那么删去状态S之后代价增加 m[x][y] m [ x ] [ y ] ,令 f[S] f [ S ] 表示 S S 作为两个位置之间字符状态,删去后由这两个位置字符产生的代价和,那么对于第i个位置的字符 x x ,位置在其后且可以与其产生代价的字符y的种类不会超过 k1 k − 1 个(必须是 i i 位置后第一次出现的y字符才可能与 x x 产生代价),故满足条件的(x,y)对数不会超过 nk n k ,可以直接暴力找到,两者之间的状态也很好维护,注意 S S 中若包含x,y字符则不计入代价

dp[S] d p [ S ] 表示删去 S S 状态后的代价,只要dp[S]T则为一种合法方案。直观上看, dp[S] d p [ S ] 的值是由若干相邻字符代价构成,而每对相邻字符在原先字符串中可能并不相邻,而是通过删去某个 S S 的子状态使其相邻的。但直接枚举S的子状态 S S ′ 累加 f[S] f [ S ′ ] 赋予 dp[S] d p [ S ] 并不正确,例如对于状态 S S ′ ,产生了一个代价 m[x][y],x,yS m [ x ] [ y ] , x , y ∉ S ′ ,而 xS x ∈ S yS y ∈ S ,这就导致 m[x][y] m [ x ] [ y ] 这一并不应该被记入 dp[S] d p [ S ] 的代价通过 f[S] f [ S ′ ] 记入了 dp[S] d p [ S ] ,故需要通过以下容斥原理对 f f 做修正:

f[S]+=m[x][y]

f[S|2x]=m[x][y] f [ S | 2 x ] − = m [ x ] [ y ]

f[S|2y]=m[x][y] f [ S | 2 y ] − = m [ x ] [ y ]

f[S|2x|2y]+=m[x][y] f [ S | 2 x | 2 y ] + = m [ x ] [ y ]

之后则有 dp[S]=SSf[S] d p [ S ] = ∑ S ′ ⊂ S f [ S ′ ] ,高维前缀和即可求出 dp[S] d p [ S ] ,时间复杂度 O(nk+k2k) O ( n ⋅ k + k ⋅ 2 k )

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
int n,k,T,pre[25],t[25],m[25][25],dp[(1<<22)+5];
char s[200005];
int main()
{
    scanf("%d%d%d%s",&n,&k,&T,s);
    for(int i=0;i<k;i++)scanf("%d",&t[i]);
    for(int i=0;i<k;i++)
        for(int j=0;j<k;j++)
            scanf("%d",&m[i][j]);
    memset(pre,-1,sizeof(pre));
    int S=0;
    for(int i=0;i<k;i++)dp[1<<i]=t[i];
    for(int i=0;i<n;i++)
    {
        int y=s[i]-'A';
        S|=(1<<y);
        for(int x=0;x<k;x++)
            if(pre[x]>=0)
            {
                if(!((pre[x]>>x)&1)&&!((pre[x]>>y)&1)) 
                {
                    dp[pre[x]]+=m[x][y];
                    dp[pre[x]|(1<<x)]-=m[x][y];
                    dp[pre[x]|(1<<y)]-=m[x][y];
                    dp[pre[x]|(1<<x)|(1<<y)]+=m[x][y];
                }
                pre[x]|=(1<<y);
            }    
        pre[y]=0;
    }
    int K=1<<k;
    for(int i=0;i<k;i++)
        for(int j=0;j<K;j++)
            if((j>>i)&1)dp[j]+=dp[j^(1<<i)];
    int ans=0;
    for(int i=0;i<K;i++)
        if((i&S)==i&&dp[i]<=T&&i!=S)
            ans++;
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值