cf1140E 回文串+染色方案dp

有点硬核的dp。。要用到一个结论。。

/*
把原串拆成奇偶串,再拆成极大连续的-1串:该串两端都是非-1数,中间都是-1,并且下标要么都是偶数,要么都是技术
然后对所有这些串进行dp,dp[i][0]表示到第i个-1的方案数,0表示第i个-1填的数和该串最右端的数不同,1表示相同

为什么这样是可行的?
    一个重要结论:拆分成奇偶串就可以使不出现回文串的条件转化为相邻两个字符不相等
而相邻两个字符不相等的填数方案可以用dp来做,并且每一段不连续的-1段都是满足乘法原理的,段内就是递推
有个经典套路: dp[i][0]表示到第i个-1的方案数,0表示第i个-1填的数和该串最右端的数不同,1表示相同
一些细节:-1段可能在段首出现,也可能在段尾出现,有可能出现a[-1]的情况,所以额外开个空数组 
*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define maxn 200005
#define mod 998244353
int n,k,use1[maxn],a[maxn];
ll use2[maxn],f[maxn][2],ans;

ll calc(int a,int b,int len){//段左端元素,右端元素,-1段的长度
     if(len==0){//如果-1段长为0,特判一下 
         if(a==b && a!=0 && b!=0)return 0;
         return 1;
    }
    if(b==0){
        ll tmp=1;
        if(a==0)tmp=k;
        else tmp=k-1;
        for(int i=1;i<=len-1;i++)tmp=tmp*(k-1)%mod;
        return tmp;
    }
    for(int i=0;i<=len;i++)f[i][0]=f[i][1]=0;
    if(a==0){//-1在段首出现的情况 
        f[0][0]=k-1,f[0][1]=1;
        len--;
    }
    else if(a==b)f[0][1]=1;//处理一下初始状态 
    else f[0][0]=1;
    for(int i=1;i<=len;i++){
        f[i][0]=(f[i][0]+f[i-1][0]*(k-2)+f[i-1][1]*(k-1))%mod;
        f[i][1]=f[i-1][0];
    } 
    
    return f[len][0];
} 
int main(){
    cin>>n>>k;ans=1;
    for(int i=1;i<=n;i++)cin>>a[i];
    
    int last=-1;
    for(int i=1;i<=n+2;i+=2)
        if(a[i]!=-1)
            ans=ans*calc(a[last],a[i],(i-last)/2-1)%mod,last=i;
    last=0;
    for(int i=2;i<=n+2;i+=2)
        if(a[i]!=-1)
            ans=ans*calc(a[last],a[i],(i-last)/2-1)%mod,last=i;
    cout<<ans; 
}

猜你喜欢

转载自www.cnblogs.com/zsben991126/p/10583793.html