题解
F - Fractal Tree
题解:
从上往下二分可以找到每个点的位置,记录二分路径上所在的层数和在原树中的位置
最后求距离的时候,发现路径的lcp是大家的公共部分,删去即可,然后各自的第一个点在原树上求lca,剩下的把深度加起来即可
技巧:
超过inf的点数,用-inf代替,判负数可知
总结:
这题要深入的理解lca的本质,即跳到一个相同的祖先之后一定完全相同。并且需要想清楚代码实现,最好把代码模块化,因为比较难以调试。
这题可以很方面的对拍,当检查不出错误的时候就应该积极的对拍。
尽量不要gdb,用输出调试即可完全知道所有信息,而用gdb很多时候不如自己解读输出的信息和原因
其实我的思路和主代码一开始就是正确的,但是因为上述的细节错误,导致调了很久。
总共调了:50 + 30 = 80min
写了 80min(包括过样例)
/*我犯的所有错误
1. 忘了特判链
2. 题目的dfn和点标号开始以为直接是对应好的,其实只是叶子按顺序dfs
3. vector删除,写成swap,但是顺序改变,应该reverse以后从尾开始删除
4. 标号对应的时候,把所有下标 + 1处理,但是改了F,没有改对应的叶子个数
5. 给root_num + 1的时候忘了用add函数,导致 -1 + 1 = 0 , 以后 INF 可以用 -inf 然后判 < 0 防止这样的小错误!
6. 把vector删空,而其实最后一个点应该作为lca保留
7. (1 << 31)刚好爆 int !
其实我的思路和主代码一开始就是正确的,但是因为上述的细节错误,导致调了很久。
总共调了:50 + 30 = 80min
写了 80min(包括过样例)
*/
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;
const ll inf = 1ll << 31;
const int maxn = 100020;
const ll mod = 1e9 + 7;
ll F[120];
ll leaf_num,tot_num,root_num,len;
pr root;
int n,k,Q;
//NOTES :
//first : 点在当前层对应的原树的节点标号
//second : 该点所在那一层
//原树中点的标号不是dfs序,需要另外记录dfn[i]代表点i对应的dfs序,b[i]表示dfn为i的实际标号
//按照加边顺序dfs,即可保证叶子顺序
//所有有关标号的运算,必须用函数,保证-1不要忘了判
//len = dfn[a[1]] - 1 : 即第一个叶子和1号点的距离
namespace Tree{
vector <int> e[maxn];
int num[maxn],fa[maxn],dth[maxn],jump[20][maxn];
int a[maxn],b[maxn],dfstime,dfn[maxn];
void adde(int x,int y){
e[x].push_back(y);
}
void dfs(int x){
dfn[x] = ++dfstime , b[dfstime] = x;
if ( !e[x].size() ) num[x] = ++leaf_num , a[leaf_num] = x;
for (auto to : e[x]){
dth[to] = dth[x] + 1;
dfs(to);
}
}
inline int lca(int x,int y){
if ( dth[x] < dth[y] ) swap(x,y);
int d = dth[x] - dth[y];
rep(i,0,19) if ( (1 << i) & d ) x = jump[i][x];
if ( x == y ) return x;
repd(i,19,0) if ( jump[i][x] != jump[i][y] ) x = jump[i][x] , y = jump[i][y];
return fa[x];
}
void build(){
for (int i = 2 ; i <= n ; i++){
scanf("%d",&fa[i]) , ++fa[i] , jump[0][i] = fa[i];
adde(fa[i],i);
}
dfs(1); //dth[1] = 0 , 刚好不用 - 1
len = dfn[a[1]] - 1;
for (int i = 1 ; i <= 19 ; i++)
for (int j = 1 ; j <= n ; j++)
jump[i][j] = jump[i - 1][jump[i - 1][j]];
}
}
using namespace Tree;
inline bool cmp(ll a,ll b){
if ( b == -1 ) return 1;
return a <= b;
}
inline ll mul(ll x,ll y){
if ( x == -1 || y == -1 ) return -1;
return (x * y) > inf ? -1 : (x * y);
}
inline ll add(ll x,ll y){
if ( x == -1 || y == -1 ) return -1;
return (x + y) > inf ? -1 : (x + y);
}
inline ll sub(ll x,ll y){
if ( x == -1 || y == -1 ) return -1;
return x - y;
}
ll power(ll x,ll y){
ll res = 1;
for ( ; y ; y >>= 1){
if ( y & 1 ) res = mul(res,x);
x = mul(x,x);
}
return res;
}
void init(){
tot_num = n;
F[0] = 1 , F[1] = tot_num;
for (int i = 2 ; i <= 100 ; i++){ //修改一个地方,所有有关联的下标都要修改!
ll d = power(leaf_num,i - 1);
F[i] = add(F[i - 1],mul(d,(tot_num - 1)));
// cout<<F[i]<<" ";
}
//cout<<endl;
int x = min(k,100);
while ( x && F[x] == -1 ){
x--;
}
//层数表示每个叶子包含的点数,注意标号从0开始
root = {1,x} , root_num = add(mul(k - x,len),1); //+1必须写成函数!
}
bool aboveRoot(ll a){
return cmp(a,root_num);
}
inline ll calc(int id,int k){ //第k层,第id个叶子所有点
ll res = add(dfn[a[id]],mul(id,sub(F[k],1)));
return res;
}
inline void getPath(int x,vector <pr> &Path){
int cur_layer = root.se; //当前层表示当前叶子子树大小为F[cur_layer]
while ( 1 ){
//每次二分查找在哪个叶子的子树中,如果在当前子树中退出
//一定能够找到位置
//在第一个叶子前
if ( x <= dfn[a[1]] ){
Path.push_back(mp(b[x],cur_layer));
break;
}
int l = 1 , r = leaf_num , res = 0; //找到最小的叶子,使得如果加上该叶子子树中的所有点,x一定包含在这之前
while ( l <= r ){
int mid = (l + r) >> 1;
ll num = calc(mid,cur_layer);
if ( cmp(x,num) ) res = mid , r = mid - 1;
else l = mid + 1;
}
x -= calc(res - 1,cur_layer) + dfn[a[res]] - dfn[a[res - 1]] - 1; //减去标号一定比x小的标号
//cout<<cur_layer<<" : "<<x<<" "<<res<<endl;
if ( x <= 1 ){ //在当前层
Path.push_back(mp(b[x + dfn[a[res]] - 1],cur_layer));
break;
}
//进入下一层
Path.push_back(mp(a[res],cur_layer));
cur_layer--;
}
}
inline ll getdis(int x,int y){
int z = lca(x,y);
return dth[x] + dth[y] - dth[z] * 2;
}
void print(vector <pr> pa,vector <pr> pb){
cout<<"path a : \n";
for (auto i : pa) cout<<i.fi<<" "<<i.se<<endl;
cout<<endl;
cout<<"path b : \n";
for (auto i : pb) cout<<i.fi<<" "<<i.se<<endl;
cout<<endl;
}
inline ll getdis(vector <pr> &pa,vector <pr> &pb){
reverse(pa.begin(),pa.end());
reverse(pb.begin(),pb.end()); //为了方便删除,要将vector删除。并且vector不能删空,最后一个节点相同说明找到lca
while ( pa.size() > 1 && pb.size() > 1 && pa.back() == pb.back()){
pa.pop_back();
pb.pop_back();
}
ll res = 0;
//print(pa,pb);
res += getdis(pa.back().fi,pb.back().fi);
pa.pop_back();
pb.pop_back();
for (auto i : pa){ //每次一定跳到上一层的叶子(当前层的根),所以直接加dth
res += dth[i.fi];
}
for (auto i : pb){
res += dth[i.fi];
}
return res;
}
ll query(int a,int b){
if ( leaf_num == 1 ){ return abs(a - b); } //特判一条链
int t1 = aboveRoot(a) , t2 = aboveRoot(b);
ll res = 0;
if ( t1 && t2 ) return abs(b - a);
if ( t1 ) res += root_num - a , a = 1;
else a -= root_num - 1;
if ( t2 ) res += root_num - b , b = 1;
else b -= root_num - 1;
// cout<<a<<" "<<b<<" "<<res<<endl;
vector <pr> pa(0),pb(0);
getPath(a,pa);
getPath(b,pb);
// print(pa,pb);
res += getdis(pa,pb);
return res;
}
int main(){
freopen("input.txt","r",stdin);
scanf("%d",&n);
Tree::build();
scanf("%d %d",&k,&Q);
init();
//cout<<root.fi<<" "<<root.se<<" "<<root_num<<endl;
while ( Q-- ){
int a,b;
scanf("%d %d",&a,&b);
printf("%lld\n",query(a,b));
}
}
H
题解
直接网络流就可以过了
跑得比贪心还快。
主要是输出方案的细节,和极角排序要想清楚!
极角排序应该写成循环的形式,减少特判。
判极角相等不能用atan2(),但是排序可以
总结:
比赛的时候一直WA 12,一直怀疑自己代码写错了,没想到被卡精度,
这样的经验还需要再积累,因为根本不知道eps需要开到1e-15
以后实数判相等真的需要注意!