0x63树的直径与最近公共祖先

 

bzoj1999 先把树的直径求出来,从左往右枚举,对于当前位置i,找到满足限制并且最远的点j,当前位置最大值就是max(i~j区间内除直径外的子树路径长度最大值,1~i的长度,j~n的长度)

然而,对于树的直径有一个很有用的性质,1~i区间内除直径外的子树路径长度最大值必然不会比1~i的长度大,否则就不是直径了。j~n同理

所以可以把i~j区间转换成1~n区间,成为定值。求完树的直径后O(n)即可出解

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;

int n,S;
struct node
{
    int x,y,d,next;
}a[1100000];int len,last[510000];
void ins(int x,int y,int d)
{
    len++;
    a[len].x=x;a[len].y=y;a[len].d=d;
    a[len].next=last[x];last[x]=len;
}
int L,ld,R,rd;
void getL(int x,int fr,int d)
{
    if(L==0||d>ld)L=x,ld=d;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(y!=fr)
            getL(y,x,d+a[k].d);
    }
}
int pre[510000];
void getR(int x,int fr,int d)
{
    if(R==0||d>rd)R=x,rd=d;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(y!=fr)
        {
            pre[y]=k;
            getR(y,x,d+a[k].d);
        }
    }
}
bool v[510000];
int getmax(int x,int fr)
{
    int mx=0;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(v[y]==false&&y!=fr)
            mx=max(mx,getmax(y,x)+a[k].d);
    }
    return mx;
}

int ulen,u[510000],dis[510000];
int main()
{
//    freopen("core.in","r",stdin);
//    freopen("core.out","w",stdout);
    int x,y,dd;
    scanf("%d%d",&n,&S);
    len=0;memset(last,0,sizeof(last));
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&dd);
        ins(x,y,dd);ins(y,x,dd);
    }
    L=0;getL(1,0,0);
    R=0;getR(L,0,0);
    
    memset(v,false,sizeof(v));
    ulen=0;u[++ulen]=R;dis[ulen]=0;v[R]=true;
    int k=pre[R];
    while(a[k].x!=L)
    {
        u[++ulen]=a[k].x;
        dis[ulen]=a[k].d+dis[ulen-1];
        v[a[k].x]=true;
        k=pre[a[k].x];
    }
    u[++ulen]=a[k].x;
    dis[ulen]=a[k].d+dis[ulen-1];
    v[a[k].x]=true;
    
    int mx=0,j=1;
    for(int i=1;i<=ulen;i++)mx=max(mx,getmax(u[i],0));
    int mn=2147483647;
    for(int i=1;i<=ulen;i++)
    {
        while(j<ulen&&dis[j+1]-dis[i]<=S)j++;
        mn=min(mn,max(mx,max(dis[i],dis[ulen]-dis[j])));
    }
    printf("%d\n",mn);
    return 0;
}
bzoj1999

poj3417 环的思想很重要。对于一条非树边,可以理解成和树上路径构成一个环,若删掉一条树边,删掉当前这条非树边,树边能够选择的就是在x,y树上最短路的边。树上路径修改,采用树上差分的做法。此后,遍历每一条边,计算答案。对于只被1个非树边覆盖的树边,ans++,只被0个覆盖的,所有非树边都可以随意。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;

struct node
{
    int x,y,next;
}a[210000];int len,last[110000];
void ins(int x,int y)
{
    len++;
    a[len].x=x;a[len].y=y;
    a[len].next=last[x];last[x]=len;
}
int f[25][110000],dep[110000],Bin[30];
void dfs(int x)
{
    for(int i=1;dep[x]>=Bin[i];i++)f[i][x]=f[i-1][f[i-1][x]];
    
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(y!=f[0][x])
        {
            f[0][y]=x;
            dep[y]=dep[x]+1;
            dfs(y);
        }
    }
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y])swap(x,y);
    for(int i=22;i>=0;i--)
        if(dep[x]-dep[y]>=Bin[i])x=f[i][x];
    if(x==y)return x;
    for(int i=22;i>=0;i--)
        if(dep[x]>=Bin[i]&&f[i][x]!=f[i][y])x=f[i][x],y=f[i][y];
    return f[0][x];
}

int ans,m,d[110000];
void getans(int x)
{
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(y!=f[0][x])
            getans(y), d[x]+=d[y];
    }
    if(x!=1)
    {
        if(d[x]==0)ans+=m;
        else if(d[x]==1)ans++;
    }
}
int main()
{
    Bin[0]=1;for(int i=1;i<=22;i++)Bin[i]=Bin[i-1]*2;
    int n,x,y;
    scanf("%d%d",&n,&m);
    len=0;memset(last,0,sizeof(last));
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        ins(x,y);ins(y,x);
    }
    dfs(1);
    
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        d[x]++;d[y]++;
        d[LCA(x,y)]-=2;
    }
    
    ans=0;
    getans(1);
    printf("%d\n",ans);
    
    return 0;
}
poj3417

CH Round #56-C 对于当前出现的异象石,按照dfs序排序,对于相邻的点求距离(包括首尾),答案就是总距离/2。动态维护这个序列,就是平衡树啊。操作简单,什么set啊,或者线段树啊奇淫维护就好

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<set>
using namespace std;
typedef long long LL;

struct node
{
    int x,y,next;LL d;
}a[210000];int len,last[110000];
void ins(int x,int y,LL d)
{
    len++;
    a[len].x=x;a[len].y=y;a[len].d=d;
    a[len].next=last[x];last[x]=len;
}
int Bin[25];
int f[25][110000],dep[110000];
int z,ys[110000];LL d[110000];
void dfs(int x)
{
    for(int i=1;dep[x]>=Bin[i];i++)f[i][x]=f[i-1][f[i-1][x]];
    ys[x]=++z;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(y!=f[0][x])
        {
            f[0][y]=x;
            dep[y]=dep[x]+1;
            d[y]=d[x]+a[k].d;
            dfs(y);
        }
    }
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y])swap(x,y);
    for(int i=22;i>=0;i--)
        if(dep[x]-dep[y]>=Bin[i])x=f[i][x];
    if(x==y)return x;
    for(int i=22;i>=0;i--)
        if(dep[x]>=Bin[i]&&f[i][x]!=f[i][y])x=f[i][x],y=f[i][y];
    return f[0][x];
}
LL getdis(int x,int y){return d[x]+d[y]-d[LCA(x,y)]*2;}

struct myset
{
    int t,id;
    myset(){}
    myset(int T,int ID){t=T;id=ID;}
    friend bool operator>(myset s1,myset s2){return s1.t>s2.t;}
    friend bool operator<(myset s1,myset s2){return s1.t<s2.t;}
}L,R,mL,mR;
set<myset>s;
int main()
{
    Bin[0]=1;for(int i=1;i<=22;i++)Bin[i]=Bin[i-1]*2;
    int n,x,y;LL dd;
    scanf("%d",&n);
    len=0;memset(last,0,sizeof(last));
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%lld",&x,&y,&dd);
        ins(x,y,dd);ins(y,x,dd);
    }
    f[0][1]=0;z=0;d[1]=0;
    dfs(1);
    
    int Q;LL ans=0; char ss[10];
    scanf("%d",&Q);
    while(Q--)
    {
        scanf("%s",ss+1);
        if(ss[1]=='?')printf("%lld\n",ans/2);
        else if(ss[1]=='+')
        {
            scanf("%d",&x);
            if(!s.empty())
            {
                mL=*s.begin();mR=*--s.end();
                
                if(ys[x]<mL.t)L=mR;
                else L=*--s.lower_bound(myset(ys[x],x));
                
                if(mR.t<ys[x])R=mL;
                else R=*s.lower_bound(myset(ys[x],x));
                
                ans-=getdis(L.id,R.id);
                ans+=getdis(L.id,x);
                ans+=getdis(x,R.id);
            }
            s.insert(myset(ys[x],x));
        }
        else
        {
            scanf("%d",&x);
            s.erase(myset(ys[x],x));
            if(!s.empty())
            {
                mL=*s.begin();mR=*--s.end();
                
                if(ys[x]<mL.t)L=mR;
                else L=*--s.lower_bound(myset(ys[x],x));
                
                if(mR.t<ys[x])R=mL;
                else R=*s.lower_bound(myset(ys[x],x));
                
                ans+=getdis(L.id,R.id);
                ans-=getdis(L.id,x);
                ans-=getdis(x,R.id);
            }
        }
    }
    return 0;
}
异象石

bzoj1977 同样是环的思想。先求出一棵最小生成树,对于非树边,必然是从和树边构成的环中,断掉最大的。然而题目要求严格次小生成树,当前非树边边权可能和最大边权相等,因此需要多维护一个严格次大值。这个和倍增求最近公共祖先的原理是一样的。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;

struct node
{
    int x,y,next;LL d;
}a[210000];int len,last[110000];
void ins(int x,int y,LL d)
{
    len++;
    a[len].x=x;a[len].y=y;a[len].d=d;
    a[len].next=last[x];last[x]=len;
}
int Bin[30];
int f[25][110000],dep[110000];
LL mx[25][110000],sx[25][110000];
LL smx(LL m1,LL s1,LL m2,LL s2)
{
    LL ret=max(s1,s2);
    if(m1!=m2)ret=max(ret,min(m1,m2));
    return ret;
}
void dfs(int x)
{
    for(int i=1;Bin[i]<dep[x];i++)
    {
        f[i][x]=f[i-1][f[i-1][x]];
        
        int m1=mx[i-1][x],m2=mx[i-1][f[i-1][x]];
        int s1=sx[i-1][x],s2=sx[i-1][f[i-1][x]];
        
        mx[i][x]=max(m1,m2);
        sx[i][x]=smx(m1,s1,m2,s2);
    }
    
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(f[0][x]!=y)
        {
            f[0][y]=x;
            mx[0][y]=a[k].d;
            dep[y]=dep[x]+1;
            dfs(y);
        }
    }
}
void getmax(int x,int y,LL &mmax,LL &smax)
{
    if(dep[y]>dep[x])swap(x,y);
    
    for(int i=22;i>=0;i--)
        if(dep[x]-dep[y]>=Bin[i])
        {
            smax=smx(mmax,smax,mx[i][x],sx[i][x]);
            mmax=max(mmax,mx[i][x]);
            x=f[i][x];
        }
        
    for(int i=22;i>=0;i--)
        if(dep[x]>Bin[i]&&f[i][x]!=f[i][y])
        {
            smax=smx(mmax,smax,mx[i][x],sx[i][x]);
            smax=smx(mmax,smax,mx[i][y],sx[i][y]);
            mmax=max(mmax,max(mx[i][x],mx[i][y]));
            x=f[i][x],y=f[i][y];
        }
    smax=smx(mmax,smax,mx[0][x],sx[0][x]);
    smax=smx(mmax,smax,mx[0][y],sx[0][y]);
    mmax=max(mmax,max(mx[0][x],mx[0][y]));
}

struct edge{int x,y;LL d;}e[310000],lz[310000];
bool cmp(edge e1,edge e2){return e1.d<e2.d;}
int fa[110000];
int findfa(int x)
{
    if(x==fa[x])return x;
    fa[x]=findfa(fa[x]);return fa[x];
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%lld",&e[i].x,&e[i].y,&e[i].d);
    sort(e+1,e+m+1,cmp);
    
    for(int i=1;i<=n;i++)fa[i]=i;
    int tp=0;LL mind=0;
    for(int i=1;i<=m;i++)
    {
        int fx=findfa(e[i].x),fy=findfa(e[i].y);
        if(fx!=fy)
        {
            fa[fx]=fy;mind+=e[i].d;
            ins(e[i].x,e[i].y,e[i].d);
            ins(e[i].y,e[i].x,e[i].d);
        }
        else lz[++tp]=e[i];
    }
    m=tp;
    
    f[0][1]=0;
    memset(sx,-1,sizeof(sx));
    Bin[0]=1;for(int i=1;i<=22;i++)Bin[i]=Bin[i-1]*2;
    dep[1]=1;dfs(1);
    
    LL ans=(1LL<<62);
    for(int i=1;i<=m;i++)
    {
        LL mmax=-(1LL<<62),smax=-(1LL<<62); getmax(lz[i].x,lz[i].y,mmax,smax);
        if(mmax==lz[i].d)
        {
            if(smax!=-1)
                ans=min(ans,mind-smax+lz[i].d);
        }
        else ans=min(ans,mind-mmax+lz[i].d);
    }
    printf("%lld\n",ans);
    return 0;
}
bzoj1977

NOIP2009 D2T3

容易想到二分答案。对于军队而言,必然是越向上控制的叶子节点越多,那么尽量向上。先不管根,假如军队无法到达根的子节点,那么根据前面的贪心位置确定。否则,有两种情况:1、留在当前子节点,2、去往根的另一个孩子。

此时,题目就转化成:给你n个点,m个军队,军队有处在的点,有权值,点亦有权。军队可以占领自己的位置,或者占领点权+自己处于的点的点权<=自身权值的位置。

瓶颈就在于我可以留在当前的点,没有花费,特殊性造成的影响就是可能一个军队可能从一个点出发,却又回到这个点,相当于点权耗费两次,判断是否能够到达自己会出错。这样一来就无法把军队集中,统一贪心地分配。

如何去掉这个限制?对于一个军队,假如它的权值<=所处点权*2,必然不会去其他点。因为去,也只能去比当前点权值小的点,然而你还需要另一支军队占领当前点,既然这支军队可以占领当前点,那么也一定可以占领前一支军队占领的点。

处理完这个以后,我们就能够保证,即使点权耗费两次,而当前点的军队的权都是>二倍点权的,就不会出现不能到达自己的情况。由此就可以忽略第1个限制了。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;

struct node
{
    int x,y,next;LL d;
}a[110000];int len,last[51000];
void ins(int x,int y,LL d)
{
    len++;
    a[len].x=x;a[len].y=y;a[len].d=d;
    a[len].next=last[x];last[x]=len;
}
int Bin[25];LL d[25][51000];
int f[25][51000],dep[51000],son[51000];
void dfs(int x)
{
    for(int i=1;Bin[i]<dep[x];i++)
    {
        d[i][x]=d[i-1][x]+d[i-1][f[i-1][x]];
        f[i][x]=f[i-1][f[i-1][x]];
    }
    
    son[x]=0;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(y!=f[0][x])
        {
            son[x]++;
            dep[y]=dep[x]+1;
            f[0][y]=x;
            d[0][y]=a[k].d;
            dfs(y);
        }
    }
}

int m,p[51000],s[51000];bool v[51000],b[51000];
struct stack{LL d;int id;}sta[51000];int top;
int arlen,ctlen;LL ar[51000],ct[51000];
bool check(LL mid)
{
    memset(v,false,sizeof(v));
    for(int k=last[1];k;k=a[k].next)b[a[k].y]=false;
    memcpy(s,son,sizeof(s));
    top=0;
    for(int i=1;i<=m;i++)
    {
        int x=p[i];LL w=mid;
        for(int j=22;j>=0;j--)
            if(dep[x]>Bin[j]&&w>=d[j][x]&&f[j][x]!=1)w-=d[j][x],x=f[j][x];
            
        if(f[0][x]!=1)
        {
            bool bk=false;
            for(int k=x;k!=1;k=f[0][k])if(v[k]==true){bk=true;break;}
            if(bk==false)
            {
                v[x]=true;
                s[f[0][x]]--;
                while(s[f[0][x]]==0)
                {
                    v[f[0][x]]=true;
                    x=f[0][x],s[f[0][x]]--;
                    if(f[0][x]==1){b[x]=true;break;}
                }
            }
        }
        else sta[++top].d=w,sta[top].id=x;
    }
    
    arlen=0;
    for(int i=1;i<=top;i++)
        if(b[sta[i].id]==false)
        {
            if(d[0][sta[i].id]*2>=sta[i].d)b[sta[i].id]=true;
            else ar[++arlen]=sta[i].d-d[0][sta[i].id];
        }
        else ar[++arlen]=sta[i].d-d[0][sta[i].id];
    sort(ar+1,ar+arlen+1);
    
    ctlen=0;
    for(int k=last[1];k;k=a[k].next)
        if(b[a[k].y]==false)ct[++ctlen]=a[k].d;
    sort(ct+1,ct+ctlen+1);
    
    int j=1;
    for(int i=1;i<=ctlen;i++)
    {
        while(j<arlen&&ar[j]<ct[i])j++;
        if(ar[j]<ct[i])return false;
        j++;
        if(j>arlen&&i<ctlen)return false;
    }
    return true;
}
int main()
{
    int n,x,y;LL dd;
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%lld",&x,&y,&dd);
        ins(x,y,dd);ins(y,x,dd);
    }
    Bin[0]=1;for(int i=1;i<=22;i++)Bin[i]=Bin[i-1]*2;
    f[0][1]=0;dep[1]=1;dfs(1);
    
    scanf("%d",&m); if(son[x]>m){printf("-1\n");return 0;}
    for(int i=1;i<=m;i++)scanf("%d",&p[i]);
    
    LL l=1,r=5*1e13,ans;
    while(l<=r)
    {
        LL mid=(l+r)/2;
        if(check(mid))
        {
            ans=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}
疫情控制

 

转载于:https://www.cnblogs.com/AKCqhzdy/p/9536546.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值