完全沒有基礎,要怎樣學好建模呢?一篇文章解決入門技巧
CF 1368 G
題目連結
題目敘述
給出一個\(n\times m\)的棋盤,佈滿了 \(1\times2\) 的多米諾骨牌,告訴你多米諾骨牌的排列情況。每次去掉一個股派,然後可以將其他骨牌挪動。每個骨牌都會被去掉一次。挪動後必須與最開始的位置至少公用一個位置。並且骨牌挪動方向必須平行於骨牌原本的方向(即不能“拐彎”)。問挪動若干次後剩下的兩個空格的方案數。
題解
問題轉化
考慮將骨牌移動想象成空格移動。對於一個空位置 \((r,c)\) (第 \(r\) 行第 \(c\) 列),如果 \((r,c)\) 下面 \((r+1,c),(r+2,c)\) 是一個多米諾骨牌,那麼 \((r,c)\)
下面證明這是一個樹。只要證明不存在一個環即可。如果存在一個環,那麼一定形如這樣:
考慮下面這樣的形狀:
可以將這樣的東西向外翻,一次將會新增 4 個格子,這樣不斷操作一定能操作成一個長方形。然後可以發現中間空出來一個長寬均為奇數的長方形,很明顯這樣多米諾骨牌並不能將這個長方形填滿。
解決
所以像上面那樣連邊將會連出一個森林。只要一對格子可以被兩個組成一個多米諾骨牌的格子移動到,那麼就算 1 的貢獻。即對於兩個結點,存在兩個祖先滿足他們組成多米諾骨牌的兩個格子,那麼就算 1 的貢獻。
這個問題可以轉化為一個矩陣面積並的問題。考慮將每一對點 \((x,y)\) ,將其對應到平面上的點 \((dfn_x,dfn_y)\) (\(dfn_i\) 為 \(i\) 結點的 \(dfs\) 序)。現在列舉每一塊骨牌,如果是由編號為 \(x\) 和 \(y\) 的樹上的結點所對應的方格組成,那麼就將 \(x\) 的子樹所對應的區間作為 \(x\) 軸的範圍, \(y\) 的子樹對應的區間最為 \(y\) 軸的範圍,給這樣組成的矩形中的所有點覆蓋。通過一共覆蓋的點數量即可算出答案。覆蓋點數只需要使用掃描線求解即可。
實現
會發現樹上結點 \(x\) 和 \(y\) 組成的矩形與 \(y\)
#include <cstdio>
#include <algorithm>
using namespace std;
const int NTOT = 2e5 + 5;
int N, M, deg[NTOT], root[NTOT], totr;
int in[NTOT], out[NTOT], dfstime;
char str[NTOT];
inline int dw(int r, int c) { return (r-1)*M+c; }
int head[NTOT];
struct E {
int v, nxt;
E(int _v, int _n) : v(_v), nxt(_n) {}
E() {}
} edge[NTOT*2];
int totE;
void AddEdge(int u, int v) {
edge[++totE] = E(v, head[u]);
head[u] = totE;
}
void Dfs(int u) {
in[u] = ++dfstime;
for (int p = head[u]; p; p = edge[p].nxt) {
int v = edge[p].v;
Dfs(v);
}
out[u] = dfstime;
}
int tsqr;
struct Square {
int u, d, l, r;
Square(int _u, int _d, int _l, int _r) : u(_u), d(_d), l(_l), r(_r) {}
Square() {}
} sqr[NTOT];
int tqz;
struct smx {
int u, d, ps, ad;
smx(int _u, int _d, int _p, int _a) : u(_u), d(_d), ps(_p), ad(_a) {}
smx() {}
} qz[NTOT*2];
bool cmp(smx &a, smx &b) { return a.ps < b.ps; }
int tag[NTOT*4], cnt[NTOT*4];
int Len(int L, int R) { return R - L + 1; }
int Cal(int p, int L, int R) {
if (!tag[p]) return cnt[p];
else return Len(L, R);
}
void AddSeg(int p, int L, int R, int ql, int qr, int a) {
if (qr < L || R < ql)
return ;
if (ql <= L && R <= qr) {
tag[p] += a;
return ;
}
int m = (L + R) >> 1;
AddSeg(p<<1, L, m, ql, qr, a);
AddSeg(p<<1|1, m+1, R, ql, qr, a);
cnt[p] = Cal(p<<1, L, m) + Cal(p<<1|1, m+1, R);
}
int main() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; ++i)
scanf("%s", str + (i-1) * M + 1);
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j) {
if (i-2 >= 1 && str[dw(i-2, j)] == 'U') {
AddEdge(dw(i, j), dw(i-2, j));
++deg[dw(i-2, j)];
}
if (j-2 >= 1 && str[dw(i, j-2)] == 'L') {
AddEdge(dw(i, j), dw(i, j-2));
++deg[dw(i, j-2)];
}
if (i+2 <= N && str[dw(i+2, j)] == 'D') {
AddEdge(dw(i, j), dw(i+2, j));
++deg[dw(i+2, j)];
}
if (j+2 <= M && str[dw(i, j+2)] == 'R') {
AddEdge(dw(i, j), dw(i, j+2));
++deg[dw(i, j+2)];
}
}
for (int i = 1; i <= N*M; ++i)
if (!deg[i])
root[++totr] = i;
for (int i = 1; i <= totr; ++i)
Dfs(root[i]);
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j) {
if (str[dw(i, j)] == 'U' && str[dw(i+1, j)] == 'D') {
sqr[++tsqr] = Square(in[dw(i, j)], out[dw(i, j)], in[dw(i+1, j)], out[dw(i+1, j)]);
sqr[++tsqr] = Square(in[dw(i+1, j)], out[dw(i+1, j)], in[dw(i, j)], out[dw(i, j)]);
} else if (str[dw(i, j)] == 'L' && str[dw(i, j+1)] == 'R') {
sqr[++tsqr] = Square(in[dw(i, j)], out[dw(i, j)], in[dw(i, j+1)], out[dw(i, j+1)]);
sqr[++tsqr] = Square(in[dw(i, j+1)], out[dw(i, j+1)], in[dw(i, j)], out[dw(i, j)]);
}
}
for (int i = 1; i <= tsqr; ++i) {
qz[++tqz] = smx(sqr[i].u, sqr[i].d, sqr[i].l, 1);
qz[++tqz] = smx(sqr[i].u, sqr[i].d, sqr[i].r+1, -1);
}
sort(qz + 1, qz + tqz + 1, cmp);
long long ans = 0;
for (int i = 1; i <= tqz; ++i) {
AddSeg(1, 1, N*M+1, qz[i].u, qz[i].d, qz[i].ad);
if (i != tqz) ans += (long long)Cal(1, 1, N) * (qz[i+1].ps - qz[i].ps);
// for (int j = 1; j <= 8; ++j)
// printf("tag[j] : %d cnt[j] : %d\n", tag[j], cnt[j]);
}
printf("%lld\n", ans / 2);
return 0;
}