题面
(0) 级
Doe
图是一个点,(1) 级Doe
图是两个点加一条边,(k) 级Doe
图是一个 (k-1) 级Doe
图加上一个编号全加了 (k-1) 级Doe
图大小的 (k-2) 级Doe
图。给一个 (n) 级Doe
图,(m) 次询问,每次查询 (a) 和 (b) 两点间的最短路。
数据范围:(1le nle 10^3),(1le mle 10^5),(1le a,ble 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) 使得 (ble fib_k),原理同上:有外部影响。
但是推推可以发现:虽然整个图对答案有影响,但因为如果对于最小 (k) 满足 (ble 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)<<'
';
}
return 0;
}
祝大家学习愉快!