GDOI 2016 Day2 T3 机密网络

Description

给出一个n个点n条边的无向连通图,每个点有点权Ai,每条边的边权都为1,求所有距离<=k的点对个数以及它们的权值乘积和。
n<=10^5,

Solution

很明显,原图是一个环套外向树。(就是一个环,外面长了一堆树)
如果没有多的那一条边,那么原题就很明显是经典点分治的题目了。
那我们可以先把环上的那一条边删掉,做点分治,然后再计算它对答案的影响。
这条边的影响就是删边前能够通过这条边而联通而删边后不行的点对。
这个似乎很难计算?
那么,我们可以用删边前可以的点对-删边前后都可以的点对。
怎么才能保证算出的点对是走了这条边?
设这条边是(a,b)
我们用da[i]表示i到a的距离,db[i]表示i到b的距离。(这里的距离指的就是删边后的)
那么就相当于把这张图分成了两个点集。
da[i] < db[i](A集)和da[i] > db[i](B集)。
只有在两个不同集合中的点才可以计算。
da[i]=db[i]的点对这个答案的计算没有影响,忽略掉它们。
具体原因画一画就知道了。
那么,删边前可以的点对直接用树状数组统计一下就好了。
把所有B集的点以db[i]为下标扔进去,对于每个A集的点,统计1~k-da[i]-1的所有B集点。
那么前后都可以呢?
我们可以在点分治里顺便统计。
也是用上面的方法,当指针扫到一个A集点的时候,统计一下当前树状数组里的答案就好了。
当然,排序可以用桶排,省掉一个log

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define N 100005
using namespace std;
typedef long long ll;
int n,k,l,x,z,st,en,Mid,len,sum,size[N],da[N],db[N];
int t[N*2],next[N*2],last[N],dis[N],v[N],a[N],b[N],d[N];
ll ans,cnt,num,tot,w[N],tr[N][2];
bool bz[N];
bool cmp(int x,int y) {return d[x]<d[y];}
void add(int x,int y) {t[++l]=y;next[l]=last[x];last[x]=l;}
int get() {
    char ch;
    while (!isdigit(ch=getchar()));
    int o=ch-48;
    while (isdigit(ch=getchar())) o=o*10+ch-48;
    return o;
}
void insert(int x,ll y,int z) {
    for(x++;x<=n;x+=x&(-x)) tr[x][z]+=y;
}
ll query(int x,int z) {
    ll ans=0;
    for(x++;x>0;x-=x&(-x)) ans+=tr[x][z];
    return ans; 
}
void find(int x,int y) {
    if (bz[x]) {st=y;en=x;return;}
    bz[x]=1;
    rep(i,x) if (t[i]!=y) find(t[i],x);
}
void dfs(int x,int y) {
    rep(i,x) if (t[i]!=y) db[t[i]]=db[x]+1,dfs(t[i],x);
}
void get_size(int x,int y) {
    size[x]=1;
    rep(i,x) if (t[i]!=y&&!bz[t[i]]) get_size(t[i],x),size[x]+=size[t[i]];
}
void get_heavy(int x,int y) {
    bool pd=1;
    rep(i,x) if (t[i]!=y&&!bz[t[i]]) {
        get_heavy(t[i],x);
        if (size[t[i]]>sum/2) pd=0;
    }
    if (sum-size[x]>sum/2) pd=0;
    if (pd) z=x;
}
void pre(int x,int y) {
    a[++a[0]]=x;b[++b[0]]=x;
    rep(i,x) if (t[i]!=y&&!bz[t[i]]) {
        d[t[i]]=d[x]+1;pre(t[i],x);
    }
}
void calc(int *x,int f) {
    sort(x+1,x+x[0]+1,cmp);ll sum=0;
    fo(i,1,x[0]) {
        sum+=w[x[i]];
        if (da[x[i]]>db[x[i]]) {
            insert(db[x[i]],w[x[i]],1);
            insert(db[x[i]],1,0);
        }
    }int j=x[0];
    fo(i,1,x[0]) {
        while (j&&d[x[j]]+d[x[i]]>k) {
            if (da[x[j]]>db[x[j]]) {
                insert(db[x[j]],-w[x[j]],1);
                insert(db[x[j]],-1,0);
            }
            sum-=w[x[j--]];
        }
        if (!j) break;
        tot+=(ll)j*f;num+=w[x[i]]*sum*f;
        if (j&&da[x[i]]<db[x[i]]) {
            cnt-=query(k-1-da[x[i]],0)*f;
            ans-=w[x[i]]*query(k-1-da[x[i]],1)*f;
        }
    }
    for(;j;j--) if (da[x[j]]>db[x[j]]) {
        insert(db[x[j]],-w[x[j]],1);
        insert(db[x[j]],-1,0);
    }
}
void work(int x) {
    get_size(x,0);sum=size[x];num=tot=0;
    get_heavy(x,0);bz[z]=1;b[0]=1;b[1]=z;d[z]=0;
    rep(i,z) if (!bz[t[i]]) {
        a[0]=0;d[t[i]]=1;
        pre(t[i],z);calc(a,-1);
    }
    calc(b,1);tot--;num-=w[z]*w[z];
    cnt+=tot/2;ans+=num/2;
    rep(i,z) if (!bz[t[i]]) work(t[i]);
}
void solve() {
    fo(i,1,n) if (da[i]>db[i]) insert(db[i],w[i],1),insert(db[i],1,0);
    fo(i,1,n) if (da[i]<db[i]) cnt+=query(k-1-da[i],0),ans+=w[i]*query(k-1-da[i],1);
}
int main() {
    freopen("pronet.in","r",stdin);
    freopen("pronet.out","w",stdout);
    n=get();k=get();
    fo(i,1,n) a[i]=get(),add(a[i],i),add(i,a[i]);
    fo(i,1,n) w[i]=get();
    find(1,0);l=0;memset(last,0,sizeof(last));
    fo(i,1,n) {
        if (i==st&&a[i]==en||i==en&&a[i]==st) continue;
        add(i,a[i]);add(a[i],i);
    }
    dfs(st,0);fo(i,1,n) da[i]=db[i];db[en]=0;
    dfs(en,0);memset(bz,0,sizeof(bz));
    work(1);solve();printf("%lld %lld",cnt,ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值