题解-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;
}
祝大家学习愉快!