【USACO】2017 December Contest, Platinum题解

本文分享了USACO 2017 December Contest, Platinum组的比赛经历和题解。重点解析了T2题目的难点,强调了GDB和输出调试在解题中的重要性。同时,提供了T1、T2、T3三道题的解题思路,包括后缀树的应用、记忆化搜索与Tarjan算法的改编、线段树维护区间问题,以及相应的代码实现。" 76694771,7016226,电池与外部电源无缝切换电路设计详解,"['电源管理', '电路设计', '电子工程', '双电源供电', 'MOS管应用']

【比赛经历】

  • 大概顺利满分了,就是T2的代码比较难调。
  • T2能够直观地反映出GDB和输出调试结合的优越性。

【T1】Standing Out from the Herd

【题目链接】

【题解链接】

【思路要点】

  • 后缀的前缀是子串,考虑使用后缀结构来解题。笔者选用的是后缀树。
  • 对所有询问串建立多串的后缀树,DFS,在访问一个子树中后缀标记唯一的点时,将其父边的长度加到对应字符串的答案计数器中即可。
  • 时间复杂度\(O(\sum|S|)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
#define MAXN	200005
#define MAXC	26
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct Suffix_Automaton {
	int child[MAXN][MAXC];
	int father[MAXN], depth[MAXN];
	int root, size, last, n;
	vector <int> a[MAXN];
	int colour[MAXN];
	bool unq[MAXN];
	long long ans[MAXN];
	int new_node(int dep) {
		depth[size] = dep;
		unq[size] = true;
		return size++;
	}
	void init() {
		size = 0;
		root = last = new_node(0);
	}
	void addcol(int pos, int col) {
		if (!unq[pos]) return;
		if (colour[pos] == 0) colour[pos] = col;
		else if (col != colour[pos]) unq[pos] = false;
	}
	void Extend(int ch, int col) {
		int np = child[last][ch];
		if (np) {
			if (depth[np] == depth[last] + 1)  {
				addcol(np, col);
				last = np;
			} else {
				int nq = new_node(depth[last] + 1);
				father[nq] = father[np];
				father[np] = nq;
				memcpy(child[nq], child[np], sizeof(child[np]));
				for (int p = last; child[p][ch] == np; p = father[p])
					child[p][ch] = nq;
				addcol(nq, col);
				last = nq;
			}
		} else {
			int np = new_node(depth[last] + 1);
			int p = last;
			for (; child[p][ch] == 0; p = father[p])
				child[p][ch] = np;
			if (child[p][ch] == np) {
				addcol(np, col);
				last = np;
				return;
			}
			int q = child[p][ch];
			if (depth[q] == depth[p] + 1) {
				father[np] = q;
				addcol(np, col);
				last = np;
				return;
			} else {
				int nq = new_node(depth[p] + 1);
				father[nq] = father[q];
				father[np] = father[q] = nq;
				memcpy(child[nq], child[q], sizeof(child[q]));
				for (; child[p][ch] == q; p = father[p])
					child[p][ch] = nq;
				addcol(np, col);
				last = np;
			}
		}
	}
	void insert(char *s, int col) {
		int len = strlen(s + 1);
		last = root; ans[col] = 0;
		for (int i = 1; i <= len; i++)
			Extend(s[i] - 'a', col);
		n = col;
	}
	void work(int pos) {
		for (unsigned i = 0; i < a[pos].size(); i++) {
			work(a[pos][i]);
			if (unq[a[pos][i]]) addcol(pos, colour[a[pos][i]]);
			else unq[pos] = false;
		}
		if (pos != 0 && unq[pos]) ans[colour[pos]] += depth[pos] - depth[father[pos]];
	}
	void solve() {
		for (int i = 1; i < size; i++)
			a[father[i]].push_back(i);
		work(0);
		for (int i = 1; i <= n; i++)
			printf("%lld\n", ans[i]);
	}
} SAM;
char s[MAXN];
int main() {
	freopen("standingout.in", "r", stdin);
	freopen("standingout.out", "w", stdout);
	SAM.init();
	int n; read(n);
	for (int i = 1; i <= n; i++) {
		scanf("%s", s + 1);
		SAM.insert(s, i);
	}
	SAM.solve();
	return 0;
}



【T2】Push a Box

【题目链接】

【题解链接】

【思路要点】

  • 首先,如果我们已经得到了每一个点在删去之后其上下左右四个点的连通性,那么简单的记忆化搜索即可解决本题。
  • 如何求解这个信息呢?我们发现,“删去一个点”的前提与点双联通分量的定义十分类似,我们考虑对传统的Tarjan算法进行改编,来求得上述信息。
  • 具体来说,在一个点刚刚被访问时,它已经被访问过的相邻的点以及能够与其在DFS树上的祖先联通的点是在删去这个点后依然相互联通的;在结束对它的一个子节点的访问时,我们可以得到,其他在访问这个子节点时新被访问的子节点应当与这个子节点在删去这个点时相互联通。
  • 这个过程比较难以说清,如有看不懂的读者可以自行阅读下文的代码。在这个程序中,笔者对每一个点开设了一个大小为4的数组(Col数组),如果某个点的两个方向在删去它之后依然联通,那么对应位置的值相等。另外,笔者考试时的调试函数也保留在了这个程序中。
  • 时间复杂度\(O(NM+Q)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
#define MAXN	1505
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int pr[4] = {1, 0, 3, 2};
//0: Right, 1: Left, 2: Down, 3: Up.
char mp[MAXN][MAXN];
int n, m, q, sx, sy, tx, ty, sd;
int num[MAXN][MAXN], col[MAXN][MAXN][4];
int timer, dfn[MAXN][MAXN], low[MAXN][MAXN];
bool tv[MAXN][MAXN], visited[MAXN][MAXN][4];
bool valid(char x) {
	return x == '.' || x == 'A' || x == 'B';
}
void work(int px, int py, int fx, int fy, int fd) {
	dfn[px][py] = low[px][py] = ++timer;
	int tmp = 0;
	if (fd != -1) {
		col[px][py][fd] = ++tmp;
		for (int j = 0; j <= 3; j++) {
			int tx = px + dx[j];
			int ty = py + dy[j];
			if (!valid(mp[tx][ty])) continue;
			if (dfn[tx][ty] && col[px][py][j] == 0)
				col[px][py][j] = tmp;
		}
	}
	for (int i = 0; i <= 3; i++) {
		int tx = px + dx[i];
		int ty = py + dy[i];
		if (tx == fx && ty == fy) continue;
		if (!valid(mp[tx][ty])) continue;
		if (dfn[tx][ty]) {
			low[px][py] = min(low[px][py], dfn[tx][ty]);
			continue;
		}
		work(tx, ty, px, py, pr[i]);
		low[px][py] = min(low[px][py], low[tx][ty]);
		if (low[tx][ty] >= dfn[px][py]) {
			col[px][py][i] = ++tmp;
			for (int j = i + 1; j <= 3; j++) {
				int tx = px + dx[j];
				int ty = py + dy[j];
				if (!valid(mp[tx][ty])) continue;
				if (dfn[tx][ty] && col[px][py][j] == 0)
					col[px][py][j] = tmp;
			}
		} else {
			col[px][py][i] = 1;
			for (int j = i + 1; j <= 3; j++) {
				int tx = px + dx[j];
				int ty = py + dy[j];
				if (!valid(mp[tx][ty])) continue;
				if (dfn[tx][ty] && col[px][py][j] == 0)
					col[px][py][j] = 1;
			}
		}
	}
}
void printcol() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			for (int k = 0; k <= 3; k++)
				printf("%d", col[i][j][k]);
			printf(" ");
		}
		printf("\n");
	}
}
void getsd(int px, int py, int ld) {
	tv[px][py] = true;
	if (px == tx && py == ty) sd = ld;
	for (int i = 0; i <= 3; i++) {
		int tx = px + dx[i];
		int ty = py + dy[i];
		if (!valid(mp[tx][ty]) || tv[tx][ty]) continue;
		getsd(tx, ty, pr[i]);
	}
}
void dfs(int px, int py, int ld) {
	visited[px][py][ld] = true;
	int td = pr[ld];
	if (valid(mp[px + dx[td]][py + dy[td]]) && !visited[px + dx[td]][py + dy[td]][ld]) {
		dfs(px + dx[td], py + dy[td], ld);
	}
	for (int i = 0; i <= 3; i++)
		if (!visited[px][py][i] && col[px][py][i] == col[px][py][ld]) {
			dfs(px, py, i);
		}
}
void printvis() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			for (int k = 0; k <= 3; k++)
				printf("%d", visited[i][j][k]);
			printf(" ");
		}
		printf("\n");
	}
}
int main() {
	freopen("pushabox.in", "r", stdin);
	freopen("pushabox.out", "w", stdout);
	read(n), read(m), read(q);
	for (int i = 1; i <= n; i++)
		scanf("%s", mp[i] + 1);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++)
		if (dfn[i][j] == 0 && mp[i][j] != '#') {
			work(i, j, 0, 0, -1);
		}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++) {
		if (mp[i][j] == 'A') sx = i, sy = j;
		if (mp[i][j] == 'B') tx = i, ty = j;
	}
	sd = -1;
	getsd(sx, sy, -1);
	if (sd == -1) visited[tx][ty][0] = true;
	else dfs(tx, ty, sd);
	for (int i = 1; i <= q; i++) {
		int x, y;
		read(x), read(y);
		bool flg = false;
		for (int j = 0; j <= 3; j++)
			flg |= visited[x][y][j];
		if (flg) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}


【T3】Greedy Gift Takers

【题目链接】

【题解链接】

【思路要点】

  • 无法得到礼物的人一定是某一段右端点为序列末尾的区间中的人。
  • 进一步地,如果在位置1到位置\(j\)中一共出现了\(i\)个大于等于\(N-i\)的数,那么\(j+1\)号位的人无法得到礼物。
  • 依次扫描数列,用线段树维护一个支持区间减法的序列,在第一次出现一个\(i\)满足一共出现了\(i\)个大于等于\(N-i\)的数时得到答案。
  • 时间复杂度\(O(NLogN)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
#define MAXN	200005
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct SegmentTree {
	struct Node {
		int lc, rc;
		int Min, tag;
	} a[MAXN];
	int n, root, size;
	void pushdown(int root) {
		if (a[root].tag) {
			int tmp = a[root].tag, pos;
			pos = a[root].lc;
			a[pos].Min -= tmp;
			a[pos].tag += tmp;
			pos = a[root].rc;
			a[pos].Min -= tmp;
			a[pos].tag += tmp;
			a[root].tag = 0;
		}
	}
	void update(int root) {
		a[root].Min = min(a[a[root].lc].Min, a[a[root].rc].Min);
	}
	void build(int &root, int l, int r) {
		root = ++size;
		if (l == r) {
			a[root].Min = n - l;
			return;
		}
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r); 
		update(root);
	}
	void init(int x) {
		n = x; size = root = 0;
		build(root, 0, n - 1);
	}
	void modify(int root, int l, int r, int ql, int qr, int d) {
		if (l == ql && r == qr) {
			a[root].tag += d;
			a[root].Min -= d;
			return;
		}
		pushdown(root);
		int mid = (l + r) / 2;
		if (mid >= ql) modify(a[root].lc, l, mid, ql, min(mid, qr), d);
		if (mid + 1 <= qr) modify(a[root].rc, mid + 1, r, max(mid + 1, ql), qr, d);
		update(root);
	}
	void solve(int x) {
		modify(root, 0, n - 1, 0, x, 1);
	}
	int query() {
		return a[root].Min;
	}
} SMT;
int main() {
	freopen("greedy.in", "r", stdin);
	freopen("greedy.out", "w", stdout);
	int n; read(n);
	SMT.init(n);
	for (int i = 1; i <= n; i++) {
		int x; read(x);
		SMT.solve(x);
		if (SMT.query() == 0) {
			printf("%d\n", n - i);
			return 0;
		}
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值