HHHOJ #1252. 「NOIP 2023 模拟赛 20230716 B」打怪兽 思考--zhengjun
赛时卡了很久,没想出来怎么做。
随便乱搞了个优先级队列,过拍直接走人,然后成为了唯一的 100 分,无语了。
完事后发现这样做确实是对的,而且好写,简单讲一下。
首先需要能够判断打两个怪 \(i,j\) 的优先级:
-
\(a_i\le b_i,a_j\le b_j\),则优先攻击 \(a\) 较小的那个;
-
\(a_i > b_i,a_j > b_j\),则优先攻击 \(b\) 较大的那个;
-
否则,优先攻击 \(a\le b\) 的那个。
由于有拓扑序的限制,所以考虑从下往上解决问题。
假设已经知道了 \(u\) 的儿子 \(v_1,v_2,\cdots,v_k\) 的攻击序列,且儿子的攻击序列均已有序。
那么显然,\(u\) 只需要把所有的攻击序列归并一下就完事了。
但是,此时需要在序列的最开头加入 \(u\),可能会破坏原来的顺序。
记 \(u\) 表示的怪为 \(x\),儿子归并后序列的第一项表示的怪为 \(y\)。
若 \(y<x\),说明攻击完 \(x\) 时,可以继续攻击比 \(x\) 还要优的怪。
这里有个隐含条件:攻击 \(x\) 代表着 \(x\) 已经是现有中最优的怪,而 \(y\) 比 \(x\) 优,所以攻击完 \(x\) 后一定会攻击 \(y\)。
那么我们不妨直接合并这两个怪,并把 \(y\) 移出原序列。
继续重复上述步骤直到 \(x\) 可以满足序列的单调性为止。
时间复杂度:\(O(n\log n)/O(n\log ^2 n)\),取决于写的是可并堆还是启发式合并。
代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e5+10;
int n,m;
struct zj{
ll a,b;
zj operator + (const zj &x)const{
if(x.a>b)return {a+x.a-b,x.b};
return {a,b+x.b-x.a};
}
bool operator < (const zj &x)const{
int t1=a<=b,t2=x.a<=x.b;
if(t1^t2)return t1;
if(t1)return a<x.a;
return b>x.b;
}
}a[N];
struct tree{
zj x;
int ls,rs,dis;
}t[N];
void merge(int &x,int y){
if(!x||!y){
x|=y;return;
}
if(t[y].x<t[x].x)swap(x,y);
merge(t[x].rs,y);
if(t[t[x].ls].dis<t[t[x].rs].dis)swap(t[x].ls,t[x].rs);
t[x].dis=t[t[x].rs].dis+1;
}
void pop(int &x){
int r1=t[x].ls,r2=t[x].rs;
merge(x=r1,r2);
}
int root[N];
vector<int>to[N];
void dfs(int u,int fa=0){
for(int v:to[u])if(v^fa){
dfs(v,u);
merge(root[u],root[v]);
}
for(;root[u]&&t[root[u]].x<a[u];pop(root[u])){
a[u]=a[u]+t[root[u]].x;
}
t[u]={a[u],0,0,1},merge(root[u],u);
}
int main(){
freopen("monster.in","r",stdin);
// freopen("monster.out","w",stdout);
scanf("%d",&n);
for(int i=2;i<=n;i++)scanf("%lld%lld",&a[i].a,&a[i].b);
for(int i=1,u,v;i<n;i++){
scanf("%d%d",&u,&v);
to[u].push_back(v),to[v].push_back(u);
}
dfs(1);
zj ans={0,0};
for(;root[1];pop(root[1]))ans=ans+t[root[1]].x;
cout<<ans.a;
return 0;
}