• 【BZOJ】P2144 跳跳棋


    LCA+二分

    看了题面,再看标签:二分也就算了,但。。。LCA??
    没错,就是LCA!
    来慢慢分析一波。。。。。

    由于一次只允许跳过1颗棋子并且两个棋子不能同时在一个点,我们以(1,2,3)为例((x,y,z)表示3个棋子分别在x,y和z位置)模拟一下.

    注意红体字部分。
    不难发现,当中间数向两边跳时,会产生两种未出现过的子状态,而左右两边数只能取一个距离离中间数小的向中间跳,这样产生的子状态是上一层状态

    看到这里,我们假设把当前状态连条边到子状态,那么就有了

    WOC!这不就是一棵树吗?
    对吗?完全正确!!

    那么最小变化次数不就是求树上两点之间的路径吗?
    这不禁又让我们想起。。。LCA
    没错,(dis_{(x,y)}=deep_x+deep_y-2×deep_{LCA(x,y)})

    这样我们就有大致的算法框架了。
    先求出两个状态的根节点,判断是否相同,若不相同,则直接输出"NO"。
    而根节点,其3个数必定构成等差数列,那么我们就存一个公差d和第一个数x,就可以确定了。

    但注意一点:
    若x,y,z为0,1,10^9,那么我们查找根节点时显然会TLE。
    如何优化?
    对于上面那组样例,我们可以想成这样

    由于这么多次都是z点和x,y之间的距离都不变,也就是说x,y沿着坐标轴平移了一段距离,那么我们就可以快速地算出增加的深度和x,y的最终位置,时间就会大大降低。

    至于最后计算路径长度,同样我们可以二分LCA的深度,找到LCA。(类似于倍增算法,若这两个点向上跳跃深度为x时,相等了,就往小的距离去找)

    代码:

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    int st[4],ed[4],dst,ded,lst,led,rtst,rted,ans,last;
    void Get(int x,int y,int z,int &deep,int &l,int rt) {
         
        deep=0;
        int d1=y-x,d2=z-y;
        while(d1!=d2) {
            d1=y-x,d2=z-y;
            if(d1<d2) {
                int a1=d2/d1,a2=d2%d1;
                if(a2==0) {
                    a1--;
                    deep+=a1;
                    x+=a1*d1,y+=a1*d1;
                    l=d1;
                    rt=x;
                    return;
                } else {
                    deep+=a1;
                    x+=a1*d1,y+=a1*d1;
                    l=d1;
                }
            } else {
                int a1=d1/d2,a2=d1%d2;
                if(a2==0) {
                    a1--;
                    deep+=a1;
                    z-=a1*d2,y-=a1*d2;
                    l=d2;
                    rt=x;
                    return;
                } else {
                    deep+=a1;
                    z-=a1*d2,y-=a1*d2;
                    l=d2;
                }
            }
        }
        l=d1,rt=x;
        return;
    }
    void Up(int &x,int &y,int &z,int dep){
        while(dep){
            int d1=y-x,d2=z-y;
            if(d1<d2){
                int a1=d2/d1,a2=d2%d1;
                if(dep<=a1){
                    x+=dep*d1,y+=dep*d1;
                    return;
                }
                x+=a1*d1,y+=a1*d1;
                dep-=a1;
            }
            else {
                int a1=d1/d2,a2=d1%d2;
                if(dep<=a1){
                    y-=dep*d2,z-=dep*d2;
                    return;
                }
                z-=a1*d2,y-=a1*d2;
                dep-=a1;
            }
        }
    }
    signed main() {
        scanf("%lld %lld %lld %lld %lld %lld",&st[1],&st[2],&st[3],&ed[1],&ed[2],&ed[3]);
        sort(st+1,st+4),sort(ed+1,ed+4);
        Get(st[1],st[2],st[3],dst,lst,rtst);
        Get(ed[1],ed[2],ed[3],ded,led,rted);
        //cout<<dst<<" "<<ded;
        if(rtst!=rted||lst!=led){
            puts("NO");
            return 0;
        }
        puts("YES");
        if(dst>ded){
            ans+=dst-ded;
            dst=ded;
            Up(st[1],st[2],st[3],ans);
        }
        else {
            ans+=ded-dst;
            ded=dst;
            Up(ed[1],ed[2],ed[3],ans);
        }
        int l=0,r=dst;
        while(l<=r){
            int mid=(l+r)>>1;
            int a1=st[1],a2=st[2],a3=st[3];
            int b1=ed[1],b2=ed[2],b3=ed[3];
            Up(a1,a2,a3,mid);
            Up(b1,b2,b3,mid);
            if(a1==b1&&a2==b2&&a3==b3){
                last=2*mid;
                r=mid-1;   
            }
            else l=mid+1;
        }
        ans+=last;
        cout<<ans;
        return 0;
    }
    
    
  • 相关阅读:
    VS2010下配置CxImage
    Visual Studio 2010 开发配置
    主机屋使用感受
    Web开发者必备的20款超赞jQuery插件
    自动页面居中
    jQuery+CSS打造的网页背景颜色切换效果
    小按钮,大学问
    【网站开发必备】——12款响应式 Lightbox(灯箱)效果插件
    修正 IE 的双倍页边距 bug
    a>b?a:b
  • 原文地址:https://www.cnblogs.com/SillyTieT/p/11185813.html
Copyright © 2020-2023  润新知