JZOJ6400. 【NOIP2019模擬11.01】Game

Description

傳送門

  • 小 A 和小 B 在玩一個遊戲,他們兩個人每人有 𝑛 張牌,每張牌有一個點數,並且在接下來的 𝑛 個回合中每回合他們兩人會分別打出手中的一張牌,點數嚴格更高的一方得一分.
  • 然而現在小 A 通過某種神祕的方法得到了小 B 的出牌順序,現在他希望規劃自己的出牌順序使得自己在得分儘可能高的前提下出牌的字典序儘可能大。
    1<=n<=1e5

Solution

  • 前兩天才做過的超級弱化版JZOJ6387,n<=1000,當時沒有考慮優化,因爲數據那麼小,沒有仔細去想更加優的做法,盲目相信出題人的水平就出鍋了
  • 首先,對於求答案,有很多種方法,可以O(n)暴力去求,但也可以O(log n)用一個權值線段樹去維護。記錄當前區間匹配後剩下的a和b的個數。然後對於兩個區間由於已經滿足了大小,所以直接讓左邊的a匹配右邊的b就好了。
  • 不難證明這樣維護是沒有問題的。
  • 那麼按位貪心。
  • 題解的做法是直接二分這個位置跟哪個b匹配,然後再線段樹上刪掉這一對a和b。看看最終的答案是否最優。這樣是O(n log2n)的。
  • 實際上還有更加優秀的O(n log n)的做法。
  • 我們可以分類討論。
  • (1)去掉ai後答案不變。假設ls0表示去掉ai後在線段樹的匹配中最大的匹配成功的b,ls1表示最大的未被匹配的b。再分兩類討論:
    1. ls0<=ai,那麼ls1一定<=ai(否則ls1就可以和ai匹配了,實際答案就會變大1),又因爲我們匹配的時候是小的a儘量匹配小的b(一種顯然的O(n)的貪心思路),所以如果用ai去和ls1(或者從ls1開始後往前一段連續被匹配的b中的任何一個)匹配的話,答案就會減1。所以ai只能和ls1匹配
    2. ls0>aiai和ls0匹配就好了,雖然會使ls0已經匹配的那個失去對象,但是同時也加了1,所以沒有影響,並且因爲ls1<=ai,所以不選ls1。
  • (2) 去掉ai後答案改變。說明ai是必選的。所以一定有一個空格給ai(保證當前答案的合法性),所以就選ls1
  • 我們可以在線段樹上二分來得到ls0和ls1.
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 200005
#define maxm 1000005
using namespace std;

int n,i,j,k,a[maxn],b[maxn],cnt,tot,fr[maxn];
int t0[maxm],t1[maxm],t[maxm],tmp[2][maxn];
struct arr{int x,i,tp;} A[maxn];
int cmp(arr a,arr b){return a.x<b.x;}

void read(int &x){
	x=0; char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar());
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
}

void upd(int x){
	int ls=x<<1,rs=(x<<1)^1;;
	int tmp=min(t0[ls],t1[rs]);
	t[x]=t[ls]+t[rs]+tmp,t0[x]=t0[ls]+t0[rs]-tmp,t1[x]=t1[ls]+t1[rs]-tmp;
}

void maketree(int x,int l,int r){
	if (l==r) {
		t0[x]=tmp[0][l],t1[x]=tmp[1][l],t[x]=0;
		return;
	}
	int mid=(l+r)>>1;
	maketree(x<<1,l,mid),maketree((x<<1)^1,mid+1,r);
	upd(x);
}

void del(int x,int l,int r,int p,int tp){
	if (l==r) {
		if (!tp) t0[x]--; else t1[x]--;
		return;
	}
	int mid=(l+r)/2;
	if (p<=mid) del(x<<1,l,mid,p,tp);
	else del((x<<1)^1,mid+1,r,p,tp);
	upd(x);
}

int find(int x,int l,int r,int tp,int d){
	if (l==r) return l;
	int mid=(l+r)/2,ls=x<<1,rs=(x<<1)^1;
	int res=max(d-t1[ls],0)+t0[ls];
	if (tp==1){
		if (t[rs]||min(res,t1[rs])) return find(rs,mid+1,r,tp,res);
		else return find(ls,l,mid,tp,d);
	} else {
		if (res<t1[rs]) return find(rs,mid+1,r,tp,res);
		else return find(ls,l,mid,tp,d);
	}
}

int main(){
//	freopen("game.in","r",stdin);
//	freopen("game.out","w",stdout);
	read(n);
	for(i=1;i<=n;i++) read(a[i]),A[i].x=a[i],A[i].i=i,A[i].tp=0;
	for(i=1;i<=n;i++) read(b[i]),A[i+n].x=b[i],A[i+n].i=i,A[i+n].tp=1;
	tot=n*2,sort(A+1,A+1+tot,cmp);
	for(i=1;i<=tot;i++){
		if (i==1||A[i].x!=A[i-1].x) cnt++,fr[cnt]=A[i].x;
		if (A[i].tp==0) a[A[i].i]=cnt,tmp[0][cnt]++;
		else b[A[i].i]=cnt,tmp[1][cnt]++;
	}
	maketree(1,0,cnt);
	for(i=1;i<=n;i++){
		int tmp=t[1];
		del(1,0,cnt,a[i],0); 
		if (t[1]==tmp){
			int ls0=find(1,0,cnt,1,0),ls1=find(1,0,cnt,2,0);
			if (ls0<=a[i]) k=ls1; else k=ls0;
		} else k=find(1,0,cnt,2,0);
		printf("%d ",fr[k]);
		del(1,0,cnt,k,1);
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章