[APIO2018]铁人两项(圆方树)
过了14个月再重新看这题,发现圆方树从来就没有写过。然后写了这题发现自己APIO2018打铁的原因竟然是没开long long,将树的部分的O(n)写挂了(爆int),毕竟去年APIO时我啥都不会,连tarjan都写不来,活该打铁。
不扯了写题解。
首先建立圆方树,然后任意枚举圆点s和f,然后c可以在这两个点路径中每个点双的点挑选。所以令圆点值为-1,方点值为点双大小,然后选法是圆点路径权值和。然后计算每个点出现多少次,可以对每个连通块树形DP求解,然后这道题就没了。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=2e5+7; vector<int>G1[N],G2[N]; int n,m,tot,sum,tim,top,low[N],dfn[N],q[N],sz[N],val[N]; ll ans; void tarjan(int u) { dfn[u]=low[u]=++tim,q[++top]=u; sz[u]=1,val[u]=-1; for(int i=0;i<G1[u].size();i++) if(!dfn[G1[u][i]]) { int v=G1[u][i]; tarjan(v),low[u]=min(low[u],low[v]); if(low[v]>=dfn[u]) { G2[u].push_back(++tot),val[tot]=1; int x=0; do{ x=q[top--],G2[tot].push_back(x); sz[tot]+=sz[x],val[tot]++; }while(x!=v); sz[u]+=sz[tot]; } } else low[u]=min(low[u],dfn[G1[u][i]]); } void dfs(int u) { if(u<=n)ans+=1ll*(sum-1)*val[u]; ans+=1ll*(sum-sz[u])*sz[u]*val[u]; for(int i=0;i<G2[u].size();i++) ans+=1ll*(sum-sz[G2[u][i]])*sz[G2[u][i]]*val[u],dfs(G2[u][i]); } int main() { scanf("%d%d",&n,&m),tot=n; for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),G1[x].push_back(y),G1[y].push_back(x); for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i),sum=sz[i],dfs(i); printf("%lld",ans); }