191106-考试总结

T1T3过难就只能放弃了。

题目描述

一共有 2 n 2n 2n 个节点按顺序排布在一行上面,他们分成了 n n n 对,每一对之间有一根弦,小F需要把一段节点取出来检查。
他一共会取出 m m m 次,每次会选定两个右端点 r 1 r_1 r1; r 2 r_2 r2,取出来一段节点的同时不能割断任何一根弦,但是无聊的小C想确认符合条件的左端点的个数,也就说这个左端点和两个右端点(含端点)形成的区间取出来以后都不会割断弦。
在这里插入图片描述

输入格式

两个正整数 n , m n,m n,m,表示弦个数和检查次数
2 n 2n 2n 个整数:第 i i i 个整数表示第 i i i 个点连接这根弦的另一端的点
m m m 行,每行两个整数 r 1 , r 2 r_1,r_2 r1,r2 表示两个右端点, r r r 有可能是 0 0 0

输出格式

m m m 行,每行一个整数表示答案。

数据范围

对于所有测试点, n , m ≤ 100 W n,m \le 100W n,m100W

解析

考虑一个右端点,定义它的父亲为最近的左端点使得它俩可以构成一个合法区间的左侧的那一个点,那么这个合法区间一定是极小合法区间,而两个合法区间的交一定合法,一个合法区间要么是交集,要么是极小合法区间,因此可以形成一个森林结构,任意一个祖先(+1)到后代的路径都对应一个合法区间,那么题目中要求的实际上就是 LCA 的 depth,这就比较容易了,只是注意特殊情况。

重点:
  1. 找极小合法区间:利用 做括号匹配的问题。
  2. 根节点的确立:当一个点入度==0,就是根。

我为了适应平均的std码风,改用动态数组来建单向边。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
int n,m,p[2222222];
int l[2222222],r[2222222],sta[2222222],topp;
int tot,rt[2222222],fa[2222222];
vector<int>edge[2222222];
int f[2222222][22],dep[2222222];
inline int Read(){
	register int x=0;register char ch=getchar();
	while(ch>57||ch<48)ch=getchar();
	while(ch<=57&&ch>=48)x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return x;
}
inline void Write(int x){
	if(x>=10)Write(x/10);
	putchar(x%10+48);
}
void Init(int u,int rot){
	rt[u]=rot;dep[u]=fa[u]==-1?1:dep[fa[u]]+1;
	f[u][0]=fa[u];
	for(register int i=0;i<=21;i++)f[u][i+1]=f[f[u][i]][i];
	int w=edge[u].size();
	for(register int i=0;i<=w-1;i++){
		int v=edge[u][i];
		Init(v,rot);
	}
}
int Lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	for(register int k=dep[u]-dep[v],i=0;(1<<i)<=k;i++)if(k>>i&1)u=f[u][i];
	if(u==v)return u;
	for(register int i=21;i>=0;i--)if((1<<i)<dep[u]&&f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
	return fa[u];
}
int Query(int r1,int r2){
	return rt[r1]==-1||rt[r2]==-1||rt[r1]!=rt[r2]?0:dep[Lca(fa[r1],fa[r2])];
}
int main(){
	n=Read();m=Read();
	for(register int i=1;i<=n*2;i++)p[i]=Read();
	for(register int i=1;i<=n*2;i++){
		l[i]=min(i,p[i]),r[i]=max(i,p[i]);
		while(topp&&sta[topp]>=l[i]){
			int pos=sta[topp--];
			l[i]=min(l[i],l[pos]),r[i]=max(r[i],r[pos]);
		}
		sta[++topp]=i;
	}
	for(register int i=0;i<=(n<<1);i++)rt[i]=fa[i]=-1;
	for(register int i=1;i<=(n<<1);i++){
		if(i==r[i]){
			fa[i]=l[i]-1;
			edge[fa[i]].push_back(i);
		}
	}
	r[0]=0;
	for(register int i=0;i<=n*2;i++)if(!edge[i].empty()&&rt[i]==-1)Init(i,i);
	while(m--){
		int r1=Read(),r2=Read();
		if(r1==0||r2==0)putchar(48);
		else Write(Query(r1,r2));
		putchar(10);
	}
	return 0;
}

在U盘上运行大约20秒,在评测机上大约1.2秒,时限2秒,内存约333兆。

预祝成功

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值