LOJ#2587. 「APIO2018」铁人两项
? 解题记录 ? ? LOJ ? ? 圆方树 ?    2018-10-04 13:14:51    750    0    0

地址:https://loj.ac/problem/2587

先来说说我和这道题的故事吧。

作为APIO2018最良心得分题,放在T3的位置是真的阴……

当打完了T1,T2的暴力后,才发现T3貌似很好拿分,想的是把T1,T2的二档暴力打完后来慢慢拿T3的分。于是就被坑进T1了。

T3最后只打了有环和链的、n<=10的。树的档都没看到,血亏。

知道了点双树(圆方树)后,这道题就很送分了。对于圆点统计从不同子树中选出两个点的方案,对于方点统计从不同子树选出三个点的方案,加起来就行了。

细节比较多,详见注释

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<stack>
#include<map>
#define LL long long
using namespace std;
const int maxm = 4e5 + 5;
const int maxn = 2e5 + 5;
typedef pair<int, int > pii;
map<pii, int > mp;
struct edge {
	int v, next;
} e[maxm << 2];
int head[maxn], cnt;
int n, m, u, v;
void adde(const int &u, const int &v) {
	e[cnt] = (edge) {v, head[u]};
	head[u] = cnt++;
}

stack<int > stk;
int dfn[maxn], low[maxn], vis[maxm], E_blk[maxm], bcnt, dcnt;
int ndcnt, is_sq[maxn];
int tarjan(int u) {
	dfn[u] = low[u] = ++dcnt;
	for(register int i = head[u]; ~i; i = e[i].next) {
		int v = e[i].v;
		if(vis[i >> 1]) continue;
		//点双树标记边 ,压边的栈 
		vis[i >> 1] = 1, stk.push(i);
		if(dfn[v]) {
			//点双的low不具有传递性,因为下面的子树会被点u挡住,只能用dfn[v] 
			low[u] = min(dfn[v], low[u]);
			continue;
		}
		low[u] = min(low[u], tarjan(v));
		if(low[v] >= dfn[u]) {
			int id = stk.top(); 
			//特判不在点双中的边 
			if(e[id ^ 1].v == u) {
				stk.pop();
				continue;
			}
			//在点双中才分配标号 
			++bcnt;
			do {
				id = stk.top(), stk.pop();
				E_blk[id >> 1] = bcnt;
			} while(e[id ^ 1].v != u);
		}
	}
	return low[u];
}

namespace CST {
	int head[maxn << 1], siz[maxn << 1];
	void adde(const int &u, const int &v) {
		//cerr << u << "---->" << v << endl;
		e[cnt] = (edge) {v, head[u]};
		head[u] = cnt++;
	}
	int dfs(int u, int p) {
		siz[u] = u <= n;
		for(register int i = head[u]; ~i; i = e[i].next) {
			int v = e[i].v;
			if(v == p) continue;
			siz[u] += dfs(v, u);
		}
		return siz[u];
	}
	LL GetAns(int u, int tot, int p) {
		LL ans = 0, sum = 0, tms = 0;
		//下面就是简单的讨论一下 
		siz[u] = u <= n;
		for(register int i = head[u]; ~i; i = e[i].next) {
			int v = e[i].v;
			if(v == p) continue;
			ans += GetAns(v, tot, u);
			siz[u] += siz[v];
			tms += 1ll * sum * siz[v], sum += siz[v];
		}
		tms += (tot - siz[u]) * sum;
		sum += tot - siz[u];
		tms *= 2;
		if(u <= n) {
			ans += tms;
		} else {
			//ans += tms;
			for(register int i = head[u]; ~i; i = e[i].next) {
				int v = e[i].v;
				if(v == p) continue;
				ans += 1ll * (tms - 2 * siz[v] * (sum - siz[v]));
			}
			ans += 1ll * (tms - 2 * (tot - siz[u]) * (sum - (tot - siz[u])));
		}
		//printf("%d %d\n", u, ans);
		return ans;
	}
}

int main() {
	memset(head, -1, sizeof(head));
	memset(CST::head, -1, sizeof(CST::head));
	scanf("%d%d", &n, &m);
	for(register int i = 1; i <= m; ++i)
		scanf("%d%d", &u, &v), adde(u, v), adde(v, u);
	for(register int i = 1; i <= n; ++i)
		if(!dfn[i]) tarjan(i);
	for(register int i = 0; i < m; ++i) {
		u = e[i << 1].v, v = e[i << 1 | 1].v;
		if(!E_blk[i]) {
			if(u > v) swap(u, v);
			if(mp[make_pair(u, v)]) continue;
			CST::adde(u, v), CST::adde(v, u);
			mp[make_pair(u, v)] = 1;
		} else {
			int S = E_blk[i] + n;
			if(!mp[make_pair(u, S)]) {
				CST::adde(u, S), CST::adde(S, u);
				mp[make_pair(u, S)] = 1;
			}
			if(!mp[make_pair(v, S)]) {
				CST::adde(v, S), CST::adde(S, v);
				mp[make_pair(v, S)] = 1;
			}
		}
	}
	LL ans = 0;
	for(register int i = 1; i <= n; ++i)
		if(!CST::siz[i]) {
			CST::dfs(i, 0);
			ans += CST::GetAns(i, CST::siz[i], 0);
		}
	printf("%lld\n", ans);
	return 0;
}


上一篇: LOJ#2567. 「APIO2016」划艇

下一篇: LOJ#2586. 「APIO2018」选圆圈

750 人读过
立即登录, 发表评论.
没有帐号? 立即注册
0 条评论
文档导航