【树链的交】ICPC Dhaka Regional 2018 - F - Path Intersection

题目链接https://codeforces.com/gym/102040/attachments


题意

给出一棵树,有Q个询问,每次询问K条路径,问这K条路径都经过的点有多少个。


题解

第一眼树剖裸题,怕时间复杂度背卡,选择LCA分类讨论。疯狂wa之后,才明白这是不可能讨论出来的。

然后想了个被卡时间的方法:维护一个条可行链(x,y),每次加入一条链(nx,ny)求交。先把nx和ny尽可能缩到链(x,y)上,这样会少很多情况。
缩完以后新链的端点只会在6个点出现:两条链的端点和两条链本身的lca。
枚举所有在两条链路径上的两个点,取个最大值。
这样常数达到二十左右,被卡了。队友机智的给lca加了个map优化,能够1s水过。

其实这是个套路题,对于两条链,取出8个点:x,y,nx,ny,lca(x,nx),lca(x,ny),lca(y,nx),lca(y,ny)。按照dfs序排个序,去重后,取最后两个在两条路径上的点即可。


#include <bits/stdc++.h>
using namespace std;
const int N=1e4+7;
int T,n,q,k,cs;
struct Edge{
    int v,nxt;
}e[N*2];
int p[N],edn;
void add(int u,int v){
    e[++edn]=(Edge){v,p[u]};p[u]=edn;
    e[++edn]=(Edge){u,p[v]};p[v]=edn;
}
int fa[N][30],d[N],id[N],tot;
void dfs(int u){
    d[u]=d[fa[u][0]]+1;
    id[u]=++tot;
    for(int i=p[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(v==fa[u][0]) continue;
        fa[v][0]=u;
        dfs(v);
    }
}
void init(){
    for(int j=1;j<=20;j++){
        for(int i=1;i<=n;i++){
            fa[i][j]=fa[fa[i][j-1]][j-1];
        }
    }
}
int lca(int x,int y){
    if(d[x]<d[y]) swap(x,y);
    for(int i=20;i>=0;i--) if(d[fa[x][i]]>=d[y]) x=fa[x][i];
    if(x==y) return x;
    for(int i=20;i>=0;i--){
        if(fa[x][i]!=fa[y][i]){
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}
int dis(int x,int y,int t)
{
    return d[x]+d[y]-2*d[t]+1;
}
bool in(int x0,int x,int y){
    int t=lca(x,y);
    if(lca(x0,t)==t&&lca(x0,x)==x0) return true;
    if(lca(x0,t)==t&&lca(x0,y)==x0) return true;
    return false;
}
int arr[10];
int cmp(int k1,int k2){
    return id[k1]<id[k2];
}
int main()
{
    //freopen("in.in","r",stdin);
    scanf("%d",&T);
    while(T--){
        tot=0;
        printf("Case %d:\n",++cs);
        scanf("%d",&n);
        for(int i=1;i<=n;i++) p[i]=-1;edn=-1;
        for(int i=1,u,v;i<n;i++){
            scanf("%d%d",&u,&v);
            add(u,v);
        }
        dfs(1);
        init();
        scanf("%d",&q);
        int x,y,t;
        bool flag=true;
        while(q--){
            scanf("%d",&k);
            scanf("%d%d",&x,&y);
            t=lca(x,y);
            flag=true;
            for(int i=2,nx,ny,nt;i<=k;i++)
            {
                scanf("%d%d",&nx,&ny);
                if (!flag) continue;
                nt=lca(nx,ny);
                arr[1]=x;arr[2]=y;arr[3]=nx;arr[4]=ny;arr[5]=lca(x,nx);arr[6]=lca(x,ny);arr[7]=lca(y,nx);arr[8]=lca(y,ny);
                int cnt=0;
                for(int u=1;u<=8;u++) if(in(arr[u],x,y)&&in(arr[u],nx,ny)) arr[++cnt]=arr[u];
                sort(arr+1,arr+1+cnt,cmp);
                cnt=unique(arr+1,arr+1+cnt)-arr-1;
                x=y=0;
                for(int u=cnt;u>=1;u--){
                    if(!x) y=x=arr[u];
                    else{
                        y=arr[u];
                        break;
                    }
                }
                if(x) t=lca(x,y);
                else flag=false;
            }
            if(flag) printf("%d\n",d[x]+d[y]-2*d[t]+1);
            else printf("0\n");
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值