河南萌新联赛2024第(四)场:河南理工大学 A
非常愉快的补完了这道了这道割点题(已红温)
题源:https://ac.nowcoder.com/acm/contest/88269/A
对于割点问题,学到一种新的方法——圆方树
圆方树最初是处理「仙人掌图」(每条边在不超过一个简单环中的无向图)的一种工具,不过发掘它的更多性质,有时我们可以在一般无向图上使用它。在圆方树中,原来的每个点对应一个 圆点,每一个点双对应一个 方点。
如图所示:
思路
在转化为圆方树后我们就可以方便地进行一系列便捷的操作,记录每个子树的点权和,在一一枚举结点即可。
注意
数据可能会超过long long,可以用__int128来输出最后答案
可能不只一个联通块,也就是说可能有多棵园方树,所以要标记每个结点是否有被遍历。
代码
#include<bits/stdc++.h> using namespace std; const int MN = 200005; int N, M, cnt; __int128 sum1,sum,dul,ans,dp[MN*2]; long long a[MN*2]; std::vector<int> G[MN*2], T[MN * 2]; int dfn[MN*2], low[MN*2], dfc; int stk[MN*2], tp; void Tarjan(int u) { low[u] = dfn[u] = ++dfc; // low 初始化为当前节点 dfn stk[++tp] = u; // 加入栈中 for (int v : G[u]) { // 遍历 u 的相邻节点 if (!dfn[v]) { // 如果未访问过 Tarjan(v); // 递归 low[u] = std::min(low[u], low[v]); // 未访问的和 low 取 min if (low[v] == dfn[u]) { // 标志着找到一个以 u 为根的点双连通分量 ++cnt; // 增加方点个数 // 将点双中除了 u 的点退栈,并在圆方树中连边 for (int x = 0; x != v; --tp) { x = stk[tp]; T[cnt].push_back(x); T[x].push_back(cnt); } // 注意 u 自身也要连边(但不退栈) T[cnt].push_back(u); T[u].push_back(cnt); } } else low[u] = std::min(low[u], dfn[v]); // 已访问的和 dfn 取 min } } void dfs(int u,int fa) { dp[u]=a[u]; for (int v : T[u]) { if(v==fa) continue; dfs(v,u); dp[u]+=dp[v]; } } void print(__int128 x){ if(x < 0){ cout<<'-';x = -x;} if(x > 9) print(x / 10); cout<<char(x % 10 + '0'); } void check(int u,int fa) { __int128 posibleans=(sum1-dp[u])*(sum1-dp[u]); for (int v : T[u]) { // print(posibleans); //printf("\n"); if(v==fa) continue; posibleans+=dp[v]*dp[v]; check(v,u); } if(u<=N) ans=min(ans,posibleans); } int main() { scanf("%d%d", &N, &M); cnt = N; // 点双 / 方点标号从 N 开始 for (int i = 1; i <= M; ++i) { int u, v; scanf("%d%d", &u, &v); G[u].push_back(v); // 加双向边 G[v].push_back(u); } for(int i=1;i<=N;i++) scanf("%lld",&a[i]); // 处理非连通图 for (int u = 1; u <= N; ++u) if (!dfn[u]) { ans=1e27; Tarjan(u); --tp; dfs(u,0); sum1=dp[u]; sum+=sum1*sum1; check(u,0); dul=max(dul,dp[u]*dp[u]-ans); } // for(int i=1;i<=N;i++) printf("%lld ",dp[i]); print(sum-dul); // 注意到退出 Tarjan 时栈中还有一个元素即根,将其退栈 return 0; }