1. 程式人生 > 其它 >每日一題 #001 (草稿)

每日一題 #001 (草稿)

\[\large\texttt{HydroOJ 每日一題 #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\)

\(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