题解 [COCI2018-2019 Final T4] TENIS

题目传送门

分析

思维题。

拿到题目首先观察样例。

针对第二个事件,我们发现,虽然在前两个场地 \(4\) 都是最弱的,但是在第三个场地 \(4\) 能打赢 \(1\)\(3\),而在第一个场地 \(1\) 又能打赢 \(2\)。因此只需让能被选手 \(4\) 打败的人先去打选手 \(4\) 打不过的人,选手 \(4\) 才有可能获得胜利。

将以上策略抽象成图,将每一个人视为一个结点,向每个他能打败的人(不管在哪个场地)都连一条边,那么询问就是看从这个人的结点出发,能否遍历其他所有结点。如下图:

hyhUZq.png

(此处 \(1.1,1.2,1.3\) 为选手 \(1\) 在三个场地的排名,在这种方法中可缩为一个点)

这时处理询问的复杂度为 \(\mathcal{O}(n)\),总时间复杂度为 \(\mathcal{O}(n\times Q)\),期望得分 \(30\) 分。

我们发现问题的瓶颈在于处理询问,而上述处理方法基于连向其他结点的边。为方便观察,先将这些边删去。我们来随手搓一组。

5 0
1 2 4 3 5
2 1 4 5 3
1 4 2 3 5

如下图:

hytAmR.png

观察发现,选手 \(1\)、选手 \(2\)、选手 \(4\) 可能获胜。

如上图,将同一个选手在三个场地的位置用边连接,可以发现,能获胜的选手和不能获胜的选手中间有一道明显的分隔线。这条分割线满足:没有一条边横跨分割线。这时在分割线左边的选手能获胜,右边的则不能。

然而随手一组就 Hack 掉了。比如:

5 0
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5

在这组中有多条满足要求的分割线,怎么办?显然只有最左边的是正确的。考虑维护最左边的分割线

为什么这样是正确的?考虑分割线左边选手的情况。显然左边的选手可以直接或间接打败在左边的其他选手。假设某一个选手不能打败左边的其他选手,那么这条分割线一定可以向左移动,把这个选手排除在外。那么这样就不满足最左边的分割线这一前提条件。因此假设不成立。

基于以上事实,分割线左边的选手必定可以直接或间接打败其他选手。同样的,分割线右边的选手不可能直接或间接打败左边的全部选手。(同样可以反证)

这条线如何维护呢?我们记数组 \(p\)\(p_i\) 表示有 \(p_i\) 个选手的边没有跨过 \(i\)。记第 \(i\) 位选手的在第 \(j\) 个场地的排名为 \(d_{i,j}(1\le j\le 3)\),那么对于这名选手来说,他的边不会跨过 \(\max(d_{i,j})\to n~(1\le j\le 3)\),体现在 \(p\) 数组上即对 \(p_i \to p_n\) 区间加 \(1\)

最后,\(p\) 何时满足条件?根据 \(p_i\) 的定义可知,若 \(p_i=i\),则此处有一条分割线。找最左边的一条即可。

总结上文,需要支持的操作有:

  • 区间加。
  • 区间查询。

用线段树维护即可。时间复杂度 \(\mathcal{O}(Q \log n)\)

tips:线段树叶节点可以赋初值 \(-l\),其中 \(l\) 为结点代表的范围。在每个结点里记最大值。查询时树上二分,若左子树最大值为 \(0\),查左子树,否则查右子树。

code

#include<bits/stdc++.h>
#define ll long long
#define reg register
#define _max(a,b) ((a)>(b)?(a):(b))
#define Max(x,y,z) ((x)>(y)?_max((x),(z)):_max((y),(z)))
#define _Max(x) Max(d[1][x],d[2][x],d[3][x])
#define F(i,a,b) for(reg int i=(a);i<=(b);++i)
using namespace std;
bool beginning;
inline int read();
const int N=1e5+5;
int n,q,d[4][N],Ans;
struct P {
	int l,r,Mx,lz;
} f[N<<2];
#define ls (x<<1)
#define rs (x<<1|1)
void build(int x,int l,int r) {
	f[x].l=l,f[x].r=r;
	if(l==r) {
		f[x].Mx=-l;
		return;
	}
	int mid=l+r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	f[x].Mx=_max(f[ls].Mx,f[rs].Mx);
}
inline void push_down(int x) {
	if(!f[x].lz)return;
	f[ls].lz+=f[x].lz,f[rs].lz+=f[x].lz;
	f[ls].Mx+=f[x].lz,f[rs].Mx+=f[x].lz;
	f[x].lz=0;
}
void modify(int x,int l,int r,int val) {
	if(l<=f[x].l and f[x].r<=r) {
		f[x].lz+=val,f[x].Mx+=val;
		return;
	}
	push_down(x);
	int mid=f[x].l+f[x].r>>1;
	if(l<=mid)modify(ls,l,r,val);
	if(r>mid)modify(rs,l,r,val);
	f[x].Mx=_max(f[ls].Mx,f[rs].Mx);
}
int query(int x,int l,int r) {
	if(l==r)return l;
	int mid=l+r>>1;
	push_down(x);
	if(!f[ls].Mx)return query(ls,l,mid);
	return query(rs,mid+1,r);
}
bool ending;
int main() {
//  printf("%.2lfMB\n",1.0*(&beginning-&ending)/1024/1024);
	n=read(),q=read();
	build(1,1,n);
	F(i,1,3)F(j,1,n)d[i][read()]=j;//记排名 
	F(i,1,n)modify(1,_Max(i),n,1);//将初始结点插入答案 
	Ans=query(1,1,n);//记录分割线 
	reg int op,x,y,z;
	while(q--) {
		op=read();
		if(op==1) {
			x=read();
			puts(d[1][x]<=Ans?"DA":"NE");
		} else if(op==2) {
			z=read(),x=read(),y=read();
			modify(1,_Max(x),n,-1);
			modify(1,_Max(y),n,-1);
			swap(d[z][x],d[z][y]);
			modify(1,_Max(x),n,1);
			modify(1,_Max(y),n,1);
			Ans=query(1,1,n);
		}
	}
	return 0;
}
inline int read() {
	reg int x=0;
	reg char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x;
}
posted @ 2021-09-03 13:33  Maplisky  阅读(127)  评论(0编辑  收藏  举报