• [省选集训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]);
    }
    
  • 相关阅读:
    HDOJ1301 Jungle Roads[最小生成树Prim()]
    HDOJ1166 敌兵布阵[线段树求和]||HDOJ1754 I Hate It[线段树求最大值]
    HDOJ2159 FATE[DP背包问题]
    ZOJ3175 Number of Containers[数学题]
    POJ1791 Parallelogram Counting[数学题平行四边形求个数]
    HDOJ1281 棋盘游戏[匈牙利(最大匹配)+枚举]
    HDOJ1045 Fire Net[二分图匈牙利算法]
    POJ2449 Remmarguts' Date[K短路入门题(Dijkstra()||Spfa()+A*)]
    Fibonacci序列
    HDOJ1568 Fibonacci[公式求前四位数]
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15887623.html
Copyright © 2020-2023  润新知