[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;
}