[APIO2018] Duathlon 铁人两项

考虑建出圆方树。
那么如果我们固定两点,对答案的贡献是两个点之间的点数减去\(2\)
那么我们考虑在圆方树上做。
把方点的权值设为该点双的大小,把圆点设为\(-1\)
则统计任意两圆点间的点权和。
拆成单点贡献就好了。
另外注意在建圆方树时,\(u\)\(v\)在栈中不一定相邻,要退栈到\(v\),单独考虑\(u\)

[APIO2018] Duathlon 铁人两项
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#define ll long long
#define N 400000

struct P{int to,next;};

struct Map{
	P e[N];
	int cnt,head[N];
	Map(){
		cnt = 0;
		std::memset(head,0,sizeof(head));
	}
	inline void add(int x,int y){		
		e[++cnt].to = y;
		e[cnt].next = head[x];
		head[x] = cnt;
	}
}A,T;

ll n,m;
ll dfn[N],dfncnt,low[N],siz[N];

std::stack<int>QWQ;

ll vi[N],fcnt,num;

inline void tarjan(int u){
	vi[u] = -1;
	dfn[u] = low[u] = ++dfncnt;
	QWQ.push(u);
	++num;
	for(int i = A.head[u];i;i = A.e[i].next){
		int v = A.e[i].to;
		if(!dfn[v]){
			tarjan(v);
			low[u] = std::min(low[u],low[v]);
			if(low[v] == dfn[u]){
				vi[++fcnt] = 0;
//				std::cout<<fcnt<<":"<<std::endl;
				while(QWQ.top() != v){
//					std::cout<<QWQ.top()<<std::endl;
					T.add(QWQ.top(),fcnt);
					T.add(fcnt,QWQ.top());
					++vi[fcnt];
					QWQ.pop();
				}
//				std::cout<<QWQ.top()<<std::endl;				
				T.add(QWQ.top(),fcnt);
				T.add(fcnt,QWQ.top());
				++vi[fcnt];
				QWQ.pop();
				T.add(u,fcnt);
				T.add(fcnt,u);		
				++vi[fcnt];
			}
		}
		else
		low[u] = std::min(low[u],dfn[v]);
	}
}

ll ans = 0;

inline void dfs(int u,int fa){
	siz[u] = (u <= n);
	for(int i = T.head[u];i;i = T.e[i].next){
		int v = T.e[i].to;
		if(v == fa)
		continue;
		dfs(v,u);
		ans += 2 * vi[u] * siz[u] * siz[v];//子树间。 
		siz[u] += siz[v];
	}
	ans += 2 * siz[u] * vi[u] * (num - siz[u]);//祖先和子树。 
}

int main(){
	scanf("%lld%lld",&n,&m);
	fcnt = n;
	for(int i = 1;i <= m;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		A.add(x,y);
		A.add(y,x);
	}
	for(int i = 1;i <= n;++i)
	if(!dfn[i])
	num = 0,tarjan(i),dfs(i,0);
	std::cout<<ans<<std::endl;
}
posted @ 2021-05-03 22:01  fhq_treap  阅读(49)  评论(0编辑  收藏  举报