Hydro 每日一题 #001
简介
HydroOJ 每日将从 HydroOJ 主题库及各大域中选出一题附以详解在此处分享给各位用户。
本期题目(2021.9.3)
[COCI2018-2019 Final T4] TENIS
题目描述
Vito 十分喜欢打网球。不久,他就会组织一次大规模的锦标赛。这次锦标赛会有 \(n\) 名选手参加,编号为从 \(1\) 到 \(n\)。Vito 在过去几年跟踪了这些选手的数据。因此,他确定了这些选手在三种不同的球场:红土,草地和硬地上比赛的能力值。也就是说,对于每种场地,他都按最强到最弱的顺序确定了选手的排名。
Vito 的锦标赛赛制有点与众不同。一共会进行 \(n-1\) 轮比赛。在每场比赛中,还没有被淘汰的两名选手会在某种特定的场地上进行比赛。在这种场地上较弱的选手会被淘汰出局。\(n-1\) 轮比赛后唯一的胜者就是这次锦标赛的冠军。
Vito 是一个很有影响力的人,可以操纵比赛的结果。即对于每场比赛,Vito 可以选择参赛选手和比赛场地。当然,他只能选择未被淘汰的选手。
Vito 经常更新他收集的数据,他有时会交换在某种场地上两名选手的排名。并且他有很多朋友,有些朋友会问他:“选手 \(x\) 是我的外甥,他有机会夺冠吗?”,为了回答这些询问,你需要写一个程序帮助 Vito 更新排名,并根据当前时刻 Vito 的排名表回答他朋友的提问。
题解
思维题。
拿到题目首先观察样例。
4 4
1 2 3 4
2 1 3 4
2 4 3 1
1 1
1 4
2 3 1 4
1 4
针对第二个事件,我们发现,虽然在前两个场地 \(4\) 都是最弱的,但是在第三个场地 \(4\) 能打赢 \(1\) 和 \(3\),而在第一个场地 \(1\) 又能打赢 \(2\)。因此只需让能被选手 \(4\) 打败的人先去打选手 \(4\) 打不过的人,选手 \(4\) 才有可能获得胜利。
将以上策略抽象成图,将每一个人视为一个结点,向每个他能打败的人(不管在哪个场地)都连一条边,那么询问就是看从这个人的结点出发,能否遍历其他所有结点。如下图:
(此处 \(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
将同一个选手在三个场地的位置用边连接,如下图:
此时选手 \(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;
}
本文来自博客园,作者:Maplisky,转载请注明原文链接:https://www.cnblogs.com/lbh2021/p/15222997.html