cz_xuyixuan的博客

当我跨过沉沦的一切,向永恒开战的时候,你是我的军旗。

【USACO】2018 January Contest, Platinum题解

【比赛经历】

  • 先看完题,准备按顺序做。
  • T1先写了一个\(O(NK^{2})\)的DP,交一发,得分8/10。
  • 仔细一想,\(O(NK)\)的做法好像可行,但稍微有点难写,决定卡常+骗分。
  • 把Max换成If语句,给循环变量加上人register,得分9/10。
  • 4次提交后,发现T的那个测试点满足\(K≥90\)、\(N≥80000\),针对性地骗分后,得到满分。
  • 此时时间刚过1h。
  • T2想了一段时间,得到一个\(O(N^{2})\)的算法,写完提交,得分2/11。
  • 一会儿过后,发现一个较强的剪枝,加上后得到满分。(实际上存在能将这个算法卡到\(O(N^{2})\)的数据)
  • 此时时间过了1.5h+。
  • 仔细想了T3后发现是简单题,写完提交,顺利满分。
  • 时间一共过了2h+。
  • 总之这场比赛并不是每道题都会做,但通过一些奇技淫巧得到了满分,笔者还是很兴奋的

【T1】Lifeguards

【题目链接】

【题解链接】

【思路要点】

  • 首先,如果区间A包含区间B,那么区间B是没有任何作用的,因此,被任何另一个区间包含的区间均可以被直接删除,并将\(K\)减1。
  • 如此操作后,区间的左右端点均是单调的,因此可以简单地设计DP。设\(F_{i,j}\)表示考虑前\(i\)个区间,保留第\(i\)个区间,且已经放弃了\(j\)个区间时,可以覆盖的最长的长度。
  • 显然有\(O(K)\)的转移,时间复杂度\(O(NK^{2})\)。
  • 笔者在考场上的做法针对较大的数据进行了初步贪心,每次选取放弃的损失最小的区间放弃,将\(K\)降至较低的一个值,然后再进行DP,在考场上可以得到满分。下面的代码也是这种做法。
  • 正确的做法应当是考虑将转移位置分为两种,一种是由与当前区间不相交的区间转移而来,这种转移可以通过记录前缀最大值做到均摊\(O(1)\);另一种是由与当前区间相交的区间转移而来,这种转移显然应该放弃尽可能多地放弃这其中的区间,选取离当前区间最远的与它相交的区间,也可以获得均摊\(O(1)\)的复杂度。
  • 总时间复杂度\(O(NK)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAXK = 105;
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 info {int l, r; };
int f[MAXN][MAXK], n, k;
info x[MAXN];
bool dis[MAXN];
bool cmp(info a, info b) {
	if (a.l == b.l) return a.r > b.r;
	else return a.l < b.l;
}
int main() {
	freopen("lifeguards.in", "r", stdin);
	freopen("lifeguards.out", "w", stdout);
	read(n), read(k);
	for (int i = 1; i <= n; i++)
		read(x[i].l), read(x[i].r);
	sort(x + 1, x + n + 1, cmp);
	int mf = 0;
	for (int i = 1; i <= n; i++) {
		if (x[i].r <= mf) k--, dis[i] = true;
		mf = max(mf, x[i].r);
	}
	int newn = 0;
	for (int i = 1; i <= n; i++)
		if (dis[i] == false) x[++newn] = x[i];
	k = max(k, 0); n = newn; newn = 0;
	if (k >= 90 && n >= 80000) {
		for (int i = 1; i <= 50; i++) {
			int opt = (int)1e9, pos = 0;
			for (int i = 2; i <= n - 1; i++) {
				int l = max(x[i].l, x[i - 1].r);
				int r = min(x[i].r, x[i + 1].l);
				if (r - l + 1 < opt) opt = r - l + 1, pos = i;
			}
			for (int i = pos; i <= n - 1; i++)
				x[i] = x[i + 1];
			k--; n--;
		}
	}
	x[++n] = (info) {(int)1e9, (int)2e9};
	for (int i = 0; i <= n; i++)
	for (int j = 0; j <= k; j++)
		f[i][j] = -(int)2e9;
	f[0][0] = 0;
	for (register int i = 1; i <= n; i++) {
		int tnp = min(k, i - 1);
		for (register int j = 0; j <= tnp; j++)
		for (register int l = 0; l <= j && i > l; l++) {
			register int tmp, t = i - l - 1;
			if (x[t].r >= x[i].l) tmp = x[i].r - x[t].r;
			else tmp = x[i].r - x[i].l;
			if (f[t][j - l] + tmp > f[i][j]) f[i][j] = f[t][j - l] + tmp;
		}
	}
	printf("%d\n", f[n][k] - (int)1e9);
	return 0;
}


【T2】Cow at Large

【题目链接】

【题解链接】

【思路要点】

  • 考虑先得到一个朴素的算法。显然,Bessie可以走到的地方是一个树上的联通块,其叶子节点数即为答案。
  • 若一个点到起点的距离小于等于其到最近的叶子结点的距离,那么Bessie可以走到这个点。
  • 因此,我们得到了一个\(O(N^{2})\)的算法,可以得到2/11分。
  • 笔者发现上述算法可以通过随机数据,但当Bessie可以到达的联通块较大时复杂度无法接受,例如一条链就可将上述算法卡到\(O(N^{2})\)。
  • 进一步思考后笔者发现如果将度为二的点缩入边权中,在移向这样的度为二的点的方向时直接移到这个方向第一个度不为二的点处,可以大大缩小在这样的情况中的联通块的大小。
  • 加上这样的剪枝后,笔者获得了满分。下面的代码也是这种做法。
  • 但仍然存在数据可以将这个做法卡到\(O(N^{2})\),例如一棵从根节点上挂下若干条长度为二的链的菊花树。
  • 正确的做法如下:考虑Bessie不能到达的点或者Bessie一旦到达就会被抓住的点,也即到起点的距离大于等于其到最近的叶子结点的距离的点。这样的点构成了树上一系列的联通块,其联通块的个数等于答案。
  • 考虑一棵这样的点构成大小为\(K\)的子树,这些点在原树上的度数之和为\(2K-1\),如果将一个点的权值定义为2减其度数,那么这一棵子树的权值之和就是1,,所有符合条件的点的权值之和就是答案。
  • 如何快速地求得起点在各点处符合条件的点的权值之和呢?
  • 对树进行DFS序标号,并分块。考虑起点从一个父亲节点移动至其某个儿子节点时,其子树中的所有点到起点的距离会减1,而其余点到起点的距离会加1。而它的子树在DFS序中是一个区间,运用树状数组对块内信息进行维护,更新权值和的变化,可以得到\(O(N\sqrt{N}LogN)\)的时间复杂度。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 70005;
const int INF = 1e9;
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 f[MAXN], q[MAXN];
vector <int> a[MAXN];
vector <int> b[MAXN];
vector <int> c[MAXN];
vector <int> d[MAXN];
int ep, ed, el;
void pro(int pos, int fa, int dist) {
	if (fa != 0 && a[pos].size() != 2) {
		ep = pos, ed = dist, el = fa;
		return;
	}
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (a[pos][i] != fa) {
			pro(a[pos][i], pos, dist + 1);
			b[pos][i] = ep;
			c[pos][i] = el;
			d[pos][i] = ed - dist;
		}
}
int work(int pos, int fa, int dist) {
	if (f[pos] <= dist) return 1;
	int ans = 0;
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (a[pos][i] != fa) ans += work(b[pos][i], c[pos][i], dist + d[pos][i]);
	return ans;
}
int main() {
	freopen("atlarge.in", "r", stdin);
	freopen("atlarge.out", "w", stdout);
	int n; read(n);
	for (int i = 1; i <= n - 1; i++) {
		int x, y;
		read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	memset(f, -1, sizeof(f));
	int l = 1, r = 0;
	for (int i = 1; i <= n; i++)
		if (a[i].size() == 1) {
			q[++r] = i;
			f[i] = 0;
		}
	while (l <= r) {
		int tmp = q[l++];
		for (unsigned i = 0; i < a[tmp].size(); i++)
			if (f[a[tmp][i]] == -1) {
				q[++r] = a[tmp][i];
				f[a[tmp][i]] = f[tmp] + 1;
			}
	}
	for (int i = 1; i <= n; i++)
	for (unsigned j = 0; j < a[i].size(); j++) {
		b[i].push_back(0);
		c[i].push_back(0);
		d[i].push_back(0);
	}
	for (int i = 1; i <= n; i++)
		if (a[i].size() != 2) pro(i, 0, 0);
	for (int i = 1; i <= n; i++)
		printf("%d\n", work(i, 0, 0));
	return 0;
}

【T3】Sprinklers

【题目链接】

【题解链接】

【思路要点】

  • 首先,计算对于横坐标\(i\)可以用纵坐标的左右端点\(L_{i}\)和\(R_{i}\)。
  • 对于纵坐标\(i\),计算在可用位置的左侧的第一个横坐标\(C_{i}\),记\(D_{i}=L_{i}-1\)。
  • 那么,显然$$Ans=\sum_{i=1}^{N}\sum_{j=L_{i}}^{R_{i}}(c_{j} - i) * (j - d_{i})$$
  • 将乘积展开,通过适当地使用前缀和,每一部分都是容易统计的。
  • 时间复杂度\(O(N)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int P = 1e9 + 7;
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 x[MAXN], l[MAXN], r[MAXN];
long long c[MAXN], d[MAXN], s[MAXN];
int main() {
	freopen("sprinklers.in", "r", stdin);
	freopen("sprinklers.out", "w", stdout);
	int n; read(n);
	for (int i = 1; i <= n; i++) {
		int y, z;
		read(y), read(z);
		x[y] = z;
	}
	int val = n;
	for (int i = 0; i <= n - 1; i++) {
		val = min(val, x[i] + 1);
		l[i + 1] = val;
	}
	val = 0;
	for (int i = n - 1; i >= 0; i--) {
		val = max(val, x[i]);
		r[i] = val;
	}
	n--;
	int j = n;
	for (int i = 1; i <= n; i++) {
		while (i > r[j]) j--;
		c[i] = j + 1;
		d[i] = l[i] - 1;
	}
	long long ans = 0;
	/*for (int i = 1; i <= n; i++)
	for (int j = l[i]; j <= r[i]; j++) {
		ans += (c[j] - i) * (j - d[i]);
		ans = (ans % P + P) % P;
	}*/
	/*For c[j] * j*/
	memset(s, 0, sizeof(s));
	for (int i = 1; i <= n; i++)
		s[i] = (s[i - 1] + c[i] * i) % P;
	for (int i = 1; i <= n; i++)
		ans = (ans + s[r[i]] - s[l[i] - 1] + P) % P;
	/*For -i * j*/
	for (int i = 1; i <= n; i++) {
		long long tmp = (r[i] + l[i]) * (r[i] - l[i] + 1ll) / 2;
		ans = (ans - tmp * i % P + P) % P;
	}
	/*For d[i] * i*/
	for (int i = 1; i <= n; i++) {
		long long tmp = d[i] * i % P;
		ans = (ans + (r[i] - l[i] + 1ll) * tmp) % P;
	}
	/*For -c[j] * d[i]*/
	memset(s, 0, sizeof(s));
	for (int i = 1; i <= n; i++)
		s[i] = (s[i - 1] + c[i]) % P;
	for (int i = 1; i <= n; i++) {
		long long tmp = (s[r[i]] - s[l[i] - 1] + P) % P;
		ans = (ans - tmp * d[i] % P + P) % P;
	}
	printf("%lld\n", ans);
	return 0;
}




阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39972971/article/details/79277544
想对作者说点什么? 我来说一句

USACO 2017 February Contest Silver

QAQ

hfl030 hfl030

2017-11-03 17:00:51

阅读数:89

没有更多推荐了,返回首页

不良信息举报

【USACO】2018 January Contest, Platinum题解

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭