题解[Loj2727舞会]

题意描述

\(n\)个数,其中有\(m\)个位置的数是确定的,另外的数随意排列。每次操作把最前面三个数取出,把它们的中位数取出来放到最后,然后删掉这三个数。通过合适的排列,使最后留下来的数最大。

Sol

首先这类有关中位数的问题,可以二分后转化为操作\(01\)序列的问题。每次二分一个有可能的答案\(mid\),把\(>=mid\)换成一,\(<mid\)的数换成零,位置确定的直接换,位置没确定的统计一的个数,考虑\(DP\)解决。

注意到每个位置对应的下一步操作呈现出树的结构,三叉树。简单来说,我们可以每次新建一个节点(从\(n+1\)开始),这个节点的三个儿子就是序列前三个数的位置,反复操作,直到建出根节点。

\(dp[i]\)\(i\)节点若要能合成\(1\)(满足最后剩下的数\(>=mid\))最少需要的“1”的个数。那转移的时候,就只要先把三个儿子的\(dp\)求出来,因为只要有两个儿子的变成\(1\) ,父节点就可以变成\(1\),所以选儿子中较小的两个就可以啦。

Code

#include<bits/stdc++.h>
#define N (200010)
using namespace std;
int n,m,tot,ans,q[N],a[N],pos[N],st[N];
int s1[N],s2[N],s3[N],dp[N];
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline void build(int num){
	tot=num;
	for(int i=0;i<=num-1;i++) q[i]=i+1;
	while(num!=1){
		int left=num%3,now,next=0;
		for(now=0;now<num-left;now+=3){
			++tot;
			s1[tot]=q[now];
			s2[tot]=q[now+1];
			s3[tot]=q[now+2];
			q[left+next]=tot;
			next++;
		}
		for(int i=0;now<num;now++,i++) q[i]=q[now];
		num=num%3+num/3;
	}
  //建树
	return;
}
inline void dfs(int x){
	if(x<=n) return;
	dfs(s1[x]),dfs(s2[x]),dfs(s3[x]);
	dp[x]=dp[s1[x]]+dp[s2[x]]+dp[s3[x]]-max(dp[s1[x]],max(dp[s2[x]],dp[s3[x]]));
	return;
}
inline bool judge(int val){
	int pd=0;//provide
	for(int i=m+1;i<=n;i++) if(a[i]>=val) pd++;
	for(int i=1;i<=n;i++) dp[i]=1;
	for(int i=1;i<=m;i++){
		if(a[i]>=val) dp[pos[i]]=0;//已经可以了
		else dp[pos[i]]=1e7;//还不行
	}
	dfs(tot);//dp根节点
	return dp[tot]<=pd;//看根节点需要的1的数量是否小于提供的1的数量
}
int main(){
	n=read(),m=read();
	build(n);
	for(int i=1;i<=m;i++) st[i]=a[i]=read(),pos[i]=read();
	for(int i=m+1;i<=n;i++) st[i]=a[i]=read();
	sort(st+1,st+1+n);
	int l=1,r=n;
	while(l<=r){
		int mid=(l+r)>>1;
		if(judge(st[mid])) l=mid+1,ans=st[mid];
		else r=mid-1;
	}
	printf("%d\n",ans);
	return 0;
}

完结撒花❀

posted @ 2021-02-22 16:52  xxbbkk  阅读(49)  评论(0编辑  收藏  举报