【2020省選Day1T1】LOJ3299 「聯合省選 2020 A | B」冰火戰士


題目鏈接

這個“比賽方式”被題目描述的花里胡哨,但實際上很簡單:

假如設定比賽溫度為\(k\)。那么,“有用”的冰系戰士,就是\(x_i\leq k\)的這些冰系戰士;“有用”的火系戰士,就是\(x_i\geq k\)的這些火系戰士。一場比賽的價值,就是冰火兩方,“有用”的戰士的\(y_i\)較小值。具體來說,就是:

\[\min\left(\sum_{i\in\text{ice},x_i\leq k}y_i,\sum_{i\in\text{fire},x_i\geq k}y_i\right) \]

我們要選擇一個\(k\),使得這個價值盡可能大。如果有多個\(k\)能使價值最大,則選最大的\(k\)

首先容易發現,最優的\(k\)一定是某名戰士的溫度。所以我們可以把所有戰士的溫度先離散化,然后答案就只要在這些值里面找即可。

考慮兩個函數,一個是\(f_{\text{ice}}(k)=\sum_{i\in\text{ice},x_i\leq k}y_i\),一個是\(f_{\text{fire}}(k)=\sum_{i\in\text{fire},x_i\geq k}y_i\)。那么,我們就是要取一個\(k\),使得\(\min(f_{\text{ice}}(k),f_{\text{fire}}(k))\)最大。

發現,\(f_{\text{ice}}(k)\)是單調不下降的;\(f_{\text{fire}}(k)\)是單調不上升的。如果把它們一起畫出來,大概是“打一個叉”的樣子。

考慮設\(\text{res}(k)=\min(f_{\text{ice}}(k),f_{\text{fire}}(k))\),則畫出來應該是這樣,也就是圖中黑色的這條線。

發現,當\(f_{\text{ice}}(k)\)\(f_{\text{fire}}(k)\)相交時,\(\text{res}(k)\)取到最大值。但是實際上,由於兩個函數並不連續(\(k\)只能取整數),所以有兩個可能的最優\(k\)

  • 一個是,最大的,使得\(f_{\text{ice}}(k)<f_{\text{fire}}(k)\)\(k\)
  • 另一個是,最小的,使得\(f_{\text{ice}}(k)\geq f_{\text{fire}}(k)\)\(k\)

容易想到二分答案。考慮用數據結構維護\(f_{\text{ice}}(k)\)\(f_{\text{fire}}(k)\),我們需要支持區間加、單點查詢。例如,每個冰系戰士,是對所有\(k\geq x_i\)(一段后綴),令它們的\(f_{\text{ice}}(k)\)值加上\(y_i\);一個火系戰士,是對所有\(k\leq x_i\)(一段前綴),令它們的\(f_{\text{fire}}(k)\)值加上\(y_i\)。(當然,你也可以轉化,或者說理解為,單點加,區間求和,這本質上是一樣的)。這個數據結構,可以用線段樹或樹狀數組。這樣做,時間復雜度是\(O(n\log^2n)\)的,無法AC。

繼續優化。你看,又是線段樹,又是二分,你很容易想到線段樹上二分。對每個區間,維護這個區間左端點的\(f_{\text{ice}}(l)\)\(f_{\text{fire}}(l)\),右端點的\(f_{\text{ice}}(r)\)\(f_{\text{fire}}(r)\),就可以二分了。具體來說,我們要做三次“線段樹上二分”。第一次,找到【最大的,使得\(f_{\text{ice}}(k)<f_{\text{fire}}(k)\)\(k\)】。第二次,找到【最小的,使得\(f_{\text{ice}}(k)\geq f_{\text{fire}}(k)\)\(k\)】(當然,這里第二個\(k\)其實就是第一個\(k\)\(1\)的位置。但是我們不光要知道位置,還需要知道這個位置上\(\min(f_{\text{ice}}(k),f_{\text{fire}}(k))\)的具體的值,所以還是需要再做一次線段樹上操作的)。那么,最大價值,就是這兩個\(k\)對應價值的較大者。然而,如果價值較大的\(k\)是第二個,你會發現,它后面,可能還存在價值和它一樣的\(k\)。而根據題目要求,如果價值一樣,我們要找到最后一個\(k\)。所以此時我們還需要把這個“最大價值”帶入,再二分一次,找到價值等於這個“最大價值”的、最靠后的\(k\)。因此,一共需要做三次“線段樹上二分”,雖然時間復雜度變成了\(O(n\log n)\),但是常數實在是太大了,最終可能和樹狀數組實現的\(O(n\log^2n)\)做法得分差不多。

我們繼續優化。其實,樹狀數組上也是可以二分的。這個“二分”的實現,其實更像“倍增”。例如用倍增法求LCA時,我們從大到小枚舉當前節點的\(2^{\log n}\dots 2^0\)次祖先,能往上跳就往上跳。在樹狀數組上也是一樣,每次檢查從當前點,往前跳\(2^i\)個位置,是否“可行”。如果“可行”,就跳過去。否則位置不變。這里“往前跳\(2^i\)是否可行”怎么“檢查”,其實就是看樹狀數組上\(\text{curpos}+2^i\)的這個位置里填的數。這是由於樹狀數組的性質:\(c[i]\)里填的是,\([i-\operatorname{lowbit}(i)+1,i]\)這段區間的信息。那么\(\text{curpos}+2^i\),這個位置,填的就是\([\text{curpos}+1,\text{curpos}+2^i]\)這段的信息。

現在我們會在樹狀數組上二分了。回到本題。我們用樹狀數組,還是維護\(f_{\text{ice}}(k)\)\(f_{\text{fire}}(k)\)這兩個東西。修改的時候,是區間修改,可以用樹狀數組的套路:差分,轉化為單點修改。具體來說,如果是后綴加,則直接在開始位置加;如果是前綴加,則先用一個全局變量記錄,再把后綴減掉(這樣只需要一次樹狀數組上操作)。

查詢的時候,先二分出【最大的,使得\(f_{\text{ice}}(k)<f_{\text{fire}}(k)\)\(k\)】和【最小的,使得\(f_{\text{ice}}(k)\geq f_{\text{fire}}(k)\)\(k\)】,並查詢出它們的\(f\)值。如果第二個\(f\)值更大,則帶入這個值,再二分一次。就和線段樹的做法類似。所以最多需要3次樹狀數組上操作。由於樹狀數組常數小得多,所以可以通過。

時間復雜度\(O(n\log n)\)

注意:要使用讀入優化。

參考代碼(在LOJ查看):

//problem:LOJ3299
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

/* --------------- fast io --------------- */ // begin
namespace Fread{
const int SIZE=1<<20;
char buf[SIZE],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,SIZE,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
namespace Fwrite{
const int SIZE=1<<20;
char buf[SIZE],*S=buf,*T=buf+SIZE;
inline void flush(){
	fwrite(buf,1,S-buf,stdout);
	S=buf;
}
inline void putchar(char c){
	*S++=c;
	if(S==T)flush();
}
struct _{
	~_(){flush();}
}__;
}//namespace Fwrite

#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
	#define putchar Fwrite::putchar
#endif

template<typename T>inline void read(T& x){
	x=0;int f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c))x=x*10+(c-'0'),c=getchar();
	x*=f;
}
template<typename T>inline void write(T x,bool _enter=0,bool _space=0){
	if (!x)putchar('0');else{
		if(x<0)putchar('-'),x=-x;
		static char dig[41];
		int top=0;
		while(x)dig[++top]=(x%10)+'0',x/=10;
		while(top)putchar(dig[top--]);
	}
	if(_enter)putchar('\n');
	if(_space)putchar(' ');
}

namespace Fastio{
struct reader{
	template<typename T>reader& operator>>(T& x){::read(x);return *this;}
	reader& operator>>(char& c){
		c=getchar();
		while(c=='\n'||c==' ')c=getchar();
		return *this;
	}
	reader& operator>>(char* str){
		int len=0;
		char c=getchar();
		while(c=='\n'||c==' ')c=getchar();
		while(c!='\n'&&c!=' ')str[len++]=c,c=getchar();
		str[len]='\0';
		return *this;
	}
}cin;
const char endl='\n';
struct writer{
	template<typename T>writer& operator<<(T x){::write(x,0,0);return *this;}
	writer& operator<<(char c){putchar(c);return *this;}
	writer& operator<<(const char* str){
		int cur=0;
		while(str[cur])putchar(str[cur++]);
		return *this;
	}
}cout;
}//namespace Fastio
#define cin Fastio::cin
#define cout Fastio::cout
#define endl Fastio::endl
/* --------------- fast io --------------- */ // end

const int MAXN=2e6;
int m,vals[MAXN+5],cnt_val;
struct Event{
	int op,t,x,y;
}ev[MAXN+5];

struct FenwickTree{
	int sz;
	int fire[MAXN+5],ice[MAXN+5],delta_fire;
	void modify_ice(int pos,int val){
		//后綴加 -> 單點加,查詢時查前綴和
		for(int p=pos;p<=sz;p+=(p&(-p))){
			ice[p]+=val;
		}
	}
	void modify_fire(int pos,int val){
		//前綴加 -> 后綴減,總偏移量加
		delta_fire+=val;
		for(int p=pos+1;p<=sz;p+=(p&(-p))){
			fire[p]-=val;
		}
	}
	int query_min(int pos){//min(ice,fire)
		int ice_sum=0,fire_sum=delta_fire;
		for(int p=pos;p;p-=(p&(-p))){
			ice_sum+=ice[p];
			fire_sum+=fire[p];
		}
		return min(ice_sum,fire_sum);
	}
	int find1(){
		//最后一個ice-fire<0的位置
		int p=0,s=-delta_fire;
		for(int i=20;i>=0;--i){
			if(p+(1<<i)>sz)continue;
			int nxt=s+(ice[p+(1<<i)]-fire[p+(1<<i)]);
			if(nxt<0){
				s=nxt;
				p+=(1<<i);
			}
		}
		return p;
	}
	int find2(int goal_min){
		//最后一個min(ice,fire)=val的位置
		int p=0,ice_sum=0,fire_sum=delta_fire;
		for(int i=20;i>=0;--i){
			if(p+(1<<i)>sz)continue;
			int new_ice=ice_sum+ice[p+(1<<i)];
			int new_fire=fire_sum+fire[p+(1<<i)];
			if(new_ice<new_fire){
				ice_sum=new_ice;
				fire_sum=new_fire;
				p+=(1<<i);
			}
			else{
				assert(min(new_ice,new_fire)<=goal_min);
				if(min(new_ice,new_fire)==goal_min){
					ice_sum=new_ice;
					fire_sum=new_fire;
					p+=(1<<i);
				}
			}
		}
		return p;
	}
	void resize(int _sz){sz=_sz;}
	FenwickTree(){}
}T;

int main() {
	//freopen("icefire.in","r",stdin);
	//freopen("icefire.out","w",stdout);
	cin>>m;
	for(int i=1;i<=m;++i){
		cin>>ev[i].op;
		if(ev[i].op==1){
			cin>>ev[i].t>>ev[i].x>>ev[i].y;
			vals[++cnt_val]=ev[i].x;
		}
		else{
			int j;cin>>j;
			ev[i].t=ev[j].t;
			ev[i].x=ev[j].x;
			ev[i].y=-ev[j].y;
		}
	}
	sort(vals+1,vals+cnt_val+1);
	cnt_val=unique(vals+1,vals+cnt_val+1)-(vals+1);
	for(int i=1;i<=m;++i){
		ev[i].x=lob(vals+1,vals+cnt_val+1,ev[i].x)-vals;
		//cout<<ev[i].x<<endl;
	}
	T.resize(cnt_val);
	for(int i=1;i<=m;++i){
		if(ev[i].t==0)
			T.modify_ice(ev[i].x,ev[i].y);
		else
			T.modify_fire(ev[i].x,ev[i].y);
		/*
		//暴力
		pii res=mk(-1,-1);
		for(int j=1;j<=cnt_val;++j){
			res=max(res,mk(T.query_min(j),j));
		}
		*/
		int p1=T.find1();
		pii res1=mk(-1,-1);
		if(p1>0){
			res1=mk(T.query_min(p1),p1);
		}
		pii res2=mk(-1,-1);
		if(p1<cnt_val){
			int goal_min=T.query_min(p1+1);
			int p2=T.find2(goal_min);
			//assert(p2>=p1+1);
			//assert(T.query_min(p2)==goal_min);
			//assert(p2==cnt_val||T.query_min(p2+1)<goal_min);
			res2=mk(goal_min,p2);
		}
		pii res=max(res1,res2);
		
		if(res.fi==0)
			cout<<"Peace"<<endl;
		else
			cout<<vals[res.se]<<" "<<res.fi*2<<endl;
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM