题解[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;
}