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
#include
#include
#include
#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);
	}
}

你可能感兴趣的:(题解,线段树,贪心)