【NOI2017模拟6.26】A

本文探讨了一种特殊的树路径计数问题,通过点分治、线段树等算法解决复杂限制条件下的路径计数问题,并介绍了如何利用扫描线算法进行矩形并集计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给出一棵树,求树上所有满足路径中任意两个不同点a,b,gcd(a,b)a的无序路径
n<=1e5

Solution

先讲讲我在考场上想的辣鸡做法
暴力部分枚举倍数来判断能做到n^2 log n
由于某些奥妙重重的原因这个做法能跑过3*1e4
一条链的话可以预处理出每个点往左/右最多能扩展到的点
然后从左往右扫一遍维护每个点往左能对答案贡献的区间
注意这个区间一定是递减的
所以复杂度是线性的

正解有很多种
反正这道题开O2O(NNlogN)都能过还有什么不能过的
考虑点分治,枚举每个点对答案贡献
那么每个限制相当于限制一个子树不能成为答案
那么用线段树维护dfs序就可以做了
实现很麻烦,当然也可以不用点分治,dfs一遍在回溯的时候处理一下影响也行

题解的方法超机智的说=w=
首先把所有限制提出来,就有N log N个点对
考虑每个点对对答案的影响,如果这个点对不互为祖先和后代,那么这两个点的子树就不互相可达
我们把二维平面上的一个点(x,y)看做x到y的路径,那么这个情况就相当于一个矩形不能选
如果互为祖先和后代,那么同理我们可以把不互相可达的点变成两个矩形的形式
这样我们就是要所有矩形求并,用总方案减去并集

那么怎么矩形求并呢?
网上大部分的做法都是线段树+扫描线,如果觉得我讲的不好的可以去找黄学长的
从左往右扫描线的同时维护当前还未到达右端点的矩形在y轴上的投影的长度
这个东西可以对每个区间维护一个cov表示这个区间被多少条线段覆盖
然后对cov做加/减法就可以了
然后这一段投影的长度*当前线段到下一个线段之间的距离就是分割出来的一个子矩形的面积
这个做法可以推广到实数范围,离散化之后用线段树维护每条边
只不过细节很多罢了,不过想清楚了好写很多

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=last[a];i;i=next[i])
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,x,y,num,tot,cnt,tr[N*4],cov[N*4];
int dfn[N],size[N],d[N],f[N][17],last[N],next[N*2],t[N*2];
ll ans;
struct note{int x,l,r,bz;}sqr[N*100];
bool cmp(note x,note y) {return x.x<y.x||x.x==y.x&&x.bz>y.bz;}
void add(int x,int y) {
    t[++num]=y;next[num]=last[x];last[x]=num;
}
void dfs(int x,int y) {
    size[x]=1;d[x]=d[y]+1;f[x][0]=y;dfn[x]=++cnt;
    rep(i,x) if (t[i]!=y) dfs(t[i],x),size[x]+=size[t[i]];
}
int lca(int x,int y) {
    if (d[x]<d[y]) swap(x,y);
    fd(j,16,0) if (d[f[x][j]]>d[y]) x=f[x][j];
    if (d[x]!=d[y]) x=f[x][0];
    fd(j,16,0) if (f[x][j]!=f[y][j]) x=f[x][j],y=f[y][j];
    if (x!=y) return f[x][0]; else return x;
}
int get(int x,int dep) {
    fd(j,16,0) if (d[f[x][j]]>dep) x=f[x][j];
    if (d[x]==dep) return x; else return f[x][0];
}
void updata(int v,int l,int r) {
    if (cov[v]) tr[v]=r-l+1;
    else if (l==r) tr[v]=0;
    else tr[v]=tr[v*2]+tr[v*2+1];
}
void modify(int v,int l,int r,int x,int y,int z) {
    if (l==x&&r==y) {
        cov[v]+=z;
        updata(v,l,r);
        return;
    }
    int m=(l+r)/2;
    if (y<=m) modify(v*2,l,m,x,y,z);
    else if (x>m) modify(v*2+1,m+1,r,x,y,z);
    else modify(v*2,l,m,x,m,z),modify(v*2+1,m+1,r,m+1,y,z);
    updata(v,l,r);
}
int main() {
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d",&n);
    fo(i,1,n-1) scanf("%d%d",&x,&y),add(x,y),add(y,x);
    dfs(1,0);
    fo(j,1,16) 
        fo(i,1,n) 
            f[i][j]=f[f[i][j-1]][j-1];
    fo(i,1,n)
        fo(j,2,n/i) {
            int x=i,y=i*j;
            if (dfn[x]>dfn[y]) swap(x,y);
            int lx=dfn[x],rx=dfn[x]+size[x]-1;
            int ly=dfn[y],ry=dfn[y]+size[y]-1;
            int z=lca(x,y);
            if (z==x) {
                int u=get(y,d[x]+1);
                int lx=dfn[u],rx=dfn[u]+size[u]-1;
                if (lx>1) {
                    sqr[++tot].x=1;sqr[tot].l=ly;sqr[tot].r=ry;sqr[tot].bz=1;
                    sqr[++tot].x=lx;sqr[tot].l=ly;sqr[tot].r=ry;sqr[tot].bz=-1;
                }
                if (rx<n) {
                    sqr[++tot].x=ly;sqr[tot].l=rx+1;sqr[tot].r=n;sqr[tot].bz=1;
                    sqr[++tot].x=ry+1;sqr[tot].l=rx+1;
                    sqr[tot].r=n;sqr[tot].bz=-1;
                }
            } else {
                sqr[++tot].x=lx;sqr[tot].l=ly;sqr[tot].r=ry;sqr[tot].bz=1;
                sqr[++tot].x=rx+1;sqr[tot].l=ly;sqr[tot].r=ry;sqr[tot].bz=-1;
            }
        }
    sort(sqr+1,sqr+tot+1,cmp);
    ans=(ll)n*(n-1)/2;
    fo(i,1,tot-1) {
        modify(1,1,n,sqr[i].l,sqr[i].r,sqr[i].bz);
        ans-=(ll)tr[1]*(sqr[i+1].x-sqr[i].x);
    }
    printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值