【USACO】2019 February Contest, Platinum题解

**【T1】**Cow Dating

【题目链接】

  • 点击打开链接

【题解链接】

  • 点击打开链接

【思路要点】

  • 考虑如何快速计算一个区间 [ l , r ] [l,r] [l,r] 的价值。
  • 定义二元组 ( x , y ) (x,y) (x,y) 来描述一个区间, x x x 表示区间中恰好有一个关键点的概率, y y y 表示区间中没有关键点的概率。显然,对于两个区间 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2) ,上述信息是可以合并的,即 ( x 1 , y 1 ) + ( x 2 , y 2 ) = ( x 1 y 2 + x 2 y 1 , y 1 y 2 ) (x_1,y_1)+(x_2,y_2)=(x_1y_2+x_2y_1,y_1y_2) (x1,y1)+(x2,y2)=(x1y2+x2y1,y1y2) ,并且,该运算存在逆运算,即 ( x 1 , y 1 ) − ( x 2 , y 2 ) = ( x 1 − y 1 ∗ x 2 y 2 y 2 , y 1 y 2 ) (x_1,y_1)-(x_2,y_2)=(\frac{x_1-y_1*\frac{x_2}{y_2}}{y_2},\frac{y_1}{y_2}) (x1,y1)(x2,y2)=(y2x1y1y2x2,y2y1)
  • 那么,记录二元组的前缀和数组 s i s_i si ,区间 [ l , r ] [l,r] [l,r] 对应的二元组即为 s r − s l − 1 s_r-s_{l-1} srsl1
  • 考虑枚举 r r r ,最优化 l l l 的选取,即我们需要找到 l l l ,最大化 x 1 − y 1 ∗ x 2 y 2 y 2 \frac{x_1-y_1*\frac{x_2}{y_2}}{y_2} y2x1y1y2x2 。记 k = x 2 y 2 k=\frac{x_2}{y_2} k=y2x2 ,即最大化 x 1 − k ∗ y 1 x_1-k*y_1 x1ky1 ,注意到 k = x 2 y 2 k=\frac{x_2}{y_2} k=y2x2 是单调的,因此采用朴素的斜率优化即可。
  • 时间复杂度 O ( N ) O(N) O(N)

【代码】

#include
using namespace std;
const int MAXN = 1e6 + 5;
const long double eps = 1e-8;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
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 n, l, r, st[MAXN];
ld a[MAXN], x[MAXN], y[MAXN];
bool exclude(int a, int b, int c) {
	return (y[b] - y[a]) * (x[c] - x[b]) - (y[c] - y[b]) * (x[b] - x[a]) > 0;
}
int main() {
	freopen("cowdate.in", "r", stdin);
	freopen("cowdate.out", "w", stdout);
	read(n), x[0] = 0, y[0] = 1;
	for (int i = 1; i <= n; i++) {
		int v; read(v);
		a[i] = v * 0.000001;
		x[i] = a[i] * y[i - 1] + (1 - a[i]) * x[i - 1];
		y[i] = y[i - 1] * (1 - a[i]);
	}
	ld res = 0;
	for (int i = n; i >= 0; i--) {
		ld k = x[i] / y[i];
		while (r - l + 1 >= 2 && x[st[l + 1]] - y[st[l + 1]] * k > x[st[l]] - y[st[l]] * k) l++;
		if (r > l) chkmax(res, (x[st[l]] - y[st[l]] * k) / y[i]);
		while (r - l + 1 >= 2 && exclude(st[r - 1], st[r], i)) r--;
		st[++r] = i;
	}
	int finalans = (res + eps) * 1000000;
	printf("%d\n", finalans);
	return 0;
}

**【T2】**Moorio Kart

【题目链接】

  • 点击打开链接

【题解链接】

  • 点击打开链接

【思路要点】

  • 考虑用所有的路径减去不合法的路径的长度。
  • 所有路径的长度是容易计算的,只需要考虑每一条边被统计了多少次。
  • 不合法的路径即长度不足 Y Y Y 的路径,可以通过每一个联通块内路径的数组卷积得到。
  • 注意到当且仅当联通块大小达到 O ( V ) O(\sqrt{V}) O(V ) 的级别,不足 Y Y Y 的所有路径长度才有可能被全部取到,因此在卷积时特判数组中为 0 0 0 的位置跳过即可。
  • 时间复杂度 O ( N V V ) O(NV\sqrt{V}) O(NVV )

【代码】

#include
using namespace std;
const int MAXN = 3005;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
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 n, m, x, y, ans, f[MAXN];
int cnt[MAXN][MAXN], res[MAXN];
vector <pair <int, int> > a[MAXN];
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int F(int x) {
	if (f[x] == x) return x;
	else return f[x] = F(f[x]);
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void calccnt(int pos, int fa, int from, int len) {
	if (pos > from) cnt[F(pos)][max(len, 0)]++;
	for (auto x : a[pos])
		if (x.first != fa) calccnt(x.first, pos, from, min(len + x.second, y));
}
int dfs(int pos, int fa, int out, int tot) {
	int res = 1;
	calccnt(pos, pos, pos, 0);
	for (auto x : a[pos])
		if (x.first != fa) {
			int tmp = dfs(x.first, pos, out, tot);
			update(ans, 1ll * tmp * (tot - tmp) * out % P * x.second % P);
			res += tmp;
		}
	return res;
}
int main() {
	freopen("mooriokart.in", "r", stdin);
	freopen("mooriokart.out", "w", stdout);
	read(n), read(m), read(x), read(y);
	for (int i = 1; i <= n; i++)
		f[i] = i;
	for (int i = 1; i <= m; i++) {
		int x, y, z;
		read(x), read(y), read(z);
		a[x].emplace_back(y, z);
		a[y].emplace_back(x, z);
		f[F(x)] = F(y);
	}
	static int cntp[MAXN], valp[MAXN];
	for (int i = 1; i <= n; i++)
		cntp[F(i)]++;
	int tmp = 1, com = 0, inv2 = power(2, P - 2);
	for (int i = 1; i <= n; i++)
		if (cntp[i]) {
			valp[i] = 1ll * cntp[i] * (cntp[i] - 1) % P * inv2 % P;
			tmp = 1ll * tmp * valp[i] % P, com++;
		}
	y -= x * com;
	for (int i = 1; i <= n; i++)
		if (cntp[i]) {
			dfs(i, i, 1ll * tmp * power(valp[i], P - 2) % P, cntp[i]);
			update(ans, 1ll * tmp * x % P);
		}
	if (y >= 0) {
		res[0] = 1;
		static int tmp[MAXN];
		for (int i = 1; i <= n; i++) {
			if (cntp[i] == 0) continue;
			memset(tmp, 0, sizeof(tmp));
			for (int j = 0; j <= y; j++) {
				int tnp = cnt[i][j];
				if (tnp == 0) continue;
				for (int k = 0; j + k <= y; k++)
					update(tmp[j + k], 1ll * tnp * res[k] % P);
			}
			memcpy(res, tmp, sizeof(tmp));
		}
		for (int i = 1; i <= y - 1; i++)
			update(ans, P - 1ll * res[i] * (i + x * com) % P);
	}
	for (int i = 1; i <= com - 1; i++)
		ans = 2ll * ans * i % P;
	writeln(ans);
	return 0;
}

**【T3】**Mowing Mischief

【题目链接】

  • 点击打开链接

【题解链接】

  • 点击打开链接

【思路要点】

  • 将所有点按照其横坐标为第一关键字,纵坐标为第二关键字排序,靠前的点无法转移到靠后的点。用树状数组进行动态规划,我们可以得到以每个点为结尾的选取序列的最优长度,并借此对点进行分层。
  • 属于同一层的点横坐标递增,纵坐标递减。
  • 接下来,我们只需要考虑层与层之间的转移。
  • 转移方程大致如下:
    d p i = M i n j   i n   t h e   p r e v i o u s   l a y e r   a n d   x j ≤ x i , y j ≤ y i { d p j + ( x i − x j ) ∗ ( y i − y j ) } dp_i=Min_{j\ in\ the\ previous\ layer\ and\ x_j\leq x_i,y_j\leq y_i}\{dp_j+(x_i-x_j)*(y_i-y_j)\} dpi=Minj in the previous layer and xjxi,yjyi{dpj+(xixj)(yiyj)}
    d p i = M i n j   i n   t h e   p r e v i o u s   l a y e r   a n d   x j ≤ x i , y j ≤ y i { d p j + x j y j − x i y j − x j y i ) } + x i y i dp_i=Min_{j\ in\ the\ previous\ layer\ and\ x_j\leq x_i,y_j\leq y_i}\{dp_j+x_jy_j-x_iy_j-x_jy_i)\}+x_iy_i dpi=Minj in the previous layer and xjxi,yjyi{dpj+xjyjxiyjxjyi)}+xiyi
  • 考虑 j , k j,k j,k 两处转移,令 j j j 优于 k k k ,则
    d p j + x j y j − x i y j − x j y i ≤ d p k + x k y k − x i y k − x k y i dp_j+x_jy_j-x_iy_j-x_jy_i\leq dp_k+x_ky_k-x_iy_k-x_ky_i dpj+xjyjxiyjxjyidpk+xkykxiykxkyi
    ( y k − y j ) x i + ( x k − x j ) y i ≤ d p k + x k y k − d p j − x j y j (y_k-y_j)x_i+(x_k-x_j)y_i\leq dp_k+x_ky_k-dp_j-x_jy_j (ykyj)xi+(xkxj)yidpk+xkykdpjxjyj
  • 注意到 y k − y j y_k-y_j ykyj x k − x j x_k-x_j xkxj 必然一正一负,因此这个半平面是一个斜率为正的半平面,一定会将下一层的点分为前后两部分,因而若排除掉 x j ≤ x i , y j ≤ y i x_j\leq x_i,y_j\leq y_i xjxi,yjyi 的限制,该 d p dp dp 的转移具有决策单调性。
  • 那么,先用线段树分治处理转移,再用决策单调性优化转移即可。
  • 时间复杂度 O ( N L o g 2 N ) O(NLog^2N) O(NLog2N)

【代码】

#include
using namespace std;
const int MAXN = 3e5 + 5;
const int MAXV = 1e6 + 5;
const long long INF = 1e18;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
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 BinaryIndexTree {
	int n, a[MAXV];
	void init(int x) {
		n = x;
		memset(a, 0, sizeof(a));
	}
	void modify(int x, int d) {
		for (int i = x; i <= n; i += i & -i)
			chkmax(a[i], d);
	}
	int query(int x) {
		int ans = 0;
		for (int i = x; i >= 1; i -= i & -i)
			chkmax(ans, a[i]);
		return ans;
	}
} BIT;
int n, m, t, s, layer[MAXN];
ll dp[MAXN];
pair <int, int> p[MAXN];
vector <int> pos[MAXN];
struct SegmentTree {
	struct Node {
		int lc, rc;
		vector <int> trans;
	} a[MAXN * 2];
	int n, size, root;
	vector <int> point, tmp;
	void build(int &root, int l, int r) {
		root = ++size;
		a[root].lc = a[root].rc = 0;
		a[root].trans.clear();
		if (l == r) return;
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r);
	}
	void init(vector <int> tmp) {
		point = tmp;
		n = tmp.size();
		root = size = 0;
		build(root, 0, n - 1);
	}
	void modify(int root, int l, int r, int x) {
		if (p[x].first >= p[point[r]].first && p[x].second >= p[point[l]].second) {
			a[root].trans.push_back(x);
			return;
		}
		if (p[x].first <= p[point[l]].first || p[x].second <= p[point[r]].second) return;
		int mid = (l + r) / 2;
		modify(a[root].lc, l, mid, x);
		modify(a[root].rc, mid + 1, r, x);
	}
	void modify(int x) {
		modify(root, 0, n - 1, x);
	}
	void solve(int l, int r, int ql, int qr) {
		ll ans = INF; int from = 0;
		int mid = (l + r) / 2, now = tmp[mid];
		for (int i = ql; i <= qr; i++) {
			int pos = point[i];
			ll res = dp[pos] + 1ll * (p[now].first - p[pos].first) * (p[now].second - p[pos].second);
			if (res < ans) {
				ans = res;
				from = i;
			}
		}
		chkmin(dp[now], ans);
		if (l < mid) solve(l, mid - 1, from, qr);
		if (mid < r) solve(mid + 1, r, ql, from);
	}
	void work(int root, int l, int r) {
		if (a[root].trans.size()) {
			tmp = a[root].trans;
			solve(0, tmp.size() - 1, l, r); 
		}
		if (l == r) return;
		int mid = (l + r) / 2;
		work(a[root].lc, l, mid);
		work(a[root].rc, mid + 1, r);
	}
	void work() {
		work(root, 0, n - 1);
	}
} ST;
int main() {
	freopen("mowing.in", "r", stdin);
	freopen("mowing.out", "w", stdout);
	read(n), read(t);
	for (int i = 1; i <= n; i++)
		read(p[i].first), read(p[i].second);
	sort(p + 1, p + n + 1), BIT.init(t);
	for (int i = 1; i <= n; i++) {
		layer[i] = BIT.query(p[i].second) + 1;
		BIT.modify(p[i].second, layer[i]);
		pos[layer[i]].push_back(i);
		chkmax(m, layer[i]);
	}
	for (auto x : pos[1])
		dp[x] = 1ll * p[x].first * p[x].second;
	for (int i = 2; i <= m; i++) {
		ST.init(pos[i - 1]);
		for (auto x : pos[i]) {
			dp[x] = INF;
			ST.modify(x);
		}
		ST.work();
	}
	ll ans = INF;
	for (auto x : pos[m])
		chkmin(ans, dp[x] + 1ll * (t - p[x].first) * (t - p[x].second));
	writeln(ans);
	return 0;
}

你可能感兴趣的:(【资料】好题,【比赛】USACO,【OJ】USACO,【算法】概率与期望,【算法】斜率优化,【算法】差分与前缀和思想,【数据结构】栈与单调栈,【算法】搜索与剪枝,【算法】复杂度分析,【算法】动态规划,【数据结构】线段树,【数据结构】树状数组,【算法】分治,【算法】线段树分治,【算法】决策单调性)