Educational Codeforces Round 62 E. Palindrome-less Arrays

本文探讨了一道算法题,要求统计将数组中所有-1用1至k的数替换后,不形成奇数长度回文子串的方法数量。文章通过细致的分类讨论和动态规划方法,提供了一个高效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述Examples
input

2 3
-1 -1

output

9

input

5 2
1 -1 -1 1 2

output

0

input

4 200000
-1 -1 12345 -1

output

735945883

题意
给出一个长为nnn的数组,数组中的每个元素要么是111$k$要么是$-1$,任务是统计将所有的$-1$用$1$kkk的数填入之后不产生长为奇数的回文串的填法之和 mod 998244353mod~998244353mod 998244353的值。
输入第一行为nnnkkk,第二行为数组。

思路
这题我当时没做出来,甚至一点头绪都没有
注意到题目只限制了长为奇数的回文串不得出现,而长为5的回文串只能由长为3的回文串两边各添加一个相同元素得到,故只要不出现长为3的回文串即可。
不出现长为3的回文串说明对于任意iiia[i−2]!=a[i]a[i-2]!=a[i]a[i2]!=a[i]a[i]!=a[i+2]a[i]!=a[i+2]a[i]!=a[i+2](如果存在)。
这样就可以把a[]a[]a[]分成a[i]a[i]a[i](i为奇数)和a[i]a[i]a[i](i为偶数)两个数列讨论,要求则变为数列中任两个相邻元素不得相同。

  • (然后疯狂分类讨论)

考虑每个连续的全为−1-11的区间,有如下几种情况:

  • 这个区间两边都没有已填进的数字,显然这个区间的填法总数为k∗(k−1)k*(k-1)k(k1)(区间长度-1)
  • 这个区间左边有已填进的数字,右边没有已填进的数字。这时左起第一位有(k−1)(k-1)(k1)种填法,在填入该位后,后一位同样有(k−1)(k-1)(k1)种填法,以此类推……总填法数为(k−1)(k-1)(k1)(区间长度)
  • 这个区间右边有已填进的数字,左边没有已填进的数字。这时右起第一位有(k−1)(k-1)(k1)种填法,在填入该位后,前一位同样有(k−1)(k-1)(k1)种填法,以此类推……总填法数为(k−1)(k-1)(k1)(区间长度)
  • 这个区间左边及右边都有已填进的数字,但两边已填进的数字相同。这时我们可以设dp[i][0]dp[i][0]dp[i][0]为第iii位填入的数字与左右两边已填进的数字不同时填法总数,dp[i][1]dp[i][1]dp[i][1]为第iii位填入的数字与左右两边已填进的数字相同时填法总数。可得动态转移方程dp[i][0]=(dp[i−1][0]∗(max(k−2,0))+dp[i−1][1]∗max(k−1,0))dp[i][0]=(dp[i-1][0]*(max(k-2,0))+dp[i-1][1]*max(k-1,0))dp[i][0]=(dp[i1][0](max(k2,0))+dp[i1][1]max(k1,0))
    dp[i][1]=(dp[i−1][0])dp[i][1]=(dp[i-1][0])dp[i][1]=(dp[i1][0])
    显然dp[1][0]=k−1dp[1][0]=k-1dp[1][0]=k1dp[1][1]=0dp[1][1]=0dp[1][1]=0。填法总数为dp[区间长度][0]dp[区间长度][0]dp[][0]
  • 这个区间左边及右边都有已填进的数字,且两边已填进的数字不同。这时我们可以设dp[i][0]dp[i][0]dp[i][0]为第iii位填入的数字与左右两边已填进的数字均不同时填法总数,dp[i][1]dp[i][1]dp[i][1]为第iii位填入的数字与左边已填进的数字相同时填法总数。dp[i][2]dp[i][2]dp[i][2]为第iii位填入的数字与右边已填进的数字相同时填法总数。可得动态转移方程dp[i][0]=(dp[i−1][0]∗(max(k−3,0))+dp[i−1][1]∗max(k−2,0)+dp[i−1][2]∗max(k−2,0))dp[i][0]=(dp[i-1][0]*(max(k-3,0))+dp[i-1][1]*max(k-2,0)+dp[i-1][2]*max(k-2,0))dp[i][0]=(dp[i1][0](max(k3,0))+dp[i1][1]max(k2,0)+dp[i1][2]max(k2,0))dp[i][1]=(dp[i−1][0]+dp[i−1][2])dp[i][1]=(dp[i-1][0]+dp[i-1][2])dp[i][1]=(dp[i1][0]+dp[i1][2])dp[i][2]=(dp[i−1][0]+dp[i−1][1])dp[i][2]=(dp[i-1][0]+dp[i-1][1])dp[i][2]=(dp[i1][0]+dp[i1][1])
    显然dp[1][0]=k−2dp[1][0]=k-2dp[1][0]=k2dp[1][1]=0dp[1][1]=0dp[1][1]=0dp[1][2]=1dp[1][2]=1dp[1][2]=1。填法总数为dp[区间长度][0]+dp[区间长度][1]dp[区间长度][0]+dp[区间长度][1]dp[][0]+dp[][1]
  • 最后还有一个坑,有可能拆分成两个数列后,数列中已经有相邻的相同且不为−1-11的元素,这种情况需要特判。

下面AC代码:

#include<stdio.h>
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
const long long MOD=998244353;
long long a[2][200005];
long long dp[200005][3];
long long n,k,ans=1;
long long right(long long l,long long r){if(l>r)return 1;long long ans=k-1;while(r>l){r--;ans*=(k-1);ans%=MOD;}return ans;}
long long left(long long l,long long r){if(l>r)return 1;long long ans=k-1;while(r>l){l++;ans*=(k-1);ans%=MOD;}return ans;}
long long full(long long l,long long r){if(l>r)return 1;long long ans=k;while(r>l){l++;ans*=(k-1);ans%=MOD;}return ans;}
long long m(long long l,long long r,long long a[])
{
	if(l==r-1)if (a[l]!=a[r])return 1;else return 0;
	if(a[l]!=a[r])
	{
		dp[l+1][0]=k-2;//不与两边相同 
		dp[l+1][1]=0;//与左边相同 
		dp[l+1][2]=1;//与右边相同 
		for(long long i=l+2;i<r;i++)
		{
			dp[i][0]=(dp[i-1][0]*(max(k-3,0))+dp[i-1][1]*max(k-2,0)+dp[i-1][2]*max(k-2,0))%MOD;
			dp[i][1]=(dp[i-1][0]+dp[i-1][2])%MOD;
			dp[i][2]=(dp[i-1][0]+dp[i-1][1])%MOD;
		}
		return(dp[r-1][0]+dp[r-1][1])%MOD;
	}else
	{
		dp[l+1][0]=k-1;//不与两边相同 
		dp[l+1][1]=0;//与左边相同 
		for(long long i=l+2;i<r;i++)
		{
			dp[i][0]=(dp[i-1][0]*(max(k-2,0))+dp[i-1][1]*max(k-1,0))%MOD;
			dp[i][1]=(dp[i-1][0])%MOD;
		}
		return dp[r-1][0]%MOD;
	}
}
int main()
{
	scanf("%lld%lld",&n,&k);
	for(long long i=1;i<=n;i++) scanf("%lld",&a[i%2][(i-1)/2+1]);
	long long f1=n/2+n%2,f2=n/2;
	long long i,j;
	for(i=1;i<=f1;)
	{
		for(j=i+1;j<=f1&&a[1][j]==-1;j++);
		if(j>f1)
		{
			if(a[1][i]==-1)
			{
				ans*=full(1,f1);
				ans%=MOD;
			}else
			{
				ans*=left(i+1,j-1);
				ans%=MOD;
			}
		}
		else 
		{
			if(a[1][i]==-1)
			{
				ans*=right(i,j-1);
				ans%=MOD;
			}else
			{
				ans*=m(i,j,a[1]);
				ans%=MOD;
			}
		}
		i=j;
	}
	for(i=1;i<=f2;)
	{
		for(j=i+1;j<=f2&&a[0][j]==-1;j++);
		if(j>f2)
		{
			if(a[0][i]==-1)
			{
				ans*=full(1,f2);
				ans%=MOD;
			}else
			{
				ans*=left(i+1,j-1);
				ans%=MOD;
			}
		}
		else 
		{
			if(a[0][i]==-1)
			{
				ans*=right(i,j-1);
				ans%=MOD;
			}else
			{
				ans*=m(i,j,a[0]);
				ans%=MOD;
			}
		}
		i=j;
	}
	printf("%lld",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值