HDU5575 Discover Water Tank(启发式合并堆,2015ICPC上海)
题意:
给出一些水槽。
用一些高度不同的隔板把水槽隔开。
在水槽的不同位置和不同高度,有一些探测器,探测器可能返回有水或没水。
询问是否存在一种放水方案,使得尽可能多的探测器说真话。
放水方案需满足连通器原理。
做法:
从小到大枚举隔板,将隔板两侧的集合合并,对每个集合维护:
1)f[i]:表示这个集合内部所有水槽全部溢出的情况下的最优解。
2)g[i]:表示这个集合内部的水槽有些还被分隔的情况下的最优解。
合并的过程中,从小到大枚举两边集合内部的探测器,模拟水位的上升,得出最优水位时的情况和完全溢出的情况对答案的影响,同时把已经被水位覆盖的探测器取出。
设t1,t2为完全溢出后的影响,设sum1,sum2为某个水位下的最佳情况的影响。
有:
ft=f[L]+f[R]+t1+t2;
gt=max(g[L],f[L]+sum1)+max(g[R],f[R]+sum2);
ft和gt为合并后的f值和g值。
注意,最后整个水槽完全溢出后,可能还有一些上方的探测器,需要再做一遍模拟。
每次取最小值这个需求,用优先队列即可。
每次合并两个队列,网上很多题解采用左偏树的方法,这里我采用的是启发式合并,好写很多。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q[maxn];
int father[maxn];
int findfather (int x) {
int a=x;
while (x!=father[x]) x=father[x];
while (a!=father[a]) {
int z=a;
a=father[a];
father[z]=x;
}
return x;
}
int _,n,m;
int h[maxn],p[maxn];
int g[maxn];//这个集合部分不合并的最大值
int f[maxn];//这个集合全部合并的最大值
int sz[maxn];
int main () {
scanf("%d",&_);
for (int k=1;k<=_;k++) {
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) father[i]=i,sz[i]=0;
for (int i=1;i<=n;i++) while (q[i].size()) q[i].pop();
for (int i=1;i<n;i++) scanf("%d",h+i),p[i]=i;
int ans=0;
for (int i=1;i<=m;i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if (z==0) sz[x]++;
y++;
q[x].push({y,z});
}
for (int i=1;i<=n;i++) f[i]=sz[i],g[i]=sz[i];//初始化未合并的值
sort(p+1,p+n,[&](int x,int y) {
return h[x]<h[y];
});
for (int i=1;i<n;i++) {
int sum1=0,t1=0;
int L=findfather(p[i]);
int R=findfather(p[i]+1);
while (q[L].size()) {
if (q[L].top().first>h[p[i]]) break;
if (q[L].top().second==0) {
t1--;
}
else {
t1++;
}
sum1=max(sum1,t1);
q[L].pop();
}
int sum2=0,t2=0;
while (q[R].size()) {
if (q[R].top().first>h[p[i]]) break;
if (q[R].top().second==0) {
t2--;
}
else {
t2++;
}
sum2=max(sum2,t2);
q[R].pop();
}
//printf("%d %d\n",sum1,sum2);
int ft,gt;//合并后新的根的f值、g值
ft=f[L]+f[R]+t1+t2;
gt=max(g[L],f[L]+sum1)+max(g[R],f[R]+sum2);
if (q[L].size()<q[R].size()) {
father[L]=R;
while (q[L].size()) q[R].push(q[L].top()),q[L].pop();
f[R]=ft;
g[R]=gt;
}
else {
father[R]=L;
while (q[R].size()) q[L].push(q[R].top()),q[R].pop();
f[L]=ft;
g[L]=gt;
}
}
int u=findfather(1);
int sum1=0,t1=0;
while (q[u].size()) {
if (q[u].top().second==0) t1--;
else t1++;
sum1=max(sum1,t1);
q[u].pop();
}
ans=max(f[u]+sum1,g[u]);
printf("Case #%d: %d\n",k,ans);
}
}