KEYENCE Programming Contest 2021 (AtCoder Beginner Contest 227) E. Swap(线性dp 交换)

题目

串S仅由KEY三种字符组成(|S|<=30),

最多进行K(0<=K<=1e9)次相邻字符交换,

问能形成的本质不同的串的方案数是多少

思路来源

官方题解

题解

dp[i][e][y][k]表示前i个字母有e个E、y个Y最少交换k次的字符串的方案数

此时也就固定下来有i-e-y个K了

转移枚举当前字符填什么

当前字符填什么,由于知道了历史每个字符都用了几个,

在原串中肯定是优先把靠前的字符换到前面来,

所以,当前字符用什么,在原串里的位置一定唯一,交换次数也能唯一确定

比如,之前已经用过e个E,y个Y,i-e-y个K,

假设当前第i个字符填E,一定用的是串中第e+1个E,

且需要在交换的时候绕过第e+1个E前面的其他字符,

也就是说,如果第e+1个E前面,Y的字符超过y个,那么剩下的Y都需要被绕过,K同理

由于逆序对(可以等价看成012三种数字)的数量,

所以当可交换次数超过n*(n-1)/2时,就等价于可重集全排列的方案数,

注意极限情况分子是30的阶乘,也就是1e32会爆ll,需要开__int128

可交换次数不超过n^2时,dp状态是O(n^5)的,转移枚举三种字符,每种算增量代价是O(n)的,

整体复杂度O(n^6),但由于有一些非法状态,并且会除掉一些系数,

所以暴力枚举也是可以通过的

代码

#include <bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
const int N=32,K=N*N;
char s[N];
int n,k,swp;
__int128 fac[N];
ll dp[N][N][N][K];
//dp[i][e][y][l]表示前i个字母有e个E,y个Y(i-e-y个K)最少交换l次的字符串的方案数
map<char,int>cnt;
void add(ll &x,ll y){
    x+=y;
}
int main(){
    scanf("%s%d",s+1,&swp);
    n=strlen(s+1);
    fac[0]=1;
    rep(i,1,n){
        fac[i]=1ll*fac[i-1]*i;
        cnt[s[i]]++;
    }
    if(swp>=n*(n-1)){
        printf("%lld\n",fac[n]/fac[cnt['K']]/fac[cnt['E']]/fac[cnt['Y']]);
        return 0;
    }
    dp[0][0][0][0]=1;
    rep(i,0,n-1){
        int ue=min(cnt['E'],i);
        rep(e,0,ue){
            int uy=min(cnt['Y'],i-e);
            rep(y,0,uy){
                int k=i-e-y;
                rep(l,0,swp){
                    if(!dp[i][e][y][l])continue;
                    for(auto x:{'K','E','Y'}){
                        map<char,int>mp;
                        mp['K']=k;mp['E']=e;mp['Y']=y;
                        int cnt=0;
                        rep(j,1,n){
                            if(mp[s[j]]){//被换到前面已经用过的字符
                                mp[s[j]]--;
                            }
                            else{
                                if(s[j]==x)break;//想要的字符
                                cnt++;//在当前想要的字符前,换途中必经的字符
                            }
                        }
                        if(l+cnt<=swp){
                            if(x=='K')add(dp[i+1][e][y][l+cnt],dp[i][e][y][l]);
                            if(x=='E')add(dp[i+1][e+1][y][l+cnt],dp[i][e][y][l]);
                            if(x=='Y')add(dp[i+1][e][y+1][l+cnt],dp[i][e][y][l]);
                        }
                    }
                }
            }
        }
    }
    ll ans=0;
    rep(i,0,swp){
        ans+=dp[n][cnt['E']][cnt['Y']][i];
    }
    printf("%lld\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值