USACO2024 Jan 合集

Platinum

啥也不会。官方题解写的很好。

T1

题解没看懂。不会仙人掌不会生成函数。

T2

题意: 有一行 \(n\) 个石子,大小为 \(s_1,\cdots,s_n\),每次等概率挑选一对相邻的石子 \(a\)\(b\) 合并,新编号等于大小更大的石子的编号(如果大小相等就是更大的编号为新编号),最后剩下的编号是 \(i\) 的概率为 \(f_i\),求 \(1 \sim f_n\)\(n \le 5000\)

有意思的区间 DP。

\(sum(l, r) = \sum_{l \le i \le r}s_i\)

枚举最后剩下的 \(c\),显然任何时候 \(c\) 都存在,我们考虑区间 \([l, r]\) 合并之后 \(c\) 是最后剩下的概率是 \(dp[l][r]\)。最后一次合并,等概率选取一个分界点,对于包含 \(c\) 的,我们会计算 \(dp\) 更小的区间。所有得到:

\[dp[l][r] = \sum_{\substack{l < m \le c\\sum(l, m - 1) \le sum(m, r)}}\frac{dp[m][r]}{r - l} + \sum_{\substack{c \le m < r \\ sum(l, m) > sum(m + 1, r)}}\frac{dp[l][m]}{r - l} \]

初始是 \(dp[c][c]=1\),答案是 \(dp[1][n]\)。大于等于号取决于编号。

计算的时间复杂度是 \(O(n^3)\) 的,加上枚举 \(c\) 就是 \(O(n^4)\),当然可以用前缀和优化,最多 \(O(n^3)\),不好做。

如果要区间 DP,复杂度至少 \(O(n^2)\),但是我们还要枚举 \(c\),所以我们考虑如何不枚举 \(c\)

我们反过来,定义 \(dp[l][r]\) 表示最终答案属于 \([l, r]\) 的概率,这样每个 \(dp[c][c]\) 就是答案,初始状态是 \(dp[1][n] = 1\)。类似上面我们可以得到转移方程:

\[dp[l][r] = \sum_{\substack{1 \le p < l\\sum(p, l - 1) \le sum(l, r)}}\frac{dp[p][r]}{r - p} + \sum_{\substack{r < p \le n\\sum(l, r) > sum(r + 1, p)}}\frac{dp[l][p]}{p - l} \]

两个部分看着很对称,我们先考虑优化第一部分。

不妨设 \(pL[l][r]\) 表示 \(dp[l][r]\)\(p\) 最小是多少。

我们现在相当于固定 \(r\),求一个 \(pL[l][r] \le p < l\)\(\frac{dp[p][r]}{r-p}\) 之和,考虑到长度从大到小,所以 \(l\) 是递增的,每次新加入的一定在最后,所以我们可以预处理一个前缀和计算。

再考虑到 \(sum(p, l - 1) \le sum(l, r)\) 的限制,可以得到 \(pL[l][r] \le pL[l + 1][r]\),决策具有单调性,于是我们可以根据这个在 \(O(n^2)\) 内算出所有的决策点。于是我们可以在 \(O(n^2)\) 解决这个问题。

点击查看代码
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5005;
const int mod = 1e9 + 7;

int n;
int a[N] = {0}, s[N] = {0};
int sum(int l, int r) {
	return s[r] - s[l - 1];
}

int inv[N] = {0};//线性求逆元
void init() {
	inv[1] = 1;
	for (int i = 2; i <= n; i++)
		inv[i] = (1ll * (mod - (mod / i)) * inv[mod % i]) % mod;
} 

int pL[N][N] = {{0}};
int pR[N][N] = {{0}};

void cal() {
	for (int r = 1; r <= n; r++) {
		int cur = 1;
		for (int l = 1; l <= r; l++) {
			while (cur < l && sum(cur, l - 1) > sum(l, r))
				cur++;
			pL[l][r] = cur;
		}		
	}
	for (int l = 1; l <= n; l++) {
		int cur = n;
		for (int r = n; r >= l; r--) {
			while (cur > r && sum(r + 1, cur) >= sum(l, r))
				cur--;
			pR[l][r] = cur;
		}	
	}
}

int dp[N][N] = {{0}}; 
vector<int> sl[N], sr[N];
//计算dp[l][p]/p - l 
//计算dp[p][r]/r - p 
int main() {
	cin >> n;
	init();
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		s[i] = s[i - 1] + a[i];
	}
	//先算决策点 
	cal();
	//转移 
	dp[1][n] = 1;
	for (int i = 1; i <= n; i++) {
		sl[i].push_back(0);
		sr[i].push_back(0);
	}
	sl[n].push_back(1ll * dp[1][n] * inv[n - 1] % mod);
	sr[1].push_back(1ll * dp[1][n] * inv[n - 1] % mod);
	for (int i = n - 1; i >= 1; i--) {
		for (int l = 1; l + i - 1 <= n; l++) {
			int r = l + i - 1;
			dp[l][r] = 0; 
			int lenL = (int)sl[r].size(), ansL = 0;//前缀和的长度
			int lenVldL = max(l - pL[l][r], 0);
			ansL = (sl[r][lenL - 1] - sl[r][lenL - 1 - lenVldL] + mod) % mod; 
		
			int lenR = (int)sr[l].size(), ansR = 0;
			int lenVldR = max(pR[l][r] - r, 0);
			ansR = (sr[l][lenR - 1] - sr[l][lenR - 1 - lenVldR] + mod) % mod;
			dp[l][r] = (ansL + ansR) % mod;
		} 
		for (int r = i; r <= n; r++) {
			int l = r - i + 1;
			sl[r].push_back(((1ll * dp[l][r] * inv[r - l] % mod) + sl[r].back()) % mod);
			sr[l].push_back(((1ll * dp[l][r] * inv[r - l] % mod) + sr[l].back()) % mod);
		}
	}
	for (int i = 1; i <= n; i++)
		cout << dp[i][i] << endl;
	return 0;
} 

T3

题意: 平面上 \(n\) 个点 \((x_i, y_i)\),其中纵坐标和横坐标都是 \(1 \sim n\) 的排列,求有多少种方案,选出非空集合 \(S,T \sub [n]\)\(S \cap T = \empty\),使得存在一条平行于横轴或纵轴的直线,分开了 \(S\)\(T\) 中的点。\(n \le 10^5\)

考场不够冷静(全想着打麻将了),只推出了最开始的一部分。

我们考虑分别按照 \(x\)\(y\) 枚举所有 \(S\) 都在 \(T\) 左边和 \(S\) 都在 \(T\) 上面,再减去 \(S\)\(T\) 的右上方,最后乘 2 就是答案。

我们先按 \(x\) 排序(\(y\) 同理),则假设 \(S\) 最右边的是 \(i\),贡献就是 \(2^{i-1}(2^{n-i}-1)\),因为 \(T\) 非空。

我们在考虑怎么求 \(S\)\(T\) 的右上的。

这时我们关心两个事儿:\(x\) 分界线和 \(y\) 分界线。我们考虑按 \(x\) 排序,再枚举 \(y\)

我们记 \(F_i(y)\) 表示 \(S\)\(S\) 最右的是 \(x_i\),并且所有点的 \(y_j\) 最小值等于 \(y\)

我们设 \(a_i(y)\) 表示所有满足 \(j < i\)\(y_j \le y\)\(j\) 的个数。

\(b_i(y)\) 表示所有满足 \(j > i\)\(y_j < y\) 的个数。

分三种情况:

\(y > y_i\),显然最小值会比 \(y\) 小,所以 \(F_i(y)=0\)

\(y = y_i\)\(F_i(y)=2^{a_i(y)}(2^{b_i(y)}-1)\)

\(y < y_i\)\(F_i(y)=(2^{a_i(y)}-2^{a_i(y+1)})(2^{b_i(y)}-1)\),这是为了保证最小值恰好为 \(y\)

答案就是 \(\sum_i\sum_yF_i(y)\)

现在我们考虑用数据结构优化求和。

拆贡献,以上三种情况取决于三个函数:\(2^{a_i(y)},2^{a_i(y)+{b_i(y)}},2^{a_i(y + 1)+{b_i(y)}}\)。最后 \(F_i(y)\) 就是它们的线性组合。所以我们考虑维护这三个函数的和。

显然这和 \(a_i(y)\)\(b_i(y)\) 的增减有关,可以转化为对这三个函数的乘除。所以我们考虑 \(a_i(y)\)\(b_i(y)\) 的变化。

\(i\) 变成 \(i+1\),我们发现所有 \(y \le y_i\)\(y\) 都使 \(a_i(y)\) 增加 \(1\)。而所有 \(y > y_{i+1}\)\(b_i(y)\) 都减少 \(1\)

所以我们用线段树维护区间乘法和区间和即可解决,时间复杂度 \(O(n \log n)\)

还有一个细节,我们不只要算 \(S\)\(T\) 右上的,还要算右下的,这是应为两种可以交换得到,所以我们要将 \(y\) 值取反再算一遍。

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 5;
const int mod = 1e9 + 7;
const int inv2 = (mod + 1) / 2;

struct SegTree {
	int sz;
	int val[N * 4];
	int tag[N * 4];
	#define ls (x << 1)
	#define rs (x << 1 | 1)
	#define mid ((lx + rx) >> 1)
	void pushup(int x) {
		val[x] = (val[ls] + val[rs]) % mod;
	}
	void pushdown(int x) {
		if (tag[x] == 1)
			return;
		val[ls] = (1ll * val[ls] * tag[x]) % mod;
		val[rs] = (1ll * val[rs] * tag[x]) % mod;
		tag[ls] = (1ll * tag[ls] * tag[x]) % mod;
		tag[rs] = (1ll * tag[rs] * tag[x]) % mod;
		tag[x] = 1; 
	} 
	void build(int x, int lx, int rx, vector<int> &a) {
		tag[x] = 1;
		if (lx + 1 == rx) {
			val[x] = a[lx - 1];
			return;
		}
		build(ls, lx, mid, a), build(rs, mid, rx, a);
		pushup(x);
	}
	void upd(int x, int lx, int rx, int l, int r, int v) {
		if (rx <= l || r <= lx)
			return;
		if (l <= lx && rx <= r) {
			val[x] = (1ll * val[x] * v) % mod;
			tag[x] = (1ll * tag[x] * v) % mod; 
			return;
		}
		pushdown(x);
		upd(ls, lx, mid, l, r, v), upd(rs, mid, rx, l, r, v);
		pushup(x); 
	}
	int qry(int x, int lx, int rx, int l, int r) {
		if (rx <= l || r <= lx)
			return 0;
		if (l <= lx && rx <= r)
			return val[x];
		pushdown(x);
		return (qry(ls, lx, mid, l, r) + qry(rs, mid, rx, l, r)) % mod;
	}
	SegTree () {}
	SegTree (vector<int> &a) {
		sz = (int)a.size();
		build(1, 1, sz + 1, a);
	}
	#undef ls
	#undef rs
	#undef mid
} st1, st2, st3;
//st1 储存 2^a(y)
//st2 储存 2^(a(y)+b(y))
//st3 储存 2^(a(y+1)+b(y))

int n;
struct Pnt {
	int x, y;
	Pnt (int _x = 0, int _y = 0) :
		x(_x), y(_y) {}
} p[N]; 
bool cmp(Pnt a, Pnt b) {
	return a.x < b.x;
}
int pw[N] = {0};
vector<int> a;

int slv() {
	//对于 2^a(y),最开始全是1
	a = vector<int>(n, 0);
	for (int i = 0; i < n; i++) 
		a[i] = 1;
	st1 = SegTree(a);
	//对于 2^a(y)+b(y),最开始就是 2^b(y)
	//而 b(y) 为所有小于 y 的个数
	for (int i = 0; i < n; i++)
		a[i] = 0; 
	for (int i = 2; i <= n; i++)
		a[p[i].y - 1]++;
	for (int i = 1; i < n; i++)
		a[i] += a[i - 1];
	for (int i = n - 1; i >= 1; i--)
		a[i] = pw[a[i - 1]];
	a[0] = 1;
	st2 = SegTree(a);
	//对于 2^a(y+1)+b(y),依然是 2^b(y)
	st3 = SegTree(a);
	int res = 0;
	for (int i = 1; i <= n; i++) {
		//对于 y = p[i].y 的贡献是 2^(a(y)+b(y)) - 2^a(y) 
		res = (res + (st2.qry(1, 1, n + 1, p[i].y, p[i].y + 1) - st1.qry(1, 1, n + 1, p[i].y, p[i].y + 1) + mod) % mod) % mod;
		//对于 y < p[i].y 的贡献为 2^(a(y)+b(y)) - 2^a(y) - 2^(a(y+1)+b(y))+2^(a(y + 1))
		//考虑 a(y + 1) 就是不包括 a(2) 到 a(p[i].y) 的所有之和
		res = (res + st2.qry(1, 1, n + 1, 1, p[i].y)) % mod;
		res = (res + st1.qry(1, 1, n + 1, 2, p[i].y + 1)) % mod; 
		res = (res - st1.qry(1, 1, n + 1, 1, p[i].y) + mod) % mod;
		res = (res - st3.qry(1, 1, n + 1, 1, p[i].y) + mod) % mod;
		if (i == n)
			break;
		//对于 2^a(y) 和 2^a(y)+b(y) 来说,所有 y <= p[i].y 的 y 都要乘2
		st1.upd(1, 1, n + 1, 1, p[i].y + 1, 2);
		st2.upd(1, 1, n + 1, 1, p[i].y + 1, 2);
		//对于 2^a(y)+b(y) 和 2^a(y+1)+b(y),所有 y > p[i + 1].y 的 y 都要除2
		st2.upd(1, 1, n + 1, p[i + 1].y + 1, n + 1, inv2);
		st3.upd(1, 1, n + 1, p[i + 1].y + 1, n + 1, inv2);
		//对与 2^a(y+1)+b(y) 来说,所有 [1, p[i].y - 1] 要乘 2
		st3.upd(1, 1, n + 1, 1, p[i].y, 2); 
	} 
	return res;
}

int main() {
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> p[i].x >> p[i].y;
	//先算分别的
	int ans = 0;
	pw[0] = 1;
	for (int i = 1; i <= n; i++)
		pw[i] = (1ll * pw[i - 1] * 2) % mod;
	for (int i = 1; i <= n; i++)
		ans = (ans + (1ll * pw[i - 1] * (pw[n - i] - 1 + mod)) % mod) % mod;
	ans = (ans * 2ll) % mod;
	//再算重叠的
	sort(p + 1, p + n + 1, cmp);
	int res = slv();
	for (int i = 1; i <= n; i++)
		p[i].y = n - p[i].y + 1;
	res = (res + slv()) % mod;
	cout << (ans - res + mod) % mod * 2ll % mod << endl;
	return 0;
} 
posted @ 2024-02-06 18:01  rlc202204  阅读(23)  评论(0编辑  收藏  举报