CF500 G

题意:
有一棵树,n个点,边长为1,m个询问。每个询问(x,y,u,v)表示一个人从x开始,不停的在(x,y)间往返跑,另一个人从u开始在(u,v)间往返跑,两人每秒的速度都是1,问什么时候第一次在某个节点上相遇。
n,m<=200000

#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#define N 210000
#define maxd 18
#define LL long long
using namespace std;
struct node{int y,nex;}a[2*N];
struct node1{int dep,par[20];}t[N];
int fir[N],len,n,m,z;
LL inf=1ll<<60;
bool bo;
void ins(int x,int y)
{
    a[++len].y=y;a[len].nex=fir[x];fir[x]=len;
}
void dfs(int x,int fa)
{
    t[x].dep=t[fa].dep+1;t[x].par[0]=fa;
    for(int i=1;i<=maxd;i++) t[x].par[i]=t[t[x].par[i-1]].par[i-1];
    for(int k=fir[x];k;k=a[k].nex)
    {
        int y=a[k].y;
        if(y==fa) continue;
        dfs(y,x);
    }
}
int lca(int x,int y)
{
    if(t[x].dep<t[y].dep) swap(x,y);
    for(int i=maxd;i>=0;i--) if(t[t[x].par[i]].dep>=t[y].dep) x=t[x].par[i];
    if(x==y) return x;
    for(int i=maxd;i>=0;i--) if(t[x].par[i]!=t[y].par[i]) x=t[x].par[i],y=t[y].par[i];
    return t[x].par[0];
}
int lower(int x,int y)
{
    if(t[x].dep>t[y].dep) return x;
    return y;
}
int dis(int x,int y)
{
    int u=lca(x,y);
    return t[x].dep+t[y].dep-2*t[u].dep;
}
int exgcd(int a,int b,LL &x,LL &y)
{
    if(b==0) {x=1;y=0;return a;}
    LL t=a/b,g,xx,yy;
    g=exgcd(b,a%b,xx,yy);
    x=yy;y=xx-t*yy;
    return g;
}
int gcd(int a,int b)
{
    if(b==0) return a;
    return gcd(b,a%b);
}
LL same(int t1,int t2,int f1,int f2)
{
    int t=((t2-t1)%f2+f2)%f2;LL p,q,g,m;
    g=exgcd(f1,f2,p,q);
    if(t%g) return inf;
    p*=t/g;q*=t/g;
    m=f2/g;
    p=(p%m+m)%m;
    return p*f1+t1;
}
int G(LL M,LL D,LL L,LL R)
{
    LL t=L/D;
    if(t*D<L) t++;
    if(t*D<=R) return t;
    if(2*D>M) return G(M,M-D,M-R,M-L);
    int k=G(D,((-M%D)+D)%D,L%D,R%D);
    L+=M*k;R+=M*k;
    t=L/D;
    if(t*D<L) t++;
    return t;
}
LL make(int M,int D,int L,int R)
{
    int g=gcd(M,D);
    if((L-1)/g>=R/g) return inf;
    return G(M,D,L,R);
}
LL diff(int t1,int t2,int f1,int f2,int d)
{
    int l=t2-t1-d,r=t2-t1+d;
    l=(l%f2+f2)%f2;
    r=(r%f2+f2)%f2;
    if(l%2) return inf;
    if(l>r || l==0) return t1+r/2;
    if(f2==d*2) return t1+r/2;
    LL p=make(f2,f1%f2,l,r),q;
    if(p==inf) return inf;
    return p*f1+t1+d-(p*f1%f2-l)/2;
}
LL solve()
{
    int x,y,u,v,a,b,p1,p2,p3,p4,p5,p6,p,d,t1,t2,t3,t4,f1,f2,d1,d2;
    z++;
    scanf("%d%d%d%d",&x,&y,&u,&v);
    if(bo && z==143) printf("%d %d %d %d\n",x,y,u,v);
    p1=lca(x,y);p2=lca(u,v);p3=lca(x,u);p4=lca(x,v);p5=lca(y,u);p6=lca(y,v);
    p=lower(p1,p2);a=lower(p3,p4);b=lower(p5,p6);
    if(t[p].dep>t[a].dep && t[p].dep>t[b].dep) return -1;
    if(t[p].dep>t[a].dep) a=p;
    else if(t[p].dep>t[b].dep) b=p;
    d=dis(a,b);f1=2*dis(x,y);f2=2*dis(u,v);
    d1=dis(x,a);d2=dis(x,b);
    if(d1<d2) t1=d1,t2=f1-d2;
    else t1=f1-d1,t2=d2;
    d1=dis(u,a);d2=dis(u,b);
    if(d1<d2) t3=d1,t4=f2-d2;
    else t3=f2-d1,t4=d2;
    LL res=inf;
    res=min(same(t1,t3,f1,f2),res);
    res=min(same(t2,t4,f1,f2),res);
    res=min(diff(t1,t4,f1,f2,d),res);
    res=min(diff(t2,t3,f1,f2,d),res);
    if(res==inf) res=-1;
    return res;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        ins(x,y);ins(y,x);
    }
    dfs(1,0);
    scanf("%d",&m);
    //if(m==8100 && a[1].y==1) bo=1;
    while(m--) 
    {
        LL ans=solve();
        if(bo==0) printf("%lld\n",ans);
    }
    return 0;
}

题解:
看懂题解后几乎抄了一遍标程
有点复杂,官方题解很清楚,这里不赘述了。
这题一个核心子问题,也是有通用性的就是给出M,D,L,R,询问最小的m满足:L<=D*m<=R(%M)
设G(M,D,L,R)表示答案。
由于D*m%M是gcd(D,M)的倍数,所以无解条件是 L1(D,M)>=R(D,M)
如果去掉取模有解,则容易找出最优解

当2D>M时,把每次加D,范围[L,R],看成每次减D,范围在[-L,-R]
这样问题就变成G(M,M-D,M-R,M-L)。注意变换一次后即有2D< M。

当2D< M时,有
L+M*k<=D*m<=R+M*k
L<=D*m-M*k<=R
若能求出一个最小的k,使m存在,那此时m也是最小的
注意L和R之间不存在D的倍数,所以可以在模D的意义下看待这个方程,即
L%D<=-M%D*k<=R%D(%D)
所以k的值就是G(D,D-M%D,L%D,R%D)
注意此时问题规模减小一半,故复杂度 O(logn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值