CSP-S 2026 migrate 题解
场上被这个题击败,WC 梦断。赛时暴力都没拼明白,以下有效思考均在场外进行。
怎么没人讲怎么拼包。补一篇。
24pts,n \le 300
枚举起点
前缀和优化一下,注意到 DP 值可能很大,每次算完对
40pts,n \le 5000
考虑怎么刻画路径最短。
令
枚举起点
有了这个结论再 DP 就能做到
48pts,性质 A:等差数列
设公差为
这个时候我们进行一些微调,把终点往左移动一格,会发现多了一些每次跳的长度形如
正解
这个题做法很多,对着上面的 DP 继续优化似乎也能做;这里提一下顺着我考场思路的做法。
固定起点、终点
- 从
u 开始贪心地往后跳\text{nxt} ,能够到v 就停下来,设这条路径为a_1 \dots a_m ; - 从
v 开始贪心地往前跳\text{pre} ,能够到u 就停下来,设这条路径为b_1 \dots b_m 。
形式化地,有
-
a_1=b_1=u, a_m=b_m=v -
\forall i\in [1,m-1), a_{i+1}=\text{nxt}(a_i) -
\forall i\in (2,m], b_{i-1}=\text{pre}(b_i)
显然有
如果这两条路径实际是同一条,显然方案唯一。否则画出来可能长这个样子:
- 引理
1 :\forall i \in [2,m-1] ,a_i - b_i \le 1 。
证明:假设存在
- 引理
2 :a_i=b_i+1 的点对恰好有一个,其余满足a_i=b_i 。
证明:考虑一个长这样子的结构。
你发现这个结构至少出现一次时才能区分
我们已经证明了满足题目条件时,
接下来证明必要性:假设存在第三条路径,令其最早不与
以上分析了结构,考虑如何计数。
看上去枚举
-
b_{i+1}=v
需要满足
-
b_{i+1}=\text{nxt}(b_i)
后面会接上
左边也处理一个
这个做法想清楚了就没啥细节,只要注意一下往左往右是否没法跳就行了。
void solve(){
scanf("%d%lld",&n,&d);
F(i,1,n) scanf("%lld",&x[i]), f[i]=g[i]=cntp[i]=cntn[i]=0;
for(int i=1,j=1;i<=n;++i){
while(x[i]-x[j]>d) j++;
pre[i]=j;
if(j!=i) cntp[j]++;
}
for(int i=n,j=n;i;--i){
while(x[j]-x[i]>d) j--;
nxt[i]=j;
if(j!=i) cntn[j]++;
}
F(i,1,n){
if(nxt[i]!=i) f[nxt[i]]++;
if(pre[i]!=i) g[pre[i]]++;
}
F(i,1,n)
if(pre[i]!=i&&nxt[pre[i]]==i) f[i]+=f[pre[i]];
rf(i,n,1)
if(nxt[i]!=i&&pre[nxt[i]]==i) g[i]+=g[nxt[i]];
ll ans=0;
F(i,1,n-1){
ll R=cntp[i]-(pre[i+1]==i)+(pre[nxt[i+1]]==i)*g[nxt[i+1]];
ll L=cntn[i+1]-(nxt[i]==i+1)+(nxt[pre[i]]==i+1)*f[pre[i]];
ans+=L*R;
}
printf("%lld\n",ans);
}
https://www.luogu.com.cn/record/307770026。