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\) 个炸弹的答案
转移枚举左右子树分别放多少炸弹,子树的根放不放即可

\(\text{Code}\)

#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\)

\(\text{Code}\)

#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}\) 的前一维表示在哪个环内
写起来麻烦了

考场代码瞅瞅

\(\text{Code}\)

#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());
}
posted @ 2022-01-21 22:14  leiyuanze  阅读(43)  评论(0编辑  收藏  举报