[省选集训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\):
那么如何把他们拼起来呢?我们枚举最高点 \(i\) 使得 \(a_i\geq a_l\and a_i\geq a_r\),那么好串的长度一定是 \(2\cdot a_i-a_l-a_r\),很容易写出转移:
但是 \(a_l,a_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\),那么这就是子问题:
设 \(a_i\) 的前缀积为 \(s_i\),那么转移可以化简为:
显然是一个分治 \(\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]);
}