专栏文章
题解:P15361 「WYZOI R2」拜年
P15361题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 2 份
- 快照标识符
- @mlro5hb6
- 此快照首次捕获于
- 2026/02/18 14:46 20 小时前
- 此快照最后确认于
- 2026/02/18 14:46 20 小时前
原题:P15361
算法分析
考虑枚举 。
需要在枚举 时确定 的下限。
设定一个函数 :
设定一个函数 :
若不存在从 到 的路径,能使得路径上的每个点 都满足 ,则 (代码中用 表示)。
考虑所有满足上述条件的路径,对于每条路径 ,令 ,则 。
由于往左走对 函数的值没有影响,故只考虑上下右转移,符合无后效性,可以使用 DP 实现 :
状态设计
设 表示从起点 出发,只经过权值大于 的格子,到达 的所有路径中,路径上经过的格子权值的最大值的最小可能值。若不可达,则 。
设 表示从起点 出发,只经过权值大于 的格子,可否到达 ,可以则为 ,不能则为 (这是为方便转移准备的,不然在转移时分不清究竟是 还是没有路径导致的不可达。所以在转移到当前行时,)。初始化
将 设为可达(,设计为从 开始可以简化第一列的转移)。DP 过程
枚举 :
- 根据 是否合法设定 的值()。
- 根据 是否合法设定 的值()。
- 将 和 均设为不可达()。
- 水平转移:
- 第一行:如果 可达且 合法(),则转移 。
- 第二行:如果 可达且 合法(),则转移 。
- 若 和 均合法(),垂直转移:
- 若 和 均可达(),上下相互转移:
- 若 ,从上往下转移:。
- 若 ,从下往下转移:。
- 若 可达, 不可达(),从上往下转移:
- 。
- 若 不可达, 可达(),从下往上转移:
- 。
- 根据 是否可达设定 的值()。
- 根据 是否可达设定 的值()。
最后返回 。
最朴素的暴力做法即为:
从 到 枚举 :
- 如果 ,。
- 否则,显然再加大限制也不可能到达了,直接退出。
但是暴力做法会 TLE。考虑优化:
考虑一种情况:。
在这种情况下,由于 的限制(从只能走 到只能走 , 不能走了)对从 走到 没有影响,。所以,在这种情况下,计算 是不必要的。
因此,只需在输入时将每个数记录进一个数组 (为方便计算,),排序,去重,并从小到大枚举:
如果 ,。 否则,显然再加大限制也不可能到达了,直接退出。为什么?
因为 (即最小需要的 )的值,只会在 经过某个格子的权值 时才可能发生变化。当 在区间 内增加时,可以通行的格子集合没有变化,因此最小需要的 也不变。所以,对于这个区间内的所有 ,它们对应的合法 范围是相同的,都是从 到 。因此,这一整段 贡献的通行卡数量就是区间长度 乘以可选的 的数量 。
程序实现
CPP#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int T,n,a[3][200005],kn[400005],cnt,dp[3][200005];
bool v[3][200005];
long long ans;
int bfs(int k){ // 用 DP 实现的 F(x) 函数
v[1][0]=true; // 初始化
for(int i=1;i<=n;i++){
dp[1][i]=dp[2][i]=-1;
v[1][i]=(a[1][i]>=k);
v[2][i]=(a[2][i]>=k);
if(v[1][i-1] && v[1][i])dp[1][i]=max(dp[1][i-1],a[1][i]);
if(v[2][i-1] && v[2][i])dp[2][i]=max(dp[2][i-1],a[2][i]); // 水平转移
if(v[1][i] && v[2][i]){ // 垂直转移
if(dp[1][i]>=0 && dp[2][i]>=0){
if(dp[1][i]<dp[2][i])
dp[2][i]=max(a[2][i],min(dp[1][i],dp[2][i]));
if(dp[1][i]>dp[2][i])
dp[1][i]=max(a[1][i],min(dp[2][i],dp[1][i]));
}
else if(dp[1][i]>=0)dp[2][i]=max(a[2][i],dp[1][i]);
else if(dp[2][i]>=0)dp[1][i]=max(a[1][i],dp[2][i]);
}
if(dp[1][i]==-1)v[1][i]=false;
if(dp[2][i]==-1)v[2][i]=false; // 更新可达性
}
return dp[2][n];
}
int main(){
scanf("%d",&T); // 多测
while(T--){
cnt=ans=0;
scanf("%d",&n);
for(int i=1;i<=2;i++)for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
kn[++cnt]=a[i][j]; // 存入数组
}
sort(kn+1,kn+cnt+1); // 排序
for(int i=1,r;i<=cnt;i++){
if(kn[i]==kn[i-1])continue; // 类似于去重操作
r=bfs(kn[i]); // 得到 r 的下限
if(r!=-1)ans+=1LL*(kn[i]-kn[i-1])*(2*n-r+1); // 计算答案
else break;
}
printf("%lld\n",ans);
}
return 0;
}
AC 记录:Record
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...