题目大意:给一张$n(nleqslant2000)$个点的无向图,给所有边定向,使定向之后存在最多的有序点对$(a,b)$满足从$a$能到$b$
题解:先把边双缩点,因为这里面的点一定两两可达。
根据网上题解得知,最优解一定长这样:存在一个点$s$,使得对于任意其他点$t$,要么$s$可以到$t$,要么$t$可以到$s$,就把$s$作为根。(出题人的题解也没给出解答,就感性理解)
所以$s$的每一个子树内的边要么都朝向$s$,要么都远离$s$
然后可以枚举哪个点作为根,记$w_i$第$i$个双联通分量的大小,$sz_i$为以第$i$个双联通分量为根的子树大小,每个子树内的点在子树内的贡献为$w_i(sz_i-w_i)$,令$P$为朝向根的节点的$sz$和,过根的贡献为$P(n-w_s-P)$,所以只需要让$P$与$(n-w_s-P)$尽可能接近即可,可以用背包来实现
卡点:无
C++ Code:
#include <cstdio> #include <bitset> #define maxn 2010 #define maxm (maxn * maxn) inline int min(int a, int b) {return a < b ? a : b;} inline int max(int a, int b) {return a > b ? a : b;} int n, m; namespace Graph { int head[maxn], cnt = 1; struct Edge { int to, nxt; } e[maxm]; inline void addE(int a, int b) { e[++cnt] = (Edge) {b, head[a]}; head[a] = cnt; e[++cnt] = (Edge) {a, head[b]}; head[b] = cnt; } int w[maxn]; int DFN[maxn], low[maxn], idx; int S[maxn], top, res[maxn], scc; void tarjan(int u, int fa = 0) { DFN[u] = low[u] = ++idx; S[++top] = u; int v; for (int i = head[u]; i; i = e[i].nxt) { v = e[i].to; if (v != fa) { if (!DFN[v]) { tarjan(v, u); low[u] = min(low[u], low[v]); } else low[u] = min(low[u], DFN[v]); } } if (DFN[u] == low[u]) { scc++; do { v = S[top--]; w[res[v] = scc]++; } while (u != v); } } } long long ans; namespace Tree { int head[maxn], cnt; struct Edge { int to, nxt; } e[maxn << 1]; inline void addE(int a, int b) { e[++cnt] = (Edge) {b, head[a]}; head[a] = cnt; e[++cnt] = (Edge) {a, head[b]}; head[b] = cnt; } using Graph::res; using Graph::w; using Graph::scc; void init(int n, int m) { for (int i = 1; i <= n; i++) if (!Graph::DFN[i]) Graph::tarjan(i); for (int i = 2; i <= Graph::cnt; i += 2) { int u = Graph::e[i ^ 1].to, v = Graph::e[i].to; if (res[u] != res[v]) addE(res[u], res[v]); } } int sz[maxn]; int __ans, sumsz; std::bitset<maxn / 2> B; #define ans __ans void dfs(int u, int fa = 0) { sz[u] = w[u]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v != fa) { dfs(v, u); sz[u] += sz[v]; } } ans += sz[u] * w[u]; } int calc(int u) { B.reset(); B[0] = true; ans = 0; dfs(u); for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; B |= B << sz[v]; } for (int i = sumsz - w[u] >> 1; i; i--) if (B[i]) return ans + i * (sumsz - w[u] - i); return ans; } #undef ans int q[maxn], h, t; bool vis[maxn]; void solve(int u) { vis[q[h = t = 0] = u] = true; int res = 0; sumsz = 0; while (h <= t) { int u = q[h++]; sumsz += w[u]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (!vis[v]) vis[q[++t] = v] = true; } } for (int i = 0; i <= t; i++) res = max(res, calc(q[i])); ans += res; } void work() { for (int i = 1; i <= scc; i++) if (!vis[i]) solve(i); } } int main() { scanf("%d%d", &n, &m); for (int i = 0, a, b; i < m; i++) { scanf("%d%d", &a, &b); Graph::addE(a, b); } Tree::init(n, m); Tree::work(); printf("%lld ", ans); return 0; }