【训练小结】NCPC 2017

trac

题解

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
以后实数判相等真的需要注意!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值