天天看點

JZOJ 2022.01.21【提高A組】模拟

簡要題解加心得

不得不說這是我打得比較痛苦且改得比較痛苦的一套題了

\(\text{T1 1085. 【GDOI2008】彩球遊戲}\)

整整改了三個半小時

直接崩潰了

明明本地可以跑過去,偏偏 \(GMOJ\) 評測機很強勢

考場沒有想雙向 \(BFS\),隻單向搜了

而且還用了 \(STL\) 給予的“巨快” \(\text{unordered_map queue}\)

改題時沿用這兩個玩意,實在太慢了

一怒之下手打循環隊列和哈希表

還是手打的快啊

這種拼常數的暴搜盡量少用 \(STL\)

很容易想到把狀态壓成三進制,雙向 \(BFS\)

也是第一次打雙向 \(BFS\)

發現當一個隊列某層拓展出去一個點能得到答案後,要把兩個隊列目前層拓展完才能退出(因為同一層可能有更優答案)

用一個标記 \(flag\) 标記這層即可

\(\text{Code}\)

#include <cstdio>
#include <cstring>
#include <iostream>
#define IN inline
#define RE register
using namespace std;

int n, m;
char s[5];
struct node{
	int m[4][4];
	IN node(){
		for(RE int i = 0; i < 4; i++)
			for(RE int j = 0; j < 4; j++) m[i][j] = 0;
	}
}a, b;
const int mod = 1e6 + 7, INF = 2e9, SIZE = 2e6;
struct point{int c, s;};
struct Queue{
	point Q[SIZE]; int head, tail;
	IN Queue(){head = tail = 0;}
	IN void push(point x){++tail; if (tail >= SIZE) tail = 0; Q[tail] = x;}
	IN point front(){int t = head + 1; if (t >= SIZE) t = 0; return Q[t];}
	IN void pop(){++head; if (head >= SIZE) head = 0;}
	IN int empty(){return (head == tail);}
}Q[2];
struct Hash_table{
	int tot, h[mod];
	struct edge{int val, nxt, w;}e[3000005];
	IN void clear(){memset(h, 0, sizeof h), tot = 0;}
	IN void insert(int s, int w){int t = s % mod; e[++tot] = edge{s, h[t], w}, h[t] = tot;}
	IN int find(int s)
	{
		int t = s % mod;
		for(RE int i = h[t]; i; i = e[i].nxt) if (e[i].val == s) return e[i].w;
		return 0;
	}
}vis[2];

IN int calc(node a)
{
	int val = 0;
	for(RE int i = 0; i < n; i++)
		for(RE int j = 0; j < m; j++) val = val * 3 + a.m[i][j];
	return val;
}
IN node trans(int s)
{
	int x = n - 1, y = m - 1; node b;
	while (s){b.m[x][y] = s % 3, s /= 3; --y; if (y == -1) y = m - 1, --x;}
	return b;
}
IN int BFS()
{
	if (calc(a) == calc(b)) return 0;
	int z, res = INF, p = 0, flag = INF, cnt = 0; point x; node cur, now;
	while (!Q[0].empty()) Q[0].pop(); 
	while (!Q[1].empty()) Q[1].pop();
	vis[0].clear(), vis[1].clear();
	Q[0].push(point{1, z = calc(a)}), vis[0].insert(z, 1);
	Q[1].push(point{1, z = calc(b)}), vis[1].insert(z, 1);
	for(; !Q[0].empty() || !Q[1].empty(); p ^= 1)
	{
		if (cnt > 1) return res;
		if (Q[p].empty()) continue;
		x = Q[p].front(), Q[p].pop();
		if (x.c > flag){++cnt; continue;}
		if (z = vis[p ^ 1].find(x.s)) res = min(res, x.c + z - 2), flag = x.c;
		now = trans(x.s);
		for(RE int i = 1; i < n; i++)
			for(RE int j = 1, k; j < m; j++)
			{
				cur = now, k = cur.m[i][j];
				if (!p) cur.m[i][j] = cur.m[i-1][j], cur.m[i-1][j] = cur.m[i-1][j-1], cur.m[i-1][j-1] = cur.m[i][j-1], cur.m[i][j-1] = k;
				else cur.m[i][j] = cur.m[i][j-1], cur.m[i][j-1] = cur.m[i-1][j-1], cur.m[i-1][j-1] = cur.m[i-1][j], cur.m[i-1][j] = k;
				if (k = vis[p ^ 1].find(z = calc(cur))) res = min(res, x.c + k - 1), flag = x.c;
				if (!vis[p].find(z)) vis[p].insert(z, x.c + 1), Q[p].push(point{x.c + 1, z});
				cur = now, cur.m[i][j] += p+1, cur.m[i-1][j] += p+1, cur.m[i-1][j-1] += p+1, cur.m[i][j-1] += p+1;
				cur.m[i][j] %= 3, cur.m[i-1][j] %= 3, cur.m[i-1][j-1] %= 3, cur.m[i][j-1] %= 3;
				if (k = vis[p ^ 1].find(z = calc(cur))) res = min(res, x.c + k - 1), flag = x.c;
				if (!vis[p].find(z)) vis[p].insert(z, x.c + 1), Q[p].push(point{x.c + 1, z});
			}
	}
	return (res == INF ? -1 : res);
}

int main()
{
	scanf("%d", &n);
	while (n)
	{
		scanf("%d", &m);
		for(RE int i = 0; i < n; i++)
		{
			scanf("%s", s);
			for(RE int j = 0; j < m; j++)
				if (s[j] == 'R') a.m[i][j] = 0;
				else if (s[j] == 'B') a.m[i][j] = 1;
				else a.m[i][j] = 2;
		}
		for(RE int i = 0; i < n; i++)
		{
			scanf("%s", s);
			for(RE int j = 0; j < m; j++)
				if (s[j] == 'R') b.m[i][j] = 0;
				else if (s[j] == 'B') b.m[i][j] = 1;
				else b.m[i][j] = 2;
		}
		printf("%d\n", BFS()), scanf("%d", &n);
	}
}
           

\(\text{T2 1087. 【NOIP動态規劃專題】魚肉炸彈}\)

感覺比較難想

因為高度互不相同,一個高度可控區間是連續的

發現将區間最大值抽出來是一棵二叉樹

那麼就可以樹形 \(dp\) 了

設 \(f_{i,j}\) 表示在 \(i\) 子樹中用了 \(j\) 個炸彈的答案

轉移枚舉左右子樹分别放多少炸彈,子樹的根放不放即可

#include <cstdio>
#include <iostream>
#include <cstring>
#define LL long long
#define RE register
using namespace std;

const int N = 1e5 + 5;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, m, rt, h[N], c[N], s[N], ls[N], rs[N];
LL f[N][6];

int build(int l, int r)
{
	if (l > r) return 0;
	if (l == r) return l;
	int mid = 0;
	for(RE int i = l; i <= r; i++) if (h[i] > h[mid]) mid = i;
	ls[mid] = build(l, mid - 1), rs[mid] = build(mid + 1, r);
	return mid;
}
LL DP(int x)
{
	if (!x) return 0;
	DP(ls[x]), DP(rs[x]);
	for(RE int i = 0; i <= m; i++)
		for(RE int j = 0; i + j <= m; j++)
		{
			f[x][i + j] = min(f[x][i + j], max(f[ls[x]][i], f[rs[x]][j]) + c[x]);
			if (i + j < m) f[x][i + j + 1] = min(f[x][i + j + 1], max(f[ls[x]][i], f[rs[x]][j]));
		}
	return f[x][m];
}

int main()
{
	scanf("%d%d", &n, &m);
	for(RE int i = 1; i <= n; i++) scanf("%d%d", &h[i], &c[i]);
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 0; j <= m; j++) f[i][j] = INF;
	rt = build(1, n), printf("%lld\n", DP(rt));
}
           

\(\text{T3 1100. 【GDOI2008】狐狸的謎語}\)

考場沒有注意到 \(0\) 與後面的大數乘起來可變 \(0\) 的情況(光榮爆0)

正解是區間 \(dp\)

實際上暴搜剪枝非常快,\(dfs\) 記錄形如 \(p+q*[x..n]\) 這樣的形式即可

大力讨論填加和乘對這種形式的影響

一般的剪枝:步數大于等于目前答案,加數太大,乘數太大且後一位不是 \(0\)

#include <cstdio>
#include <cstring>
#define RE register
using namespace std;

const int N = 205;
int n, T, res;
char str[25];

void dfs(int x, int p, int q, int step)
{
	if (res != -1 && step >= res) return;
	if (x > n)
	{
		int f = 0;
		if (p == -1 || q == -1) f = (q == T || p == T);
		else if (p != -1 && q != -1) f = (p + q == T);
		return (f ? res = step : -1), void();
	}
	if (p > T || (q > T && str[x] != '0')) return;
	int s = 0;
	for(RE int i = x, y; i <= n; i++)
	{
		s = s * 10 + (str[i] ^ 48), y = (i != n);
		if (p == -1 && q == -1) dfs(i + 1, s, -1, step + y), dfs(i + 1, -1, s, step + y);
		else if (p != -1 && q != -1) dfs(i + 1, p + q * s, -1, step + y), dfs(i + 1, p, q * s, step + y);
		else if (p == -1) dfs(i + 1, q * s, -1, step + y), dfs(i + 1, -1, q * s, step + y);
		else dfs(i + 1, p + s, -1, step + y), dfs(i + 1, p, s, step + y);
	}
}

int main()
{
	scanf("%s%d", str + 1, &T);
	while (T != -1)
		n = strlen(str + 1), res = -1, dfs(1, -1, -1, 0), printf("%d\n", res), scanf("%s%d", str + 1, &T);
}
           

\(\text{T4 1160. 【GDOI2008】醬油推廣計劃}\)

考場唯一過的題,很明顯的一個 \(Tarjan\) 縮點加 \(dp\)

題目告訴我們一個點隻屬于一個環

不難想到 \(Tarjan\) 縮點後dp,設 F[i] 表示到i點最長路徑

與LG模闆不同的是,這裡每個點隻能經過一次

于是dp轉移時要考慮環内dp值的更新

先更新環内的再讓環轉移出去

按拓撲序 \(dp\)

考場比較傻,完全是個一維的 \(dp\) 硬是套成了二維

莫名其妙加了 \(F_{i,j}\) 的前一維表示在哪個環内

寫起來麻煩了

考場代碼瞅瞅

#include <cstdio>
#include <iostream>
#include <cstring>
#define RE register
#define IN inline
using namespace std;

const int N = 1e3 + 5;
int n, m, h1[N][N], h2[N][N];

int dfn[N], low[N], col[N], vis[N], st[N], top, dfc, color;
void tarjan(int x)
{
	dfn[x] = low[x] = ++dfc, st[++top] = x, vis[x] = 1;
	for(RE int i = 1; i <= n; i++)
	if (h1[x][i])
		if (!dfn[i]) tarjan(i), low[x] = min(low[x], low[i]);
		else if (vis[i]) low[x] = min(low[x], dfn[i]);
	if (dfn[x] == low[x])
	{
		col[x] = ++color, vis[x] = 0;
		while (st[top] ^ x) col[st[top]] = color, vis[st[top]] = 0, --top;
		--top;
	}
}

int g[N][N], f[N][N], in[N], Q[N], ff[N][N], sum[N][N], up[N];
void make_circle(int x, int co)
{
	g[co][++g[co][0]] = x, vis[x] = 1;
	for(RE int i = 1; i <= n; i++) if (col[i] == co && !vis[i]) make_circle(i, co);
}
IN void prepare()
{
	for(RE int i = 1; i <= color; i++)
	{
		for(RE int j = 2; j <= g[i][0]; j++)
			sum[i][j] = sum[i][j - 1] + h1[g[i][j - 1]][g[i][j]];
		up[i] = sum[i][g[i][0]] + h1[g[i][g[i][0]]][g[i][1]];
	}
}
IN int Get(int z, int l, int r){return sum[z][r] - sum[z][l - 1];}

int Topsort()
{
	int head = 0, tail = 0; memset(vis, 0, sizeof vis); prepare();
	for(RE int i = 1; i <= color; i++) if (!in[i]) Q[++tail] = i, vis[i] = 1;
	while (head < tail)
	{
		int z = Q[++head];
		for(RE int i = 1; i <= color; i++)
			if (h2[z][i] && !vis[i]){--in[i]; if (!in[i]) Q[++tail] = i;}
		for(RE int i = 1; i <= g[z][0]; i++) ff[z][g[z][i]] = f[z][g[z][i]];
		for(RE int i = 1; i <= g[z][0]; i++)
		{
			for(RE int j = 1; j < i; j++) f[z][g[z][i]] = max(f[z][g[z][i]], ff[z][g[z][j]] + Get(z, j, i));
			for(RE int j = i + 1; j <= g[z][0]; j++)
				f[z][g[z][i]] = max(f[z][g[z][i]], ff[z][g[z][j]] + up[z] - Get(z, i, j));
		}
		for(RE int i = 1; i <= g[z][0]; i++)
			for(RE int j = 1; j <= n; j++)
			if (h1[g[z][i]][j]) f[col[j]][j] = max(f[col[j]][j], f[z][g[z][i]] + h1[g[z][i]][j]);
	}
	int res = 0;
	for(RE int i = 1; i <= color; i++)
		for(RE int j = 1; j <= g[i][0]; j++) res = max(res, f[i][g[i][j]]);
	return res;
}

int main()
{
	scanf("%d%d", &n, &m);
	for(RE int i = 1, u, v, w; i <= m; i++) scanf("%d%d%d", &u, &v, &w), h1[u][v] = max(h1[u][v], w);
	for(RE int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i); memset(vis, 0, sizeof vis);
	for(RE int i = 1; i <= n; i++) if (!vis[i]) make_circle(i, col[i]);
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= n; j++)
			if (h1[i][j] && col[i] ^ col[j]) h2[col[i]][col[j]] = h1[i][j], ++in[col[j]];
	printf("%d\n", Topsort());
}