[省选集训2022] 模拟赛6

A

题目描述

定义长度为 \(n\) 的好串 \(s\) 满足:

  • \(|s_i-s_{i-1}|=1,i\in[2,n]\)
  • \(s_i\geq\frac{s_{i+1}+s_{i-1}}{2},i\in[2,n-1]\)

给你长度为 \(n\) 的序列 \(a\)\(v\),分别表示原序列和价值序列。你每次可以选择一个原序列中的好串,将其删除之后剩下的串会前后拼接。设这次删除的长度是 \(l\),那么会得分 \(v_l\),问最大得分,不一定要把原序列删完。

\(n\leq 400,|v_i|\leq 10^5,a_i\leq10^9\)

解法

考虑求出 \(f[l][r]\) 表示把 \([l,r]\) 删完的最大得分,然后再用一维 \(dp\) 就可以拼出答案。

好串其实就是先以 \(1\) 的斜率上升、再以 \(1\) 的斜率下降的尖角形。发现还是不好做,我们可以考虑把它们拆成上升部分和下降部分,然后拼起来,拆分后的问题还是可以用 \(dp\) 解决。

具体来说我们设 \(up[l][r]\) 表示获取以 \(a_l\) 开头 \(a_r\) 结尾的上升段的最大价值,\(dn[l][r]\) 表示获取 \(a_l\) 开头 \(a_r\) 结尾的上升段的最大价值,那么转移枚举 \(i\) 使得 \(a_i+1=a_r/a_i-1=a_r\)

\[up[l][r]\leftarrow up[l][i]+f[i+1][r-1] \]

\[dn[l][r]\leftarrow dn[l][i]+f[i+1][r-1] \]

那么如何把他们拼起来呢?我们枚举最高点 \(i\) 使得 \(a_i\geq a_l\and a_i\geq a_r\),那么好串的长度一定是 \(2\cdot a_i-a_l-a_r\),很容易写出转移:

\[f[l][r]\leftarrow up[l][i]+dn[i][r]+v[2\cdot a_i-a_l-a_r+1] \]

但是 \(a_l,a_r\) 也可以不在一个好串中,这时候我们需要枚举分界点将他们分开:

\[f[l][r]\leftarrow f[l][i]+f[i+1][r] \]

此外只有上升段和下降段需要单独讨论一下,时间复杂度 \(O(n^3)\)

总结

设计多个 \(dp\) 状态,互相转移的方法是很值得思考的。其实我感觉它的原理还是分步思想,把一个较为复杂的问题拆分成若干个部分解决,这些部分中可能又蕴含子问题。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 405;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,a[M],v[M],dp[M],f[M][M],up[M][M],dn[M][M];
void upd(int &x,int y) {x=max(x,y);}
signed main()
{
	freopen("good.in","r",stdin);
	freopen("good.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++) v[i]=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int l=n;l>=1;l--)
	for(int r=l;r<=n;r++)
	{
		f[l][r]=up[l][r]=dn[l][r]=-inf;
		up[l][l]=dn[l][l]=0;
		for(int i=l;i<r;i++)
		{
			upd(f[l][r],f[l][i]+f[i+1][r]);
			if(a[i]+1==a[r])
				upd(up[l][r],up[l][i]+f[i+1][r-1]);
			if(a[i]-1==a[r])
				upd(dn[l][r],dn[l][i]+f[i+1][r-1]);
			if(a[i]>=a[l] && a[i]>=a[r]) upd(f[l][r],
				up[l][i]+dn[i][r]+v[2*a[i]-a[l]-a[r]+1]);
		}
		if(a[l]>=a[r])
			upd(f[l][r],dn[l][r]+v[a[l]-a[r]+1]);
		if(a[l]<=a[r])
			upd(f[l][r],up[l][r]+v[a[r]-a[l]+1]);
	}
	for(int i=1;i<=n;i++)
	{
		dp[i]=dp[i-1];
		for(int j=1;j<=n;j++)
			upd(dp[i],dp[j-1]+f[j][i]);
	}
	printf("%lld\n",dp[n]);
}

B

题目描述

给定一个 \(n\) 个点,\(m\) 条边的连通无向图,图有边权。每个点有一个颜色 有 \(q\) 次操作,每次操作可改变某一点颜色。每次操作后求图中不同颜色点的最短距离,保证图中点颜色至少存在两种。

\(n\leq 2\cdot 10^5,m\leq3\cdot 10^5\)

解法

有下列两点 \(\tt observations\)

  • 答案一定是原图的一条边。
  • 答案一定在原图的最小生成树上。

主要解释一下性质 \(2\),考虑一个环的最大边无论如何也不会产生贡献,所以对于所有环都可以去掉其最大边,那么留下来的一定是最小生成树。

那么在修改每个点的时候在父亲的数据结构上改一下即可,很容易 \(O(m\log n)\)

C

题目描述

给定 \(a\),求有多少长度为 \(n\) 的序列 \(s\) 满足下列条件,答案对 \(998244353\) 取模:

  • \(s_i\leq a_i\)
  • 序列不存在 \(\tt border\)

\(n\leq 10^6\)

解法

主要是没时间想了,大胆设 \(f_i\) 表示前 \(i\) 个点的序列的方案数。可以正难则反,枚举原序列的最短 \(\tt border\),那么这就是子问题:

\[f_i=\prod_{j=1}^i a_j-\sum_{j=1}^{i/2}f_j\prod_{k=j+1}^{i-j}a_k \]

\(a_i\) 的前缀积为 \(s_i\),那么转移可以化简为:

\[f_i=s_i-\sum_{j=1}^{i/2}f_j\cdot s_{i-j}\cdot s_j^{-1} \]

显然是一个分治 \(\tt NTT\) 的形式,但特殊的地方在于 \(j\leq i-j\) 才可以转移。但考虑我们的分治 \(\tt NTT\) 只要求递归下去之后可以得到这一段的 \(f\),也就是我们把贡献算明白就还是可以做的。

所以我们在递归完左半边的时候,把左半边的 \(f\) 和右半边的 \(s\) 卷起来贡献给整个序列即可,时间复杂度 \(O(n\log ^2n)\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000005;
const int MOD = 998244353;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,a[M],b[M],f[M];
int A[M<<2],B[M<<2],rev[M<<2];
void add(int &x,int y) {x=(x+y)%MOD;}
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1; 
	}
	return r;
}
void NTT(int *a,int len,int op)
{
	for(int i=0;i<len;i++)
	{
		rev[i]=(rev[i>>1]>>1)|((i&1)*(len/2));
		if(i<rev[i]) swap(a[i],a[rev[i]]); 
	}
	for(int s=2;s<=len;s<<=1)
	{
		int w=(op==1)?qkpow(3,(MOD-1)/s):
			qkpow(3,MOD-1-(MOD-1)/s);
		for(int i=0,t=s/2;i<len;i+=s)
			for(int j=0,x=1;j<t;j++,x=x*w%MOD)
			{
				int fe=a[i+j],fo=a[i+j+t];
				a[i+j]=(fe+x*fo)%MOD;
				a[i+j+t]=((fe-x*fo)+MOD)%MOD;
			}
	}
	if(op==1) return ;
	int inv=qkpow(len,MOD-2);
	for(int i=0;i<len;i++)
		a[i]=a[i]*inv%MOD;
}
void solve(int l,int r)
{
	if(l==r)
	{
		f[l]=(a[l]-f[l]+MOD)%MOD;;
		if(2*l<=n) add(f[l<<1],f[l]);
		return ;
	}
	int mid=(l+r)>>1;solve(l,mid);
	if(l+mid-1<=n)
	{
		int len=1;while(len<=r-l+1) len<<=1;
		for(int i=0;i<len;i++) A[i]=B[i]=0;
		for(int i=l;i<=mid;i++)
			A[i-l]=f[i]*b[i]%MOD;
		for(int i=mid+1;i<=r;i++)
			B[i-mid-1]=a[i];
		NTT(A,len,1);NTT(B,len,1);
		for(int i=0;i<len;i++)
			A[i]=A[i]*B[i]%MOD;
		NTT(A,len,-1);
		for(int i=0;i<len;i++)
		{
			if(i+l+mid+1>n) break;
			add(f[i+l+mid+1],A[i]);
		}
	}
	solve(mid+1,r);
}
signed main()
{
	freopen("music.in","r",stdin);
	freopen("music.out","w",stdout);
	n=read();a[0]=b[0]=1;
	for(int i=1;i<=n;i++)
	{
		int x=read();
		a[i]=a[i-1]*x%MOD;
		b[i]=qkpow(a[i],MOD-2);
	}
	solve(1,n);
	printf("%lld\n",f[n]);
}
posted @ 2022-02-12 22:09  C202044zxy  阅读(213)  评论(2编辑  收藏  举报