首页 > 其他分享 >[NOIP2023] 双序列拓展 题解

[NOIP2023] 双序列拓展 题解

时间:2024-10-07 18:11:05浏览次数:6  
标签:return val min 题解 pos MAXN max 序列 NOIP2023

qaq
首先我们考虑其实这个条件就是要满足 \(f\) 严格比 \(g\) 大或 \(f\) 严格比 \(g\) 小。

在这里只讨论大于。

然后考虑到对于一个 \(i\) 如果不满足,我们可以把对应数组向右移一位看是否满足,如果还是不满足就无解了。
考虑对于现在满足的 \(i\) ,我们可以分别把两个指针向右移一位或者都移一位,所以很容易设计出状态及转移。
设 \(dp_{i,j}\) 表示 \(f\) 数组的指针到了第 \(i\) 位, \(g\) 数组的指针到了第 \(j\) 位能否匹配,转移:\(dp_{i,j} \ |= dp_{i-1,j} \ | \ dp_{i,j-1} \ | \ dp_{i-1,j-1}\),分别对应上面的三种操作。
至此就 \(\Theta(nmq)\) 有35pts的高分了

#include<bits/stdc++.h>
using namespace std; 
int c,n,m,q,kx,ky,x,y;
int a[2005],b[2005],ca[2005],cb[2005];
bool f[2005][2005],dp[2005][2005];
void work(){
	if(a[1]<=b[1]||a[n]<=b[m]) f[n][m]=0;
	else{
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j) f[i][j]=0;
		}
		f[1][1]=1;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				if(a[i]>b[j]) f[i][j]|=(f[i-1][j]|f[i][j-1]|f[i-1][j-1]);
			}
		}
	//	for(int i=1;i<=n;++i){
	//		for(int j=1;j<=m;++j) printf("%d",f[i][j]);
	//		printf("\n");
	//	}
	//	printf("\n");
	}
	if(a[1]>=b[1]||a[n]>=b[m]) dp[m][n]=0;
	else{
		for(int i=1;i<=m;++i){
			for(int j=1;j<=n;++j) dp[i][j]=0;
		}
		dp[1][1]=1;
		for(int i=1;i<=m;++i){
			for(int j=1;j<=n;++j){
				if(a[j]<b[i]) dp[i][j]|=(dp[i-1][j]|dp[i][j-1]|dp[i-1][j-1]);
			}
		}
	//	for(int i=1;i<=m;++i){
	//		for(int j=1;j<=n;++j) printf("%d",dp[i][j]);
	//		printf("\n");
	//	}
	//	printf("\n");
	}
	printf("%d",f[n][m]|dp[m][n]);
}
int main(){
	scanf("%d %d %d %d",&c,&n,&m,&q);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),ca[i]=a[i];
	for(int i=1;i<=m;++i) scanf("%d",&b[i]),cb[i]=b[i];
	work();
	while(q--){
		for(int i=1;i<=n;++i) a[i]=ca[i];
		for(int i=1;i<=m;++i) b[i]=cb[i];
		scanf("%d %d",&kx,&ky);
		for(int i=1;i<=kx;++i) scanf("%d %d",&x,&y),a[x]=y;
		for(int i=1;i<=ky;++i) scanf("%d %d",&x,&y),b[x]=y;
		work();
	}
	return 0;
}

然后感觉这个东西好像很熟悉,这不就是网格走路吗。
考虑一个 \(n\times m\) 的网格,每次可以向右、下、右下三个方向走,中间如果小于等于就不可走,问能否从起点到达终点。
既然都分析成这样了,我们考虑怎么优化掉这个 \(n\times m\)。
看一眼特殊性质,好奇怪啊,跟max和min有什么关系。仔细想一想,发现我确实只需要知道两个数列max和min的关系,因为max和min的讨论一定更加极限,是最后的必要条件,到达性一定可以通过max和min转移过来,换句话说,它们两个是充要条件。可以走到一定没有全堵住的行和全堵住的列,而全堵住的最有可能的就是max对min,反之亦然。
然后考虑我现在要保证严格大于,那么我的数列一定有 \(f_{max}>g_{max}\) 和 \(f_{min}>g_{min}\) 。
结合上面的网格,我们思考其实就是如果 $f_{min} \leq g_{min} $ 或者 \(f_{max} \leq g_{max}\) ,那么一定不可到达的。
所以我们其实可以逐步缩小范围,就是你考虑如果从起点可以走到 \(max,min\) 这样的交点,然后从这个点又可以走到终点,那么就可以到达。
具体地,每次选择当前区间内 \(f_{max}\) 和 \(g_{min}\) ,如果当前满足条件就向左向下缩,不断递归,直到有一个不满足的或者是到达了目标行或目标列。
于是我们成功将问题转化为两部分的到达性处理,而这两部分的到达性问题我们又可以向内分治,再做一个前缀最大最小,缩的次数最多是 \(n+m\) 于是时间复杂度转化为 \(\Theta(q \ (n+m) )\)。
qaq给我调破防了

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int c,n,m,q,kx,ky,x,y;
int a[MAXN],b[MAXN],ca[MAXN],cb[MAXN];
struct W{
	int pos,val;
};
W preai[MAXN],preaa[MAXN];//f的前缀最小最大 
W prebi[MAXN],preba[MAXN];//g的前缀最小最大 
W ai[MAXN],aa[MAXN];//f的后缀最小最大 
W bi[MAXN],ba[MAXN];//g的后缀最小最大 
void pre(){
	preaa[0].val=-1;preba[0].val=-1;
	preai[0].val=1e9+1;prebi[0].val=1e9+1;
	aa[n+1].val=-1;ba[m+1].val=-1;
	ai[n+1].val=1e9+1;bi[m+1].val=1e9+1;
	for(int i=1;i<=n;++i){
		if(preai[i-1].val>a[i]) preai[i].pos=i,preai[i].val=a[i];
		else preai[i]=preai[i-1];
		if(preaa[i-1].val<a[i]) preaa[i].pos=i,preaa[i].val=a[i];
		else preaa[i]=preaa[i-1];
	}
	for(int i=n;i>=1;--i){
		if(ai[i+1].val>a[i]) ai[i].pos=i,ai[i].val=a[i];
		else ai[i]=ai[i+1];
		if(aa[i+1].val<a[i]) aa[i].pos=i,aa[i].val=a[i];
		else aa[i]=aa[i+1];
	}
	for(int i=1;i<=m;++i){
		if(prebi[i-1].val>b[i]) prebi[i].pos=i,prebi[i].val=b[i];
		else prebi[i]=prebi[i-1];
		if(preba[i-1].val<b[i]) preba[i].pos=i,preba[i].val=b[i];
		else preba[i]=preba[i-1];
	}
	for(int i=m;i>=1;--i){
		if(bi[i+1].val>b[i]) bi[i].pos=i,bi[i].val=b[i];
		else bi[i]=bi[i+1];
		if(ba[i+1].val<b[i]) ba[i].pos=i,ba[i].val=b[i];
		else ba[i]=ba[i+1];
	}
}
bool check1(int x,int y,int ac){
	if(ac){
		if(x==1||y==1) return true;
		if(preai[x-1].val>prebi[y-1].val) return check1(x,prebi[y-1].pos,ac);
		if(preaa[x-1].val>preba[y-1].val) return check1(preaa[x-1].pos,y,ac);
		return false;
	}
	else{
		if(x==1||y==1) return true;
		if(preai[x-1].val<prebi[y-1].val) return check1(preai[x-1].pos,y,ac);
		if(preaa[x-1].val<preba[y-1].val) return check1(x,preba[y-1].pos,ac);
		return false;
	}
	return 0;
}

bool check2(int x,int y,int ac){
	if(ac){
		if(x==n||y==m) return true;
		if(ai[x+1].val>bi[y+1].val) return check2(x,bi[y+1].pos,ac);
		if(aa[x+1].val>ba[y+1].val) return check2(aa[x+1].pos,y,ac);
		return false;
	}
	else{
		if(x==n||y==m) return true;
		if(ai[x+1].val<bi[y+1].val) return check2(ai[x+1].pos,y,ac);
		if(aa[x+1].val<ba[y+1].val) return check2(x,ba[y+1].pos,ac);
		return false;
	}
}
bool work(){
	if(a[1]<b[1]&&a[n]<b[m]){
		if(preaa[n].val>=preba[m].val||preai[n].val>=prebi[m].val) return false;
		return check1(preai[n].pos,preba[m].pos,0)&&check2(preai[n].pos,preba[m].pos,0);
	}
	else if(a[1]>b[1]&&a[n]>b[m]){
		if(preaa[n].val<=preba[m].val||preai[n].val<=prebi[m].val) return false;
		return check1(preaa[n].pos,prebi[m].pos,1)&&check2(preaa[n].pos,prebi[m].pos,1);
	}
	return false;
}
int main(){
	scanf("%d %d %d %d",&c,&n,&m,&q);
	for(int i=1;i<=n;++i) preai[i].val=2e9;for(int i=1;i<=m;++i) prebi[i].val=2e9;
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),ca[i]=a[i];
	for(int i=1;i<=m;++i) scanf("%d",&b[i]),cb[i]=b[i];
	pre();
	printf("%d",work());
	while(q--){
		for(int i=1;i<=n;++i) a[i]=ca[i];
		for(int i=1;i<=m;++i) b[i]=cb[i];
		scanf("%d %d",&kx,&ky);
		for(int i=1;i<=kx;++i) scanf("%d %d",&x,&y),a[x]=y;
		for(int i=1;i<=ky;++i) scanf("%d %d",&x,&y),b[x]=y;
		pre();
		printf("%d",work());
	}
	return 0;
}

标签:return,val,min,题解,pos,MAXN,max,序列,NOIP2023
From: https://www.cnblogs.com/mountzhu/p/18446486

相关文章

  • EGOI2024 简单题解
    Day1T1InfiniteRace由于只有重复超过一个人才肯定是跑过一圈的,所以只用用一个数组做标记就可以了,每超过一次就打上标记,否则去掉标记。T2Bouquet定义\(dp[i]\)为,以第\(i\)种郁金香结尾的选法中最大可选的郁金香数量,易得状态转移方程为:\(dp[i]=max{dep[j]}+1(j<l_i\le......
  • mysql登录遇到ERROR 1045问题解决方法
    遇到MySQL登录时出现 ERROR1045(访问被拒绝,用户名或密码错误),可以通过以下步骤来解决:1.确认用户名和密码检查用户名和密码:确认在连接数据库时输入的用户名和密码是否正确。尝试在命令行中连接数据库,确认是否能成功登录:bash mysql-uyour_username-p2.重......
  • 题解:AT_abc374_c [ABC374C] Separated Lunch
    无耻的广告更好的阅读体验~最近在搞个人博客博客园的差点忘了更了。已经沦落到在写这种水题题解了。题目翻译有\(n\)队人,每个队人数不同,把他们分成2组(同一队的不能拆开),使两组人数差距尽量小。形式化题意:有\(n\)个数,把它们分成两组,使两组和的差尽量小。说句闲话:感觉这......
  • FredNormer: 非平稳时间序列预测的频域正则化方法
    时间序列预测是一个具有挑战性的任务,尤其是在处理非平稳数据时。现有的基于正则化的方法虽然在解决分布偏移问题上取得了一定成功但仍存在局限性。这些方法主要在时间域进行操作,可能无法充分捕捉在频域中更明显的动态模式,从而导致次优的结果。FredNormer论文的研究目的主要包......
  • P3527 MET-Meteors 题解
    Solution单次二分:二分时间,做这个时间前的所有操作,然后线性统计。注意到可以整体二分,直接整体二分是\(O(n\log^2n)\)。考虑扫描序列,用线段树维护每个时间段内该位置的增加值,差分一下可以实现。将这棵线段树持久化一下,一个国家所有位置同时二分即可\(O(n\logn)\),可惜空间太......
  • P3250 网络 题解
    Solution单次二分:问“重要度\(\gex\)的所有操作,且\(t\)时间点还存在的所有操作中,是否有不经过这个点的”整体二分:保持操作、询问按时间有序,即预先按时间排序,下传时保持有序;对于一次Solve,对于所有重要度\(\gemid+1\)的操作(加入、删除),考虑与询问按时间混合排序,然后依次......
  • P3215 括号修复 题解
    Statement维护一个括号序列,有以下操作:区间覆盖区间翻转区间反转(左括号变右括号,右括号变左括号)区间问最少改多少位能使括号序列合法,保证有解Solution单纯没想到答案怎么算。。。首先一段括号序,如果消除中间的所有匹配,最终一定形如))))(((,这个信息是可合并的设这时左括......
  • P5416 = UOJ 198 时空旅行 题解
    Statement一棵树,每个节点上有一个集合,每个儿子集合由父亲集合增加一个点\((x_i,c_i)\)或删除一个点得到。根节点集合为\(\{(0,0,0,c_0)\}\)多次询问,每次问\(u\)点的集合内,\(\min\{(x_i-x)^2+c_i\}\)Solution首先你认真读完题发现原题中\(y,z\)都是没用的然后离线DFS......
  • 火星商店问题 题解
    Solution线段树套trie,秒了!\(O(n\log^2n)\)Code#include<bits/stdc++.h>usingnamespacestd;#definerep(i,j,k)for(inti=(j);i<=(k);++i)#definereo(i,j,k)for(inti=(j);i>=(k);--i)typedeflonglongll;constintN=1e5+......
  • Gym 100543G Virus synthesis 题解
    Solution首先只考虑回文串的答案;我们重点考虑的是偶回文串结论:对于偶回文串\(u\),从其最长的长度小于等于他的一半的回文后缀,或其父亲转移过来,一定是最优的证明:设\(u\)的一个回文子串为\(v\)(不是父亲),你要让\(v\tou\)的转移最优首先\(v\)不能跨过\(u\)的中点,因为此......