1. 程式人生 > 實用技巧 >牛客挑戰賽45 題解&總結

牛客挑戰賽45 題解&總結

被dyp,gmh77虐爆了。

A

每次找最大的可以刪的偶數。直接\(O(n\lg ^2n)\)實現不會TLE。

B

如果一條邊兩邊的子樹和都是\(k\)的倍數,這條邊一定刪。

C

顯然改的是一段字尾,列舉字尾算最小代價。然後$z \ xor \ k +k-z \(的規律,如果對應位置上\)k$為\(0\)\(z\)任選,如果\(k\)\(1\)\(z\)\(0\)\(2\)的代價,選\(1\)無代價。按照二進位制從高到低決定每個位。

D

\(T2\)隨機,子樹大小期望是\(\frac{1}{n}\sum siz_i=\frac{1}{n}\sum dep_i=O(\lg n)\)

暴力列舉修改哪些點,在\(T1\)

中用資料結構維護一下。我寫了點分樹。

題解做法更簡單:直接維護直徑端點,修改的時候舊直徑至少一個端點是新直徑的端點。

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100005
#define ll long long
#define mp(x,y) make_pair(x,y)
int n;
struct EDGE{
	int to,w;
	EDGE *las;
	int bz;
};
struct Graph{
	EDGE e[N*2];
	EDGE *rev(EDGE *ei){return (e+((ei-e)^1));}
	int ne;
	EDGE *last[N];
	void link(int u,int v,int w){
		e[ne]={v,w,last[u],N};
		last[u]=e+ne++;
	}
} S,T;
ll ans;
int fa2[N];
ll dep2[N];
void initT(int x){
	for (EDGE *ei=T.last[x];ei;ei=ei->las)
		if (ei->to!=fa2[x]){
			fa2[ei->to]=x;
			dep2[ei->to]=dep2[x]+ei->w;
			initT(ei->to);
		}
}
int mxd[N];
#define forei(x) for (EDGE *(ei)=S.last[x];ei;ei=ei->las) if (ei->to!=fa && ei->bz>=d)
int siz[N],all;
void getsiz(int x,int fa,int d){
	siz[x]=1;
	forei(x){
		getsiz(ei->to,x,d);
		siz[x]+=siz[ei->to];
	}
//	printf("%d %d %d\n",d,x,siz[x]);
}
int getG(int x,int fa,int d){
	int is=(all-siz[x]<=all>>1);
	forei(x){
		int t=getG(ei->to,x,d);
		if (t) return t;
		is&=(siz[ei->to]<=all>>1);
	}
	return is?x:0;
}
ll dis[18][N],mx[18][N];
int bel[18][N],rt[18][N];
pair<pair<ll,int>,pair<ll,int> > opt[N];
void upd(pair<pair<ll,int>,pair<ll,int> > &a,pair<ll,int> b){
	if (b>a.first)
		a.second=a.first,a.first=b;
	else if (b>a.second)
		a.second=b;
}
void getdis(int x,int fa,int d){
	mx[d][x]=dis[d][x]+dep2[x];
	forei(x){
		dis[d][ei->to]=dis[d][x]+ei->w;
		if (fa==0){
			bel[d][ei->to]=ei->to;
			rt[d][ei->to]=x;
		}
		else{
			bel[d][ei->to]=bel[d][x];
			rt[d][ei->to]=rt[d][x];
		}
		getdis(ei->to,x,d);
		mx[d][x]=max(mx[d][x],mx[d][ei->to]);
	}
}
void divide(int x,int d){
	getsiz(x,0,d),all=siz[x];
	x=getG(x,0,d);
	mxd[x]=d;
	bel[d][x]=rt[d][x]=x;
	dis[d][x]=0,getdis(x,0,d);
	opt[x]=mp(mp(dep2[x],x),mp(dep2[x],x));
	for (EDGE *ei=S.last[x];ei;ei=ei->las)
		if (ei->bz>=d)
			upd(opt[x],mp(mx[d][ei->to],ei->to));
	ans=max(ans,opt[x].first.first+opt[x].second.first);
	for (EDGE *ei=S.last[x];ei;ei=ei->las)
		if (ei->bz>=d){
			S.rev(ei)->bz=ei->bz=d;
			divide(ei->to,d+1);
		}
}
void change(int x,int k){
	dep2[x]+=k;
	for (int i=mxd[x];i>=1;--i){
		pair<ll,int> tmp=mp(dis[i][x]+dep2[x],bel[i][x]);
		pair<pair<ll,int>,pair<ll,int> > &o=opt[rt[i][x]];
		if (o.first.second==tmp.second)
			o.first=max(o.first,tmp);
		else if (o.second.second==tmp.second){
			if (tmp>o.second){
				o.second=tmp;
				if (o.first<o.second)
					swap(o.first,o.second);
			}
		}
		else
			upd(o,tmp);
		ans=max(ans,o.first.first+o.second.first);
	}
}
void dfs(int x,int k){
//	printf("%d %d\n",x,k);
	change(x,k);
	for (EDGE *ei=T.last[x];ei;ei=ei->las)
		if (ei->to!=fa2[x])
			dfs(ei->to,k);
}
int main(){
	int Q;
	scanf("%d%d",&n,&Q);
	for (int i=1;i<n;++i){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		S.link(u,v,w);
		S.link(v,u,w);
	}
	for (int i=1;i<n;++i){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		T.link(u,v,w);
		T.link(v,u,w);
	}
	initT(1);
	divide(1,1);
//	for (int i=1;i<=n;++i)
//		printf("%d ",mxd[i]);
//	printf("\n");
	printf("%lld\n",ans);
	while (Q--){
		int x,k;
		scanf("%d%d",&x,&k);
		dfs(x,k);
		printf("%lld\n",ans);
	}
	return 0;
}

E

https://www.cnblogs.com/jz-597/p/13972201.html

F

待填坑

G

https://www.cnblogs.com/jz-597/p/13977888.html


最大的敗筆是想不出E……

總是在考慮每次修改之後連通塊的變化,而不是最後連通塊的情況。

正難則反,這種基本的思想都掌握不好呢。