Loading

题解-CF232C Doe Graphs

题面

CF232C Doe Graphs

\(0\)Doe 图是一个点,\(1\)Doe 图是两个点加一条边,\(k\)Doe 图是一个 \(k-1\)Doe 图加上一个编号全加了 \(k-1\)Doe 图大小的 \(k-2\)Doe 图。给一个 \(n\)Doe 图,\(m\) 次询问,每次查询 \(a\)\(b\) 两点间的最短路。

数据范围:\(1\le n\le 10^3\)\(1\le m\le 10^5\)\(1\le a,b\le 10^{16}\)


题解

肝了一个下午最终贺题了。既然狗做不动题,就多写点题解吧。

注意到 \(a,b\)\(fib_n\) 小很多,发现 \(n\)\(fib_{79}\)\(>10^{16}\)) 可以和 \(80\) 取最小值。

考虑一个 \(k\) 级图,把 \(k-1\) 级部分叫做 \(A\),把 \(k-2\) 级部分叫做 \(B\)

有四个点:\(S=1\)\(T_A=fib_{k-1}\)\(S_B=T_A+1\)\(T=fib_{k}\)

有两条边在这里:\((S,S_B)\)\((T_A,S_B)\)


容易想到计算出每个点到 \(S\) 的距离和到 \(T\) 的距离,然后任意两点间的询问,貌似可以通过这两个距离搞出来。

这样便缩小了查询的一维。但是还是不能直接求。

由于 \(a,b\) 是固定的,所以它们在每级图中的地位也是固定的,所以可以考虑只算对于一个查询的点 \(u\),计算它在每层图中和 \(S,T\) 的距离,这东西是可以递推的。

如果设 \(f(k,0/1)\) 表示 \(k\) 级图下 \(u\)\(S/T\) 的距离,便可以非常睿智地玩出一个递推方程,但它是错的。

玩的时候,会发现一个 \(k\) 级图外的边也是对内部的距离有影响的。

最好的例子就是对于 \(S\)\(T_A\),它们都连向 \(S_B\),而它们的内部距离可能 \(>2\)

然后狗就是很 naive 以为只要对这种情况特殊处理掉就 OK 了,没想到有以下情况:

有以下边:\((S,T)\)\((T_A,S_B)\)\((S,S_B)\),然后查询 \(S_B\)\(T\) 的距离。

这种时其实是有个性质的:所有的外部边的影响可以改为在 \(S,T\) 之间加一条带权边。

可以把 dp 转成搜索,\(dp(u,k,d)\) 表示 \(k\) 级图,\(S,T\) 上有一条长度 \(k\) 的边,返回值是个 pair,即 \(u\)\(S,T\) 的距离。

然后就很好递推了。


再考虑接下来如何求 \(a,b\) 之间的距离,不妨设 \(a<b\)

发现也不能直接把 \(n\) 替换成最小的 \(k\) 使得 \(b\le fib_k\),原理同上:有外部影响。

但是推推可以发现:虽然整个图对答案有影响,但因为如果对于最小 \(k\) 满足 \(b\le fib_{k-1}\)\(a,b\) 必然都在它大的部分子图内,所以这里的 \(d\) 肯定是 \(2\),所以 \(n\)\(80\)min 依然是可以的 /cy

所以同样可以搜索,设 \(solve(u,v,k,d)\) 表示 \(k\) 级图,\(S,T\) 上有一条长度 \(k\) 的边,返回它们间的距离。


至于 \(d\) 怎么递推,dp 怎么递推(包括上面)留给读者自行思考了。

时间复杂度 \(\Theta(80m)\)


代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define x first
#define y second
#define bg begin()
#define ed end()
#define pb push_back
#define mp make_pair
#define sz(u) int((u).size())
#define R(i,n) for(int i(0);i<(n);++i)
#define L(i,n) for(int i((n)-1);i>=0;--i)
const int iinf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f;

//Data
const int N=80;
int n;

//Math
ll fib[N];
void math_init(){
    fib[0]=fib[1]=1;
    for(int i=2;i<N;++i)    
        fib[i]=fib[i-1]+fib[i-2];
}

//Function
#define mi fib[k-1]
pair<int,int> dp(ll u,int k,int d=iinf){
    if(k<=1) return mp(0,0);
    if(u<mi){
        auto p=dp(u,k-1,2);
        return mp(p.x,min(min(p.x,p.y)+1+(k-2)/2,p.x+d));
    } else {
        auto p=dp(u-mi,k-2,d+1);
        return mp(min(p.x+1,p.y+d),p.y);
    }
}
int solve(ll u,ll v,int k,int d=iinf){
    if(u==0&&v==fib[k]-1) return min(d,k/2);
    if(v<mi) return solve(u,v,k-1,2);
    if(u>=mi) return solve(u-mi,v-mi,k-2,d+1);
    auto ud=dp(u,k-1,2),vd=dp(v-mi,k-2,d+1);
    return min(min(ud.x,ud.y)+1+vd.x,ud.x+d+vd.y);
}

//Main
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    math_init();
    int m; cin>>m>>n,n=min(n+1,N-1);
    while(m--){
        ll u,v; cin>>u>>v,--u,--v;
        if(u>v) swap(u,v);
        cout<<solve(u,v,n)<<'\n';
    }
    return 0;
}


祝大家学习愉快!

posted @ 2021-01-04 18:47  George1123  阅读(150)  评论(0编辑  收藏  举报