P6137 [IOI 2012] 理想城 Solution
神仙题。
这里我最初考虑的是把整块的方格缩成一个点,然后发现假了。
我们注意到一个优美的性质:对于同一行上连在一块的点,我们能发现他们到任意一个点在列上移动的步数都是相同的。
所以我们先统计列上的贡献,我们考虑把同一行连在一块的点缩成一个,然后纵向相邻的连边,会发现这样是一颗树。我们考虑连接的这条边会造成多少贡献:只有这条边两边的点的距离会经过造成贡献。所以考虑一个 dfs 求出子树大小 \(sz_u\),然后一个点到其父亲这条边的贡献即为:\(sz_u\times(n-sz_u)\)。然后求和即可。
子树大小指的是原图上整块点的大小,而不是缩点后的一个点。
然后对列上的贡献我们直接行列交换一下跑一遍即可。
这里建图部分使用了大量 STL。我是 STL 高手。注意值域比较大,但是因为连通所以考虑整个图平移,然后值域就是 \(O(n)\) 的了。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e5 + 10;
const LL MOD = 1e9;
int n, X[N], Y[N], tot, sz[N]; LL Ans = 0;
vector<int> G[N], bel[N]; set<int> F[N]; vector<pair<int, int> > G1[N];
int ssz[N];
void DFS(int u, int f) {
ssz[u] = sz[u];
for (int v : F[u]) if (v != f) {
DFS(v, u); ssz[u] += ssz[v];
} Ans = (Ans + 1ll * ssz[u] * (n - ssz[u])) % MOD; return ;
} // 统计 size 和答案。
void Solve() {
for (int i = 1; i <= n; i ++) G[i].clear(), bel[i].clear(), F[i].clear(), G1[i].clear();
for (int i = 1; i <= n; i ++) G[X[i]].emplace_back(Y[i]);
for (int i = 1; i <= n; i ++) sort(G[i].begin(), G[i].end());
tot = 0;
for (int i = 1; i <= n; i ++) if (G[i].size()) {
int lst = 0;
for (int j = 1; j < (int)G[i].size(); j ++) {
if (G[i][j] != G[i][j - 1] + 1) {
sz[++ tot] = j - lst;
while (lst < j) ++ lst, bel[i].emplace_back(tot);
}
} sz[++ tot] = (int)G[i].size() - lst;
while (lst < (int)G[i].size()) ++ lst, bel[i].emplace_back(tot);
} // 缩点。
for (int i = 1; i <= n; i ++) for (int j = 0; j < (int)G[i].size(); j ++)
G1[G[i][j]].emplace_back(make_pair(bel[i][j], i));
for (int i = 1; i <= n; i ++) for (int j = 0; j < (int)G1[i].size() - 1; j ++) {
if (G1[i][j].second + 1 == G1[i][j + 1].second) {
int a = G1[i][j].first, b = G1[i][j + 1].first;
F[a].insert(b); F[b].insert(a); // 连边。
}
} DFS(1, 0);
return ;
}
int main() {
freopen(".in", "r", stdin); freopen(".out", "w", stdout);
ios :: sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n; for (int i = 1; i <= n; i ++) cin >> X[i] >> Y[i];
int mnx = X[1], mny = Y[1];
for (int i = 2; i <= n; i ++) mnx = min(mnx, X[i]), mny = min(mny, Y[i]);
for (int i = 1; i <= n; i ++) X[i] -= mnx - 1, Y[i] -= mny - 1;
Solve();
for (int i = 1; i <= n; i ++) swap(X[i], Y[i]);
Solve();
cout << Ans << "\n";
return 0;
}