【BZOJ1023】【SHOI2008】仙人掌图

【题目链接】

【思路要点】

  • 建立圆方树,并进行树形DP,求出每个圆点到其子树内最远的圆点的距离\(dp_{i,0}\),以及在不同的子树内距离最远的圆点的距离\(dp_{i,1}\)。
  • 考虑枚举直径上离根最近的点:
  • 若该点为圆点,那么该圆点对答案的贡献显然为\(dp_{i,0}+dp_{i,1}\)。
  • 若该点为方点,那么问题便转化为了“在一个\(N\)个点的环上有一系列点,每个点有权值\(val_i\),现在要选出两个点\(i\),\(j\),使得\(val_i+val_j+dist(i,j)\)最大”。倍长环,转化为序列问题。不妨令\(i<j\)且\(j-i≤\frac{N}{2}\),则要求\(val_i-i+val_j+j\)最大,枚举\(i\),用单调队列维护\(val_j+j\)的最大值即可。
  • 时间复杂度\(O(N)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
vector <int> a[MAXN], b[MAXN];
int n, m, oldn, ans;
int top, Stack[MAXN], len[MAXN];
int timer, dfn[MAXN], low[MAXN];
int dp[MAXN][2];
void dfs(int pos, int fa) {
	if (pos <= oldn) {
		for (unsigned i = 0; i < b[pos].size(); i++)
			if (b[pos][i] != fa) dfs(b[pos][i], pos);
	} else {
		int tmp = 0;
		for (unsigned i = 0; i < b[pos].size(); i++)
			if (b[pos][i] != fa) {
				int dest = b[pos][i];
				dfs(dest, pos);
				int tnp = b[pos].size() - 1 - i;
				chkmax(tmp, dp[dest][0] + min(tnp, len[pos] - tnp));
			}
		if (tmp > dp[fa][0]) {
			dp[fa][1] = dp[fa][0];
			dp[fa][0] = tmp;
		} else chkmax(dp[fa][1], tmp);
	}
}
void tarjan(int pos) {
	Stack[++top] = pos;
	dfn[pos] = low[pos] = ++timer;
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (dfn[a[pos][i]] == 0) {
			tarjan(a[pos][i]);
			chkmin(low[pos], low[a[pos][i]]);
			if (low[a[pos][i]] >= dfn[pos]) {
				int tmp = 0, last = 0; n++;
				while (tmp != a[pos][i]) {
					last = tmp;
					tmp = Stack[top--];
					b[n].push_back(tmp);
					b[tmp].push_back(n);
					len[n]++;
				}
				b[n].push_back(pos);
				b[pos].push_back(n);
				len[n]++;
			}
		} else chkmin(low[pos], dfn[a[pos][i]]);
}
int main() {
	read(n), read(m), oldn = n;
	for (int i = 1; i <= m; i++) {
		int k; read(k);
		int pos; read(pos);
		for (int j = 2; j <= k; j++) {
			int x; read(x);
			a[x].push_back(pos);
			a[pos].push_back(x);
			pos = x;
		}
	}
	tarjan(1);
	dfs(1, 0);
	ans = 0;
	for (int i = 1; i <= oldn; i++)
		chkmax(ans, dp[i][0] + dp[i][1]);
	for (int t = oldn + 1; t <= n; t++) {
		static int val[MAXN]; int tot = 0, limit = len[t] / 2;
		for (unsigned i = 0; i < b[t].size(); i++)
			if (i == b[t].size() - 1) val[++tot] = 0;
			else val[++tot] = dp[b[t][i]][0];
		for (unsigned i = 0; i < b[t].size(); i++)
			if (i == b[t].size() - 1) val[++tot] = 0;
			else val[++tot] = dp[b[t][i]][0];
		static int q[MAXN]; int l = 0, r = -1;
		for (int i = 1; i <= limit; i++) {
			while (l <= r && val[i] + i >= val[q[r]] + q[r]) r--;
			q[++r] = i;
		}
		for (int i = 1; i <= len[t]; i++) {
			if (q[l] == i) l++;
			int j = i + limit;
			while (l <= r && val[j] + j >= val[q[r]] + q[r]) r--;
			q[++r] = j;
			chkmax(ans, val[i] + val[q[l]] + q[l] - i);
		}
	}
	writeln(ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值