厕所工程(线段树+倍增+扫描线+DSU on tree 树上启发式合并优化 DP)

题意

ZBQC 要建个新的厕所了!

解决资金问题后,现在摆在面前的是一个大难题:厕所建在哪儿?

ZBQC 是个大中学,有 N N N N N N <= 2e5)间教室,由于学校植被较多,空间比较紧凑,因此,这 N N N 间教室由 N N N-1 条等长的双向道路连通。现在一些教室有班级(一间教室只能有一个班级),一些是空教室,每个班级到这个厕所有一定路程(即通过的道路条数)。如果某间教室满足所有班级到这里的路程和最小,那么这间教室就是最佳施工地点,厕所可以紧挨着它建。当然,最佳施工地点可能有很多个。

但是,考虑到新学年快要来了,新的学年那些教室有班级,一共多少个班级都不清楚。所以,工程师想知道:对于每个 i ∈ [ 1 , N ] i∈[1,N] i[1,N] ,当班级总数为 i i i 时,最佳施工地点最多能有多少个?工程师求助了 OI 最强 JZM。

但是 JZM 正在去国赛的路上堵车,心情不好想睡觉,于是把这个问题丢给了 PPL 。

PPL 打了个暴力,但是由于跑太快了,还没看到结果,程序就结束关闭了,他只好又把问题抛给 ZXY。

ZXY 瞬间就码出 O ( 1 ) O(1) O(1) 代码来了,正在跑……现在还在跑,于是 PPL 见 ZXY 靠不住,又求助 SY。

SY 想出了个新做法,配合他的新数据结构,用他的新码风码了起来……现在还在码。

于是 PPL 只好求助你。

题解

首先有一个结论, i i i 为奇数时答案一定为 1 。这个通过调整法可以很容易地证明出来,不展开。

然后是 i i i 为偶数时,如果答案大于 1 的话,一定是在这样一个子图中:一条长度为答案-1的链,链两端吊着两棵树,每棵树里各有 i / 2 i/2 i/2 个关键点。

这样一来,我们可以证明答案的单调性: a n s [ i ∗ 2 ] > = a n s [ ( i + 1 ) ∗ 2 ] ans[i*2]>=ans[(i+1)*2] ans[i2]>=ans[(i+1)2]。接下来怎么求呢,我们不妨分类讨论贡献:

  • 链两端有祖先—后代关系:我们可以把每个点作为下端时按照子树从大到小排序,每条作为上端时按照 N − S i z e s o n ( 总 点 数 − 下 端 子 树 大 小 ) N-Size_{son}(总点数-下端子树大小) NSizeson() 排序,然后从大到小枚举 SIZE 同时加合法点和加合法边,加点时产生的贡献可以通过倍增找最远的合法的祖先计算,加边时可以找边下端子树内深度最大的合法点计算(按照 dfs 序拍到线段树上)。这里某个点的合法祖先,是指以此点为根时该祖先的子树大小不小于我们现在枚举的 SIZE ,合法点是指子树大小不超过 SIZE 的点,因此我们枚举到新的 SIZE 时应该先加点再加边。时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)
  • 链两端分别在不同的子树内,没有祖先—后代关系:这种是最麻烦的,但是我们可以树 DP:令 d p [ i ] [ j ] dp[i][j] dp[i][j] 为 i 子树内 Size 大于等于 j 的点的最大深度, f [ i ] [ j ] f[i][j] f[i][j] 表示 i 子树内限制 SIZE 为 j 时的最大答案。 d p [ i ] [ j ] dp[i][j] dp[i][j] 可以直接从重儿子继承过来,然后 DSU on tree 暴力更新,同时就可以更新 f [ i ] [ j ] f[i][j] f[i][j] 了。时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

CODE

#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define SI set<cp>::iterator
#define lowbit(x) (-(x) & (x))
#pragma GCC optimize(2)
#define int LL
LL read() {
    
    
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {
    
    if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {
    
    x=x*10+(s-'0');s = getchar();}
	return f * x;
}
const int MOD = 1000000007;
int n,m,i,j,s,o,k;
int ANS[MAXN];
int tre[MAXN<<2],M;
void maketree(int n) {
    
    M=1;while(M<n+2)M<<=1;}
void addtree(int x,int y) {
    
    
	int s = M+x;tre[s] = y;s >>= 1;
	while(s) tre[s] = max(tre[s<<1],tre[s<<1|1]),s >>= 1;
}
int findtree(int l,int r) {
    
    
	if(l > r) return 0;
	int s = M+l-1,t = M+r+1,as = 0;
	while(s || t) {
    
    
		if((s>>1) ^ (t>>1)) {
    
    
			if(!(s & 1)) as = max(as,tre[s^1]);
			if(t & 1) as = max(as,tre[t^1]);
		}else break;
		s >>= 1;t >>= 1;
	}
	return as;
}
vector<int> qp[MAXN],qe[MAXN];
vector<int> g[MAXN];
int d[MAXN],siz[MAXN],f[MAXN][20],son[MAXN],tp[MAXN],dfn[MAXN],rr[MAXN],tim;//18
int fp[MAXN];
void dfs0(int x,int ff) {
    
    // d,siz,f,son,
	d[x] = d[f[x][0] = ff] + 1;
	for(int i = 1;i <= 18;i ++) f[x][i] = f[f[x][i-1]][i-1];
	siz[x] = 1; son[x] = 0; tim = 0;
	for(int i = 0;i < (int)g[x].size();i ++) {
    
    
		int y = g[x][i];
		if(y != ff) {
    
    
			dfs0(y,x);
			siz[x] += siz[y];
			if(siz[y] > siz[son[x]]) son[x] = y;
		}
	}
	qp[siz[x]].push_back(x);
	return ;
}
void dfs1(int x,int ff) {
    
     // tp,dfn,rr,fp
	if(son[ff] == x) tp[x] = tp[ff];
	else tp[x] = x;
	if(ff) {
    
    
		fp[x] = n - siz[x];
		qe[fp[x]].push_back(x);
	}
	dfn[x] = ++ tim;
	if(son[x]) dfs1(son[x],x);
	for(int i = 0;i < (int)g[x].size();i ++) {
    
    
		int y = g[x][i];
		if(y != ff && y != son[x]) {
    
    
			dfs1(y,x);
		}
	}
	rr[x] = tim;
	return ;
}
int dpp[MAXN],maxd;
int dp1[MAXN],dp2[MAXN],le;
void cont(int x,int ff) {
    
    
	dpp[siz[x]] = max(dpp[siz[x]],d[x]);
	maxd = max(maxd,siz[x]);
	for(int i = 0;i < (int)g[x].size();i ++) {
    
    
		int y = g[x][i];
		if(y != ff) {
    
    
			cont(y,x);
		}
	}return ;
}

void dfs2(int x,int ff) {
    
    
	if(siz[x] == 1) {
    
    
		dp1[1] = d[x];
		dp2[1] = -0x7f7f7f7f; le = 1;
		return ;
	}
	for(int i = 0;i < (int)g[x].size();i ++) {
    
    
		int y = g[x][i];
		if(y != ff && y != son[x]) {
    
    
			dfs2(y,x);
		}
	}
	le = 1;
	dfs2(son[x],x);
	while(le < siz[x]) le ++, dp1[le] = d[x],dp2[le] = -0x7f7f7f7f;
	while(maxd > 0) dpp[maxd --] = -0x7f7f7f7f;
	for(int i = 0;i < (int)g[x].size();i ++) {
    
    
		int y = g[x][i];
		if(y != ff && y != son[x]) {
    
    
			while(maxd > 0) dpp[maxd --] = -0x7f7f7f7f;
			cont(y,x);
			int mx = -0x7f7f7f7f;
			for(int i = maxd;i > 0;i --) {
    
    
				mx = max(mx,dpp[i]);
				dp2[i] = max(dp2[i],dp1[i] + mx - d[x] - d[ff]);
				dp1[i] = max(dp1[i],mx);
			}
		}
	}
	dp1[siz[x]] = d[x];
	if(tp[x] == x) {
    
    
		for(int i = 1;i <= le;i ++) {
    
    
			ANS[i] = max(ANS[i],dp2[i]);
		}
	}
	return ;
}
//-------------------
int maxtmp = 0;
void addp(int x,int pnm) {
    
    
	addtree(dfn[x],d[x]);
	int ff = x;
	for(int i = 18;i >= 0;i --) {
    
    
		if(fp[f[ff][i]] >= pnm) {
    
    
			ff = f[ff][i];
		}
	}
	if(fp[x] >= pnm) maxtmp = max(maxtmp,d[x] - d[ff] + 2);
	return ;
}
void adde(int x) {
    
    
	int mxd = findtree(dfn[x],rr[x]);
	if(mxd > 0) maxtmp = max(maxtmp,mxd - d[x] + 2);
	return ;
}
signed main() {
    
    
	n = read();
	for(int i = 1;i < n;i ++) {
    
    
		s = read();o = read();
		g[s].push_back(o);
		g[o].push_back(s);
	}
	dfs0(1,0);
	dfs1(1,0);
	dfs2(1,0);
	maketree(n);
	int tg = n+1; maxtmp = 1;
	for(int i = n/2;i > 0;i --) {
    
    
		while(tg > i) {
    
    
			tg --;
			for(int j = 0;j < (int)qp[tg].size();j ++) {
    
    
				int y = qp[tg][j];
				addp(y,i);
			}
			for(int j = 0;j < (int)qe[tg].size();j ++) {
    
    
				int y = qe[tg][j];
				adde(y);
			}
		}
		ANS[i] = max(ANS[i],maxtmp);
		maxtmp = max(ANS[i],maxtmp);
	}
	for(int i = 1;i <= n;i ++) {
    
    
		if(i & 1) {
    
    
			printf("1\n");
		}
		else {
    
    
			printf("%lld\n",ANS[i>>1]);
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43960414/article/details/115082665