DP 专训

因为一些原因前面部分先咕着

string Counting

题意

    26 种不同的字符,第 i 种有 ci 个。构造一个长为 n 的字符串,使得字符串上不存在长度为奇数且大于 1 的回文串的方案数。
     3n400,n3<cin.

题解

     实质就是第 i 位不与 i+2 位相同。也就是分成奇数和偶数位,前 n2+1 位相邻的不同,后面的相邻的不同。

     注意到如果不存在限制的话,那其实很容易做。由上面的转化可知答案为 262×25n2.

     然后呢?然后假设我们注意到奇异的 n3<cin 的限制,那就会发现最多有两个字符是超限的,所以直接考虑容斥做。

     所以我们要求出至少一个字符超限的方案数和至少两个字符超限的方案数,先考虑前者:

     fi,j,k 表示前 i 位,共用了 j 个钦定字符,且当前用的是否为钦定字符。

     那么有转移:

dpi,j,0={dpi1,j,0×24+dpi1,j,1×25    in2+1dpi1,j,0×25+dpi1,j,1×25    i=n2+1

dpi,j,1={dpi1,j,0    in2+1dpi1,j,0+dpi1,j,1    i=n2+1

     求完过后答案就是 i=126j=ci+1ndpi,j,0+dpi,j,1

     同理可以定义另一个 dpfi,j,k,l 表示两种字符分别用了 jk 次,且当前位不是钦定字符,钦定字符1,钦定字符2。转移与上面类似,答案也类似,但注意求的时候要考虑两个钦定的字符不能重复。(对有傻子没有考虑。)

代码

查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
const int MOD=998244353;
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
	int ret=1;
	while(b)
	{
		if(b&1) ret=Mul(ret,a);
		b>>=1;a=Mul(a,a);
	}
	return ret;
} 
int f[405][405][2],g[405][405][405][3],Lim[405]; 
int main() {
	int n;read(n);
	for(int i=1;i<=26;i++) read(Lim[i]);
	int Ans1=Mul(qpow(26,2),qpow(25,n-2)),Ans2=0,Ans3=0;
	f[1][1][1]=1;f[1][0][0]=25;
	for(int i=2;i<=n;i++)
	{
		for(int j=0;j<=n;j++)
		{
			if(i!=n/2+1)
			{
				if(j) f[i][j][1]=f[i-1][j-1][0];
				f[i][j][0]=Add(Mul(24,f[i-1][j][0]),Mul(25,f[i-1][j][1]));
			}
			else
			{
				if(j) f[i][j][1]=Add(f[i-1][j-1][0],f[i-1][j-1][1]);
				f[i][j][0]=Add(Mul(f[i-1][j][0],25),Mul(f[i-1][j][1],25));	
			}
		}
	}
	for(int i=1;i<=26;i++) for(int j=Lim[i]+1;j<=n;j++) Ans2=Add(Ans2,Add(f[n][j][1],f[n][j][0]));
	g[1][0][0][0]=24; g[1][1][0][1]=g[1][0][1][2]=1;
	for(int i=2;i<=n;i++)
	{
		for(int j=0;j<=i;j++)
		{
			for(int k=0;j+k<=i;k++)
			{
				if(i!=n/2+1)
				{
					if(j) g[i][j][k][1]=Add(g[i-1][j-1][k][0],g[i-1][j-1][k][2]);
					if(k) g[i][j][k][2]=Add(g[i-1][j][k-1][0],g[i-1][j][k-1][1]);
					g[i][j][k][0]=Add(Mul(g[i-1][j][k][0],23),Add(Mul(g[i-1][j][k][1],24),Mul(g[i-1][j][k][2],24)));
				}
				else
				{
					if(j) g[i][j][k][1]=Add(g[i-1][j-1][k][0],Add(g[i-1][j-1][k][1],g[i-1][j-1][k][2]));
					if(k) g[i][j][k][2]=Add(g[i-1][j][k-1][0],Add(g[i-1][j][k-1][1],g[i-1][j][k-1][2]));
					g[i][j][k][0]=Add(Mul(g[i-1][j][k][0],24),Add(Mul(g[i-1][j][k][1],24),Mul(g[i-1][j][k][2],24)));
				}
			}
		}
	}
	for(int i=1;i<=26;i++)
		for(int j=i+1;j<=26;j++)
			for(int k=Lim[i]+1;k<=n;k++)
				for(int l=Lim[j]+1;k+l<=n;l++) Ans3=Add(Ans3,Add(g[n][k][l][0],Add(g[n][k][l][1],g[n][k][l][2])));
			
	printf("%d",Add(Dec(Ans1,Ans2),Ans3));
	return 0;
}
/*
不会吧?26^3 * n 先完成没有限制的做法
甚至好像可以组合数学 26^2*25^{n-2} 
注意到会超限的字符不多,应该是两个以内? 
容斥掉就好了?
对于一个超限的情况:
	记 dp_{i,j,k} 表示前 i 个字符,一共用了 j 个超限字符,并且是/不是超限字符
	dp_{i,j,1}=dp_{i-1,j-1,0} / dp_{i-1,j-1,0}+dp_{i-1,j-1,1} (i=n/2+1时)
	dp_{i,j,0}=24*dp_{i-1,j,0}+25*dp_{i-1,j,1} / 25*dp_{i-1,j,0}+25*dp_{i-1,j,1} 
	(我们认为是前一半相邻不能相同,后一半相邻不能相同)
对于两个超限的情况:
	记 dp_{i,j,k,l,m} 表示`···j个超限字符 1,k个超限字符2,l表示上一位是普通/字符1/字符2的情况 
	转移相似 
*/

Escape Through Leaf

题意

     给一棵根为 1 的树,第 i 个点上有两个值 aibi。允许从一个子树的根节点 u 跳到子树内任何一个非自身的v,代价为 au×bv,总的代价就是每次跳跃的代价和,求从每个点跳到一个叶子的最小代价和。
     1n105,105ai,bi105

题解

     首先读对题,不要读成跳到儿子然后认为这是sb换根dp

     首先我们有一个简单的 dp:记 dpi 表示第 i 个节点的答案,则 dpu=dpv+au×bv    vsubtree(u)。可以 O(n2) 求答案。

     然后来考虑一下 dp 怎么优化,注意到这个dp转移是一个一次函数的形式,所以把 dpv 视作常数项,au 视作 xbv 视作斜率,那不就是用李超数维护最小值吗。

     然后在树上怎么做?由于我不会李超树的线段树合并,所以这里选用 dsu on tree,多加一个 logn 对这题影响不大。

     至于说线段树合并为什么是 nlogn 的复杂度证明我不会,并且也没写,但应该跟普通线段树合并大差不离。

     然后还有一个问题是,我写的代码没过CF (尴尬)

Beautiful Bracket Sequence

题意

     一个长为 n 的字符串,包含 (,),?,每个 ? 可替换为 () ,定义一个括号序列的权值为其最深的匹配深度(空串深度为 0,形如 (s) 的串深度为 deps+1,st的权值为两者的较大值)。求所有替换方案的权值和。对 998244353 取模。
1n106,Easy Version:1n2000.

题解

     Easy Version 是 有用 没用 有用的。

     如果Easy Version 像我一样用dp去想那就没用,但如果在dp专题里想组合数学(?)那就有用。

     怎么求权值?注意到,如果我们把一个已知的括号序列分成两部分,左边的左括号数为 li,右边的右括号数为 ri ,那么这个划分的答案就是 min(li,ri),感觉很显然。所以可以枚举划分点求出所有情况的 max

     那加入了 ? 怎么做呢?枚举现在的划分点和答案,但是 min 不好处理?

     发现 l 是递增的,r 是递减的,所以取得 max 时一定有 li=ri 的情况,所以认为两边都是答案是合理的。

     记当前分界点左边有 s1(s2?,右边有 s3)s4?

     那答案就是:

i=1nj=0nj×(s2js1)×(s4js3)

     这样我们就完成了一个 Easy Version.

     然后优化这个式子?组合意义天地灭!

    j 换成 s1s1+j,然后拆开成 s1js1

     对于拆出 s1 的情况:

i=1nj=0ns1×(s2js1)×(s4js3)=i=1nj=0ns1×(s2js1)×(s4s4j+s3)

     注意到后面两个数的组合意义连同求和表示从 s2+s4 个物品中选出 s4+s3s1 个,那也是直接组合数 (s2+s4s4+s3s1) 上去就好了.

     对于拆出 js1 的情况:

i=1nj=0n(js1)×(s2js1)×(s4js3)=i=1nj=0n(js1)×s2!(js1)!(s2j+s1)!×(s4js3)=i=1nj=0ns2!(js11)!(s2j+s1)!×(s4s2s3)=i=1nj=0ns2×(s21)!(js11)!(s2j+s1)!×(s4js3)=i=1nj=0ns2×(s21js11)×(s4s4j+s3)

     那还是用上面的套路,把后面部分换成 (s2+s41s4+s3s11) 即可。

     然后两个部分换成组合数后就 O(n) 做即可。

代码

查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
const int MOD=998244353;
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
	int ret=1;
	while(b)
	{
		if(b&1) ret=Mul(ret,a);
		b>>=1;a=Mul(a,a);
	}
	return ret;
}
char S[2000005];
int Fac[2000005],Inv[2000005];
int PreL[2000005],PreR[2000005],PreQ[2000005];
inline int C(int n,int r){return n>=r?Mul(Fac[n],Mul(Inv[r],Inv[n-r])):0;}
int main() {
	Fac[0]=1;
	for(int i=1;i<=2000000;i++) Fac[i]=Mul(Fac[i-1],i);
	Inv[2000000]=qpow(Fac[2000000],MOD-2);
	for(int i=2000000-1;i>=0;i--) Inv[i]=Mul(Inv[i+1],i+1); 
	int n;
	scanf("%s",S+1); n=strlen(S+1); 
	for(int i=1;i<=n;i++)
	{
		PreL[i]=PreL[i-1]+(S[i]=='(');
		PreR[i]=PreR[i-1]+(S[i]==')');
		PreQ[i]=PreQ[i-1]+(S[i]=='?');
	}
	int Ans=0;
	for(int i=0;i<=n;i++)
	{
		int L=PreL[i],R=PreR[n]-PreR[i],Q1=PreQ[i],Q2=PreQ[n]-PreQ[i];
		Ans=Add(Ans,Mul(L,C(Q1+Q2,Q2+R-L)));
		Ans=Add(Ans,Mul(Q1,C(Q1+Q2-1,R+Q2-L-1)));
	}
	printf("%d",Ans);
	return 0;
}
/*
DP 专题,但是组合数学 
*/

posted @   Azazеl  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示