CF533A——题解

13 篇文章 0 订阅
2 篇文章 0 订阅
一道搞了N天的神仙题

题目大意

你现在有M个货物要从根节点运到树上各节点(每个节点只能放一个),每个节点有高度 h [ i ] h[i] h[i],每个货物都有一个高度 B [ i ] B[i] B[i],所以这个货物经过的所有山洞,都不能低于 B [ i ] B[i] B[i]
你现在可以改变一个山洞的高度,问最少增加多少,使得所有货物都能运出去

题解

  • 首先来考虑不开凿情况

    设第 i i i个能放进 n u m [ i ] num[i] num[i]个山洞,那么显然满足条件的情况为 ∀ n u m [ i ] > = i \forall num[i]>=i num[i]>=i,即 ∀ n u m [ i ] − i > = 0 \forall num[i]-i>=0 num[i]i>=0
    这东西还是不好维护,再转化一下,就是 m i n ( n u m [ i ] − i ) > = 0 min(num[i]-i)>=0 min(num[i]i)>=0
    ⟶ \longrightarrow 这就可以用线段树方便地维护了

  • 接下来考虑开凿

    我们可以二分枚举每一个节点开凿的高度,然后check上面的条件
    当一个 h [ i ] h[i] h[i]改变时,对于 n u m num num的影响是一个区间 [ 1 , k ] [1,k] [1,k],在 B [ i ] B[i] B[i](有序)上二分得出这个k,在线段树上修改,之后再改回来即可
    复杂度 O ( N ∗ l o g 2 ) O(N*log^2) O(Nlog2)
    然后拼命卡常,仍然卡不进时限。。

  • 继续优化

    线段树的log基本是优不掉了,那能不能把节点的二分给去掉呢?
    考虑一个元素 m a x ( B [ i ] ) ∈ { n u m [ i ] − i &lt; 0 } max(B[i])\in \{num[i]-i&lt;0\} max(B[i]){num[i]i<0}
    对于这个东西,在有解的情况下,一定会有一个山洞被改成 B [ i ] B[i] B[i]

    若改B[k]>B[i],则其他B[k]一定是放满的,对于B[i]没有影响
    若改B[k]<B[i],那么只可能让num[i]变得更小,也没用

    所以,只需要枚举每个节点让它变成 B [ i ] B[i] B[i]即可
    复杂度 O ( N ∗ l o g ) O(N*log) O(Nlog)
    普通递归式线段树常数巨大,仍然跑得有点慢。。

  • 小Trick

    我们在构造时要构造出

    Min[i]:到根节点路径的最小值
    Min_[i]:到根节点路径的次小值(非严格,更改时要用)
    id[i]:最小值的节点标号

    那么我们贪心想法,按 M i n [ i ] Min[i] Min[i]从大到小的顺序枚举节点(此时可行解越来越大)选择
    并且只选择id[i]==i且Min_[i]>Min[i]的节点进行尝试——只有这些节点才会真正产生影响
    然后就跑得比较快乐了~~

Code略长~~

#include<bits/stdc++.h>
#define gt() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
#define pt(ch) (Top<1000000?St[Top++]=ch:(Out(),St[(Top=0)++]=ch))
#define Out() (fwrite(St,1,Top,stdout))
#define __R register
#define IL inline
using namespace std;
int Top;static char St[1000000],buf[1000000],*p1=buf,*p2=buf;
const int maxn=(5e5)+5,INF=2e9;
int N,Ans,M,Bas,B[maxn],h[maxn];bool vis[maxn];
struct Vector{
    int tot,lnk[maxn],nxt[maxn<<1],son[maxn<<1];
    IL void add_e(int x,int y){son[++tot]=y,nxt[tot]=lnk[x],lnk[x]=tot;}
}T,G;
IL int read(){
    __R int ret=0;__R char ch=gt();
    while(ch<'0'||ch>'9') ch=gt();
    while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=gt();
    return ret;
}
void write(int x){if(x>9) write(x/10);pt(x%10+'0');}
//SegmentTree
int tag[maxn<<2],P[maxn<<2],Mid[maxn<<2];
#define Ls (i<<1)
#define Rs (Ls|1)
IL int Min(int x,int y){return x<y?x:y;}
IL void Add(int i,int p){P[i]+=p,tag[i]+=p;}
IL void Down(int i){Add(Ls,tag[i]),Add(Rs,tag[i]),tag[i]=0;}
IL void Updata(int i){P[i]=Min(P[Ls],P[Rs]);}
void Build(int i=1,int L=1,int R=M){
    if(L==R) return (void)(P[i]=L-M-1);
    Mid[i]=L+R>>1;
    Build(Ls,L,Mid[i]),Build(Rs,Mid[i]+1,R),Updata(i);
}
void Modify(int x,int y,int p,int i=1,int L=1,int R=M){
    if(x<=L&&R<=y) return (void)(Add(i,p));
    if(tag[i]) Down(i);
    if(x<=Mid[i]) Modify(x,y,p,Ls,L,Mid[i]);if(y>Mid[i]) Modify(x,y,p,Rs,Mid[i]+1,R);
    Updata(i);
}
int Query(int x,int i=1,int L=1,int R=M){
    if(L==R) return P[i];
    if(tag[i]) Down(i);
    return x<=Mid[i]?Query(x,Ls,L,Mid[i]):Query(x,Rs,Mid[i]+1,R);
}
//只要尝试将所有洞改成max(Bi)∈{num[i]-i<0},因为在有解时,一定要有一个洞被凿成这个Bk 
void Find(){for(__R int i=M;i;i--) if(Query(i)<0){Bas=B[i];break;}}
//Build
int A1[maxn],A2[maxn],out[maxn],fuk[maxn];
void Dfs(int x,int fa,int id,int Mi,int Mi_){
    if(h[x]<=Mi) Mi_=Mi,Mi=h[x],id=x;else if(h[x]<Mi_) Mi_=h[x];
    A1[x]=Mi,A2[x]=Mi_,T.add_e(id,x),fuk[x]=id;
    for(__R int j=G.lnk[x];j;j=G.nxt[j]) if(G.son[j]!=fa) Dfs(G.son[j],x,id,Mi,Mi_);
}
IL int Calc(int x,int p){
    __R int L=1,R=M,mid;
    while(L<=R) if(B[mid=L+R>>1]<=x) L=mid+1;else R=mid-1;
    if(R>=1) Modify(1,R,p);
    return R;
}
int X[maxn],Y[maxn];
IL void Solve(int x){
    __R int lst=h[x];h[x]=Bas;
    for(__R int j=T.lnk[x];j;j=T.nxt[j]) X[j]=Calc(A1[x],-1),Y[j]=Calc(Min(A2[T.son[j]],h[x]),1);
    if(P[1]>=0) Ans=Bas-lst;
    for(__R int j=T.lnk[x];j;j=T.nxt[j]){
        if(X[j]>=1) Modify(1,X[j],1);
        if(Y[j]>=1) Modify(1,Y[j],-1);
    }
    h[x]=lst;
}
//XG
int len,id[maxn];
struct ff{
    int x,y;
    IL bool operator <(const ff b)const{return y<b.y;}
}hep[maxn];
IL void Put(int x,int y){hep[++len]=(ff){x,y},push_heap(hep+1,hep+1+len);}
IL ff Get(){return pop_heap(hep+1,hep+1+len),hep[len--];}
IL void ReBuild(){
    int cnt=M;len=0,Put(1,h[1]);
    while(len&&cnt){
        __R ff i=Get();vis[i.x]=1,id[++id[0]]=i.x;
        if(B[cnt]>i.y) break;
        cnt--;
        for(int j=G.lnk[i.x];j;j=G.nxt[j]) if(!vis[G.son[j]]) Put(G.son[j],Min(i.y,h[G.son[j]]));
    }
    while(len) vis[id[++id[0]]=Get().x]=1;
}
int main(){
    N=read();
    for(__R int i=1;i<=N;i++) h[i]=read();
    for(__R int i=1;i<N;i++){
        int x=read(),y=read();
        G.add_e(x,y),G.add_e(y,x);
    }
    if((M=read())>N) return puts("-1"),0;
    for(__R int i=1;i<=M;i++) B[i]=read();sort(B+1,B+1+M),Ans=B[M];
    Build(),Dfs(1,0,0,INF,INF),ReBuild();
    for(__R int i=1;i<=N;i++) Calc(A1[i],1);
    if(P[1]>=0) Ans=0;
      else{
      	Find();
      	for(int i=1,x;i<=id[0];i++) if(x=id[i],Bas>h[x]&&Bas-h[x]<Ans&&fuk[x]==x&&A2[x]>A1[x]) Solve(x);
      }
    if(Ans<B[M]) printf("%d\n",Ans);else puts("-1");
    return Out(),0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值