ABC211 覆盤

2020luke發表於2024-04-17

ABC211 覆盤

[ABC211C] chokudai

思路解析

題目說的很明白,看到匹配子序列可以輕易想到是簡單 dp,直接做即可。

時間複雜度:兩個字串兩層迴圈,\(O(8 \times N)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const long long mod = 1e9 + 7;
string t = " chokudai";
string s;
long long f[15];
int main() {
	cin >> s;
	s = ' ' + s;
	f[0] = 1;
	for(int i = 1; i < s.size(); i++) {
		for(int j = 1; j <= 8; j++) {
			if(s[i] == t[j]) {
				f[j] = (f[j] + f[j - 1]) % mod;
			}
		}
	}
	cout << f[8];
	return 0;
}

[ABC211D] Number of Shortest paths

思路解析

題目其實說得很明白了,就是最短路計數。我們可以用一個 \(s_i\) 表示從起點到 \(i\) 的最短路計數,然後進行 bfs,由於邊權為 \(1\),所以可以使用 bfs 求最短路。如果 \(u \to v\)\(v\) 的最短路的其中一段,就把 \(s_u \to s_v\) 轉移即可。

注意要記得取模。

時間複雜度:bfs,複雜度 \(O(N)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, mod = 1e9 + 7;
int n, m, d[N], s[N];
vector<int> g[N];
void bfs() {
	memset(d, 0x3f, sizeof(d));
	d[1] = 0; s[1] = 1;
	queue<int> q;
	q.push(1);
	while(!q.empty()) {
		int u = q.front(); q.pop();
		for(auto it : g[u]) {
			if(d[it] >= d[u] + 1) {
				if(d[it] > 1e8) q.push(it);
				d[it] = d[u] + 1;
				s[it] = (s[it] + s[u]) % mod;
			}
		}
	}
}
int main() {
	cin >> n >> m;
	for(int i = 1, u, v; i <= m; i++) {
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	bfs();
	cout << s[n];
	return 0;
}

[ABC211E] Red Polyomino

思路解析

可見直接列舉每個格子是否著色會 TLE,於是可以想到 dfs,dfs(x) 表示我們已經用了 \(x\) 個紅色方格,然後只找周圍又紅色格子的白格子轉移,然後只要在每一個 dfs 裡只進行一次遞迴後就返回,就可以避免 TLE 和重複方案。

code

#include<bits/stdc++.h>
using namespace std;
const int N = 10;
int n, k, ans = 0;
char ch[N][N];
int dx[4] = {0, 0, -1, 1};
int dy[4] = {-1, 1, 0, 0};
bool check(int x, int y) {
	return (x > 0 && x <= n && y > 0 && y <= n);
}
void dfs(int c) {
	if(c == k) return (void)(ans++);
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			if(ch[i][j] == '.') {
				bool flag = false;
				for(int d = 0; d < 4; d++) {
					int nx = i + dx[d], ny = j + dy[d];
					flag |= (check(nx, ny) && (ch[nx][ny] == '@'));
				}
				if(flag || c == 0) {
					ch[i][j] = '@'; dfs(c + 1);
					ch[i][j] = '#'; dfs(c);
					ch[i][j] = '.';
					return;
				}
			}
		}
	}
}
int main() {
	cin >> n >> k;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			cin >> ch[i][j];
		}
	}
	dfs(0);
	cout << ans;
	return 0;
}

[ABC211F] Rectilinear Polygons

思路解析

先說結論:掃描線。顧名思義,掃描線的本質就是用一條線沿著 \(x\)\(y\) 軸掃過去,每碰到一條邊就記錄一下加邊後是面積是增加還是減少,然後用樹狀陣列或線段樹統計。由於我們並不需要知道整張圖所有的位置的值,我們就只需要求出來掃描線所在的那一行 or 列每一個點的值即可。

這裡就不過多解釋,如果沒有學過掃描線建議去做一下模板題 ,推薦看第一篇題解。

接下來就是本題的重點,該如何把不規則圖形存下來。如下圖,我們先把圖上的豎邊找出來,同時給點的順序標一個號。

可見四條邊中左邊三條的權值都是增加的,只有右邊一條是減少的。我們尋找邊上的兩點和權值的關係,可以發現,由於是點順序輸入的,所以每一條正邊權的邊的兩點中編號較小的點 \(y\) 值也是更小的,而每一條邊的兩點中編號較小的點的編號一定是奇數。因此我們可以給每一條邊都判斷兩點編號的大小來決定是用正邊權還是負邊權。

實現

我們根據以上所說的方式存好每一條邊,然後將邊按 \(x\) 的值排序,滿足掃描線的性質。對於詢問我們將詢問離線下來,同樣按 \(x\) 排序,這樣我們在進行加邊之前判斷當前掃描線是否已經處理了所有 \(x\) 小於當前詢問的 \(x\) 的邊,若已經處理完就更新答案。

接下來有幾點注意事項。

  • 題目中的輸入點的順序不一定滿足我們想要的輸入順序,所以我們需要自己找到一個在 \(x\) 最小的情況下 \(y\) 也最小的點做編號為 \(1\) 的點。
  • 由於我們只需要知道一個點被覆蓋的次數,所以可以將模板中的線段樹換成樹狀陣列區修單查。
  • 由於我們需要先把當前 \(x\) 軸上的邊加完再統計詢問,所以我們要在所有邊的最後加上一個 \(x=\inf,val=0\) 的邊,這樣就能防止最後有幾個點沒有處理到。

code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 1e5;
int n, m, xx[N], yy[N], tv[N], q, c[N], ans[N];
struct side {
	int x, ya, yb, val;
};
struct point {
	int x, ya, yb; 
};
struct query {
	int x, y, p;
};
void add(int x, int y) {
	for(; x <= M; x += (x & -x)) {
		c[x] += y;
	}
}
int ask(int x) {
	int sum = 0;
	for(; x > 0; x -= (x & -x)) {
		sum += c[x];
	}
	return sum;
}
int main() {
	cin >> n;
	vector<side> v;
	for(int i = 1; i <= n; i++) {
		cin >> m;
		for(int i = 0; i < m; i++) {
			cin >> xx[i] >> yy[i];
			xx[i]++; yy[i]++;
		}
		int p = 0;
		for(int i = 0; i < m; i++) {
			if(xx[i] == xx[p] && yy[i] < yy[p]) p = i;	//找到起始點,注意 y 軸也要判
			else if(xx[i] < xx[p]) p = i;
		}
		int val = 1;
		for(int i = 0; i < m; i++) {
			int w = (i + p) % m;
			tv[w] = val;	//給點加權
			val = -val;
		}
		for(int i = 0; i < m; i += 2) {	//判斷
			if(yy[i] < yy[i + 1]) v.push_back({xx[i], yy[i], yy[i + 1], tv[i]});
			else v.push_back({xx[i], yy[i + 1], yy[i], tv[i + 1]});
		}
	}
	sort(v.begin(), v.end(), [](side x, side y) {	//按 x 排序
		if(x.x != y.x) return x.x < y.x;
		else return x.ya < y.ya;
	});
	v.push_back({M + 1, 1, 1, 0});	//避免結尾有點沒判
	vector<query> que;
	cin >> q;
	for(int i = 1; i <= q; i++) {
		cin >> xx[i] >> yy[i];
		xx[i]++; yy[i]++;
		que.push_back({xx[i], yy[i], i});	//離線
	}
	sort(que.begin(), que.end(), [](query x, query y) {	//排序
		if(x.x != y.x) return x.x < y.x;
		else return x.y < y.y;
	});
	int pos = 0;
	for(auto it : v) {
		int x = it.x, ya = it.ya, yb = it.yb, val = it.val;
		for(; que[pos].x < x && pos < (int)que.size(); pos++) {
			ans[que[pos].p] = ask(que[pos].y);	//若已經操作完則更新
		}
		add(ya, val); add(yb, -val);	//加權
	}
	for(int i = 1; i <= q; i++) cout << ans[i] << '\n';
	return 0;
}