雨天的尾巴

题目大意

给定一棵n个点的树。有m个操作,每个操作给出x,y,z,然后对于点x 到点y 的路径上(含x 和y) 每个点对应数值为z的数的个数+1。
最后输出每个点个数最多的数的数值(个数相同输出数值较小值)。

数据范围 n,m <= 100000,z<=10^9。

树链剖分,复杂度O( nlogn2 )

(werkeytom大神用线段树合并O(n log n)解决)
z比较大,但最多也就m种,所以我们先离散化z。
首先考虑序列上的做法,对于一个操作x,y,z(离散化后的),我们拆成两个操作
1:a=x,z,b=1;2:a=y+1,z,b=-1。然后把操作按a值排序。
然后我们O(n)扫一遍每个位置,把每个位置上的操作都处理掉,每个操作为数值为z的数的个数+b,用线段树维护数值为z的数的个数,那么当前位置的答案我们可以直接得出。修改操作复杂度为O(log n),总复杂度O(n log n)。
树上的怎么做呢?我们可以树链剖分一下,每条链都是一段序列,所以我们可以把操作拆开,一个操作最多拆log n次,然后剩下的按序列上的做,总时间O( nlogn2 )

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=100000+5;
int tree[maxn*4],p[maxn],tr[maxn*4],t,n,m,k[maxn],c[maxn],next[maxn*2],g[maxn*2],num,s[maxn],son[maxn],b[maxn],a[maxn],qx[maxn],qy[maxn],ans[maxn],q[maxn],fa[maxn],f[maxn],d[maxn],bz[maxn],bk[maxn],cnt[maxn*36][3];
void add(int x,int y){
    next[++num]=k[x];
    k[x]=num;
    g[num]=y;
}
void read(int &n){
    char ch=getchar();
    while(ch!='-'&&(ch<'0')||(ch>'9'))ch=getchar();n=0;
    int q=1;if (ch=='-') ch=getchar(),q=-1;
    while(ch>='0'&&ch<='9')n=n*10+ch-'0',ch=getchar();n=n*q;
}
void dfs(int x,int y){
    int i=k[x],s1=0;
    s[x]=1;fa[x]=y;f[x]=x;
    while (i>0){
        if (g[i]!=y) {
            d[g[i]]=d[x]+1;
            dfs(g[i],x);
            s[x]+=s[g[i]];
            if (s[g[i]]>s1) s1=s[g[i]],son[x]=g[i];
        }
        i=next[i];
    }
}
void df(int x,int y){
    if (son[y]==x) f[x]=f[y];
    b[x]=++num;
    a[num]=x;
    if (son[x]>0) df(son[x],x);
    int i=k[x];
    while (i>0){
        if (g[i]!=y&&son[x]!=g[i]) df(g[i],x);
        i=next[i];
    }
}
void qs(int l,int r){
    int i=l,j=r,m=q[(l+r)/2];
    while (i<=j){
        while (q[i]<m) i++;
        while (q[j]>m) j--;
        if (i<=j){
            swap(q[i],q[j]);
            swap(qx[i],qx[j]);
            swap(qy[i],qy[j]); 
            i++;j--; 
        } 
    } 
    if (i<r) qs(i,r);
    if (l<j) qs(l,j); 
}
void qss(int l,int r){
    int i=l,j=r,m=cnt[(l+r)/2][1];
    while (i<=j){
        while (cnt[i][1]<m) i++;
        while (cnt[j][1]>m) j--;
        if (i<=j){
            swap(cnt[i],cnt[j]);
            i++;j--; 

        } 
    } 
    if (i<r) qss(i,r);
    if (l<j) qss(l,j); 
}
void put(int k,int l,int r,int x,int y){
    if (l==r) {
        tree[k]+=y;
        tr[k]=p[l];
        return;
    } 
    int m=(l+r)/2;
    if (x<=m) put(k*2,l,m,x,y);else
    put(k*2+1,m+1,r,x,y);
    if (tree[k*2]>=tree[k*2+1]) {
        tree[k]=tree[k*2];
        tr[k]=tr[k*2];
    }else{
        tree[k]=tree[k*2+1];
        tr[k]=tr[k*2+1];
    }
}
int main(){
    read(n);read(m);num=0;
    for (int i=1;i<n;i++){
        int x,y;read(x);read(y);
        add(x,y);add(y,x);
    }
    dfs(1,0);num=0;
    df(1,0);
    for (int i=1;i<=m;i++) read(qx[i]),read(qy[i]),read(q[i]);
    qs(1,m);q[0]=0;t=0; 
    for (int i=1;i<=m;i++) if (q[i]>q[i-1]) c[i]=++t,p[t]=q[i];else c[i]=t;     
    num=0;
    for (int i=1;i<=m;i++){
        int x=qx[i],y=qy[i];
        while (f[x]!=f[y]){
            if (d[f[x]]<d[f[y]]) swap(x,y);
            num++;cnt[num][0]=c[i];
            cnt[num][1]=b[f[x]];cnt[num][2]=1;
            num++;cnt[num][0]=c[i];
            cnt[num][1]=b[x]+1;cnt[num][2]=-1; 
            x=fa[f[x]];
        } 
        if (d[x]<d[y]) swap(x,y);
        num++;cnt[num][0]=c[i];
        cnt[num][1]=b[y];cnt[num][2]=1;
        num++;cnt[num][0]=c[i];
        cnt[num][1]=b[x]+1;cnt[num][2]=-1;
    } qss(1,num);int j=1;
    for (int i=1;i<=n;i++){
        while (j<=num&&cnt[j][1]==i) put(1,1,t,cnt[j][0],cnt[j][2]),j++;
        if (tree[1]>0)ans[a[i]]=tr[1];else ans[a[i]]=0;
    }
    for (int i=1;i<=n;i++) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值