1. 程式人生 > 實用技巧 >完全沒有基礎,要怎樣學好建模呢?一篇文章解決入門技巧

完全沒有基礎,要怎樣學好建模呢?一篇文章解決入門技巧

CF 1368 G

題目連結

點選開啟

題目敘述

給出一個\(n\times m\)的棋盤,佈滿了 \(1\times2\) 的多米諾骨牌,告訴你多米諾骨牌的排列情況。每次去掉一個股派,然後可以將其他骨牌挪動。每個骨牌都會被去掉一次。挪動後必須與最開始的位置至少公用一個位置。並且骨牌挪動方向必須平行於骨牌原本的方向(即不能“拐彎”)。問挪動若干次後剩下的兩個空格的方案數。

題解

問題轉化

考慮將骨牌移動想象成空格移動。對於一個空位置 \((r,c)\) (第 \(r\) 行第 \(c\) 列),如果 \((r,c)\) 下面 \((r+1,c),(r+2,c)\) 是一個多米諾骨牌,那麼 \((r,c)\)

這個空格可以移動到 \((r+2,c)\) 。這樣連出若干條邊,構成一個圖。容易知道每個點的入讀均為 1.

下面證明這是一個樹。只要證明不存在一個環即可。如果存在一個環,那麼一定形如這樣:

考慮下面這樣的形狀:

可以將這樣的東西向外翻,一次將會新增 4 個格子,這樣不斷操作一定能操作成一個長方形。然後可以發現中間空出來一個長寬均為奇數的長方形,很明顯這樣多米諾骨牌並不能將這個長方形填滿。

解決

所以像上面那樣連邊將會連出一個森林。只要一對格子可以被兩個組成一個多米諾骨牌的格子移動到,那麼就算 1 的貢獻。即對於兩個結點,存在兩個祖先滿足他們組成多米諾骨牌的兩個格子,那麼就算 1 的貢獻。

這個問題可以轉化為一個矩陣面積並的問題。考慮將每一對點 \((x,y)\) ,將其對應到平面上的點 \((dfn_x,dfn_y)\)\(dfn_i\)\(i\) 結點的 \(dfs\) 序)。現在列舉每一塊骨牌,如果是由編號為 \(x\)\(y\) 的樹上的結點所對應的方格組成,那麼就將 \(x\) 的子樹所對應的區間作為 \(x\) 軸的範圍, \(y\) 的子樹對應的區間最為 \(y\) 軸的範圍,給這樣組成的矩形中的所有點覆蓋。通過一共覆蓋的點數量即可算出答案。覆蓋點數只需要使用掃描線求解即可。

實現

會發現樹上結點 \(x\)\(y\) 組成的矩形與 \(y\)

\(x\) 組成的矩形不一樣。仔細思考可以發現都覆蓋上,再將答案/2即可。

#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;
}