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

Description

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

Input

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

( 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 01 表示每种字母是否需要删除, 1 表示要删去该种字符

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

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

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

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

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

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

之后则有 d p [ S ] = S S f [ S ] ,高维前缀和即可求出 d p [ S ] ,时间复杂度 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;
}

猜你喜欢

转载自blog.csdn.net/v5zsq/article/details/81051401
76