BJ 集训测试3 Problem C 灵大会议

http://www.elijahqi.win/archives/2962
题意:给定n座城市n-1条边 每个城市有a[i]个精灵 然后要求集中到一个点开会求人数乘距离最小 距离:每条边都是1

范围1e5

树状数组 区间覆盖变成单点修改 因为可以知道这个答案是一个单峰函数 可以知道已知带权中位数
(二)带权中位数
接着上面的题目,如果每个人都有一个权值,它们移动到一点的花费为距离与权值的乘积,那么这个时候怎么选点使总花费最小呢?
其实权值就可以看成是这个点上总的人数,仍然要找所有人的中点。那么这个问题就转化为了按坐标排序后,从一段扫描,把扫描过的点的权值相加记为sum。一旦遇到了一个点加上之后sum大于了总权值的一半,那么这个点就是所求的点。
也给出另外一种证明:
我们可以简单地把上面的证明过程看作是左边的人都集合到了M点,而右边的人都集合到了M+1点。此时形成了两军对垒的形式,如果左边的总人数比右边的多,那么从左边走到右边去就没有从右边走到左边来优,反之亦然。那么既然在当前点我们左边的总人数已经比右边多了,那么再往右边移动,左边的人数会进一步增多,而右边的人会减少,那么只会导致更差的结果,所以此时我们可以判断最优点一定在当前点的左边,或者至少在当前这个点。那么范围就从当前的[L,R]缩小到了[L,M],通过不断地缩小范围(而每一次缩小我们都砍掉了一半的范围),最后我们得到的将是一个点——那就是我们要求的集合位置。

那么可以维护一些信息我这个点到根路径上的点权之和
再维护一个信息 就是我这个点到根的路径上的所有点*距离的和 存下来
那么我每次二分一下我找的这个带权中位数在哪里 把路径拆成三段分别询问即可

#include<cstdio>
#include<algorithm>
#define N 200000
#define ll long long
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gc();}
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=gc();
    return x*f;
}
struct node{
    int y,next,z;
}data[N<<1];
int h[N],mn[N<<1][20],pos[N<<1],in[N],out[N],Log[N<<1],fa[N][20],dep[N],a[N],tot,num,n;
ll dis[N],s1[N],s2[N];
inline void dfs(int x){
    mn[++tot][0]=x;pos[x]=tot;in[x]=++num;
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y;if (y==fa[x][0]) continue;fa[y][0]=x;
        dep[y]=dep[x]+1;dis[y]=dis[x]+data[i].z;
        for (int j=1;j<=Log[dep[y]];++j) fa[y][j]=fa[fa[y][j-1]][j-1];
        dfs(y);mn[++tot][0]=x;
    }out[x]=num;
}
inline void init(){
    for (int j=1;j<=Log[tot];++j)
        for (int i=1;i+(1<<j)<=tot+1;++i)
        mn[i][j]=dep[mn[i][j-1]]<dep[mn[i+(1<<j-1)][j-1]]?mn[i][j-1]:mn[i+(1<<j-1)][j-1];
}
inline int lca(int x,int y){
    x=pos[x],y=pos[y];if (x>y) swap(x,y);int t=Log[y-x+1];
    return dep[mn[x][t]]<dep[mn[y-(1<<t)+1][t]]?mn[x][t]:mn[y-(1<<t)+1][t];
}
inline void add1(int x,ll v){for (int i=x;i<=n;i+=i&(-i)) s1[i]+=v;}
inline void add2(int x,ll v){for (int i=x;i<=n;i+=i&(-i)) s2[i]+=v;}
inline ll qr1(int x){ll tmp=0;while(x) tmp+=s1[x],x-=x&(-x);return tmp;}
inline ll qr2(int x){ll tmp=0;while(x) tmp+=s2[x],x-=x&(-x);return tmp;}
inline ll qs1(int x,int y){
    int t=lca(x,y);
    return qr1(in[x])+qr1(in[y])-(qr1(in[t])<<1)+a[t];
}
inline ll qs2(int x,int y){
    int t=lca(x,y);
    return qr2(in[x])+qr2(in[y])-(qr2(in[t])<<1)+dis[t]*a[t];
}
inline void change(int x,int v){
    add1(in[x],v-a[x]);add1(out[x]+1,a[x]-v);
    add2(in[x],dis[x]*(v-a[x]));add2(out[x]+1,dis[x]*(a[x]-v));a[x]=v;
}
inline ll query(int x,int y){
    ll sum=qs1(x,y);int t=lca(x,y);ll sum1=qs1(x,t),sum2=qs1(y,t);
    ll summ=sum+1>>1;bool flag=0;int m;
    if (a[x]>=summ) m=x;else if(a[y]>=summ) m=y,flag=1;else
    if (sum1>sum2){
        int xx=x;for (int i=Log[dep[x]];~i;--i) 
        if (dep[fa[xx][i]]>=dep[t]&&qs1(x,fa[xx][i])<summ) xx=fa[xx][i];
        m=fa[xx][0];
    }else{
        flag=1;
        int yy=y;for (int i=Log[dep[y]];~i;--i) 
        if (dep[fa[yy][i]]>=dep[t]&&qs1(y,fa[yy][i])<summ) yy=fa[yy][i];
        m=fa[yy][0];
    }if (flag) swap(x,y);ll ans=0;ll t1=qs1(t,y),t2=qs1(m,t),t3=dis[m]-dis[t];
    ans+=qs2(y,t)-dis[t]*t1;ans+=qs2(x,m)-dis[m]*qs1(x,m);
    ans+=(ll)t2*t3-qs2(m,t)+dis[t]*t2;ans+=t1*t3;ans-=(ll)a[t]*t3;
    return ans;
}
int main(){
    freopen("meeting.in","r",stdin);
    freopen("meeting.out","w",stdout);
    n=read();for (int i=1;i<=n;++i) a[i]=read();Log[0]=-1;
    for (int i=1;i<=n<<1;++i) Log[i]=Log[i>>1]+1;
    for (int i=1;i<n;++i){
        int x=read(),y=read(),z=read();
        data[++num].y=y;data[num].next=h[x];data[num].z=z;h[x]=num;
        data[++num].y=x;data[num].next=h[y];data[num].z=z;h[y]=num;
    }num=0;dfs(1);init();for (int i=1;i<=n;++i){int tmp=a[i];a[i]=0;change(i,tmp);}int m=read();
    while(m--){
        int op=read(),x=read(),y=read();
        if (op==1) printf("%lld\n",query(x,y));else change(x,y);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值