题解:P16433 [APIO 2026 中国赛区] 上升
感觉就是,不要一上来就套你所熟知的各种技巧,包括不限于:容斥,提前计算贡献,拆式子等。
先考虑
对于一般的情况,主要问题在于:只记录相对大小关系是不够的!
如上图所示,设红色为,相对顺序中已经确定值的元素。我们是从左到右扫描的,因此左边的这个矩形中,这些元素某种程度上被限制住了:之后我们想把新的数插入到左边的这个矩形中,就有了一定的限制:相邻两个红球中的黑球个数必须是给定值。
直接记录相邻两个关键数中已经插入了多少个数是不现实的。但是,我们可以把这些数放回数轴上。记
直接记录
这样状态数看起来就已经是
然后考虑转移
- 如果下一个数未确定,那么可以根据它和上一个数的相对大小关系(以及
\max_{1 \le j \le i} p_j 的相对大小关系)进行分类讨论; - 如果下一个数确定了,这时候涉及到最大值的扩展。
如上图所示,假设最大值
v_1 \to v_2 ,我们可以将(k \le v_2-v_1-1) 个> v_1 的数转化为<v_2 ,有\binom{v_2-v_1-1}{k} 种方法。根据k ,也可以确定它和上一个数的相对大小关系来计算系数。
对于第一种情况,我们发现:给定
在滚动数组的加持下,可以做到
可读性(也许)比较高的代码
#include<bits/stdc++.h>
#define ll long long
#define ffor(i,a,b) for(int i=(a);i<=(b);i++)
#define roff(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int MAXN=500+10;
int n,MOD,p[MAXN],w[MAXN],C[MAXN][MAXN];
//left-right 个数保持一定
ll dp[2][MAXN][MAXN];
struct Mod
{
ll m, p;
inline void init(const int pp) { m = ((__int128)1 << 64) / pp; p = pp; }
inline ll operator ()(const ll x)
{
return x - ((__int128(x) * m) >> 64) * p;
}
} mod;
inline void Add(const int st,const int left,const int l,const int r,const int v) {
if(left<0||l>r||!v) return ;
dp[st][left][l]+=v,dp[st][left][r+1]+=MOD-v;
return ;
}
inline int Solve(void) {
mod.init(MOD);
memset(dp,0,sizeof(dp));
C[0][0]=1; ffor(i,1,n) {C[i][0]=1;ffor(j,1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;}
int delta=0,lstv=0; dp[0][0][1]=1,w[0]=1;
ffor(i,1,n) {
int st=(i&1),lst=st^1;
ffor(c,0,n) ffor(t,0,n+1) dp[st][c][t]=0;
if(p[i]) {
//直接往右边推进,反而简单一些。选择右边的前 k 个放到左边去,注意更新 t'
//如果 t<=left+1+k 那么会新增.
ffor(c,0,n) ffor(t,1,n+1) {
dp[lst][c][t]+=dp[lst][c][t-1];
if(dp[lst][c][t]>=MOD) dp[lst][c][t]-=MOD;
}
ffor(c,0,n) {
int left=c,right=left-delta,len=p[i]-lstv-1;
if(right<0||left+right>n) continue ;
ffor(k,0,min(len,right)) {
ll add=mod(dp[lst][c][left+1+k]*(w[i-1]-1)+dp[lst][c][n+1]+MOD);
if(add) dp[st][left+len-k][left+len-k+1]+=mod(C[len][k]*add);
}
}
ffor(c,0,n) ffor(t,c+1,c+1) {
dp[st][c][t]=mod(dp[st][c][t]);
if(dp[st][c][t]>=MOD) dp[st][c][t]-=MOD;
}
delta+=p[i]-lstv-1,lstv=p[i];
}
else {
ffor(c,0,n) ffor(t,1,n+1) if(dp[lst][c][t]) {
int left=c,right=left-delta;
if(right<0||left+right>n) continue ;
ll v=dp[lst][c][t];
if(t<=left+1) {
if(i-1==0||p[i-1]) { //上一个数是确定的
//case1 更小:left 减少,t'=1~left
Add(st,left-1,1,left,v);
//case2 更大:left 不变,t'=left+2~left+right+2
Add(st,left,left+2,left+right+2,mod(v*w[i-1]));
}
else {
//case 1 更小:left 减少,t'=1~t-1
Add(st,left-1,1,t-1,v);
//case 2.1 更大:left 减少,t'=t~left
ll mzx;
Add(st,left-1,t,left,mzx=mod(v*w[i-1]));
//case 2.2 更大:left 不变,t'=left+2~left+right+2
Add(st,left,left+2,left+right+2,mzx);
}
}
else {
//case 1 更大:left 不变,t'= t+1~left+right+2
Add(st,left,t+1,left+right+2,mod(v*w[i-1]));
//case 2.1 更小:left 不变,t'=left+2~t
Add(st,left,left+2,t,v);
//case 2.2 更小:left 减小,t'=1~left
Add(st,left-1,1,left,v);
}
}
ffor(c,0,n) ffor(t,1,n+1) {
if(dp[st][c][t]) {
dp[st][c][t]=mod(dp[st][c][t]+dp[st][c][t-1]);
if(dp[st][c][t]>=MOD) dp[st][c][t]-=MOD;
}
else dp[st][c][t]=dp[st][c][t-1];
}
delta--;
}
}
int res=0;
ffor(p,0,n+1) res=(res+dp[n&1][0][p])%MOD;
return (res%MOD+MOD)%MOD;
}
int ascend(int C,int N,int M,vector<int> P,vector<int> W) {
n=N,MOD=M;
ffor(i,1,n) p[i]=P[i-1];
ffor(i,1,n-1) w[i]=W[i-1];
return Solve();
}
虽然这篇题解写得很长,但是核心思路很简单:先考虑最朴素的插入 DP,然后通过简单修正处理“存在定值”的情况,再经观察降低状态数,最后用前缀和优化转移。
个人感觉绝对难度并不比 CSP25T4 高多少,问题可能出在这是 APIO。