USACO23FEB P【杂题】

A. [USACO23FEB] Hungry Cow P

Bessie 很饿,每天晚饭如果有干草就会吃 \(1\) 份,没有就不吃,初始没有干草。每天早上 Farmer John 会给它送若干干草,设第 \(k\) 天送 \(a_k\) 份干草,初始时 \(a_k=0\),表示该天不送干草。

\(q\) 次操作,每次给出 \(x,y\),表示将 \(a_x\) 改成 \(y\),求此时 Bessie 有干草吃的日期编号和。答案对 \(10^9+7\) 取模。操作间互不独立。

\(q\le10^5\)\(x\le10^{14}\)\(y\le10^9\)


一开始把题面看成覆盖了,所以自然会跑去想 set,但是发现这个东西并不太能支持撤销操作,所以修改同一个位置的时候就寄了。

我们不妨还是先考虑只插入不同位置的情况。此时有一个线段树做法,直接维护权值和以及黑色格子的个数。那么一个修改 \((i,j)\) 相当于区间覆盖 \([i,k]\),其中 \([i,k]\) 中修改前白色格子的数量为 \(j\),线段树上二分即可。

这个东西其实也不太好撤销。但是我们发现其实真的需要撤销吗,考虑相邻两个下标相同的修改,设操作的时间分别是 \([t_1,t_2)\),那么可以看做第一个操作影响的时间段是 \([t_1,t_2)\),也就是说每次操作的影响范围都是一段区间,于是线段树分治就行了。

实现的时候由于不好撤销,所以需要可持久化动态开点线段树,每次回溯的时候把新增的信息清空。时间复杂度 \(\mathcal{O}(n \log n \log V)\)

code
#include <bits/stdc++.h>
#define fi first
#define se second
#define int long long
using namespace std;
typedef long long LL;
typedef pair <LL, int> pi;
const int N = 1e5 + 5, mod = 1e9 + 7, inv2 = (mod + 1) / 2;
const LL m = 1e15;
int n, tot, rt[N << 2]; LL ans[N];
map <LL, int> lst;
pi q[N];
vector <int> v[N << 2];
struct dat {
	LL val, cnt; bool tag; int lc, rc;
} tr[N << 8];
#define ls tr[x].lc
#define rs tr[x].rc
#define mid ((l + r) >> 1)
void ptag(int x, LL l, LL r) {
	tr[x].tag = 1, tr[x].cnt = r - l + 1, tr[x].val = (1LL * (l + r) % mod * ((r + mod - l + 1) % mod) % mod * inv2 % mod); 
}
void up(int x, LL l, LL r) {
	if (tr[x].tag) return;
	tr[x].cnt = tr[ls].cnt + tr[rs].cnt, tr[x].val = (tr[ls].val + tr[rs].val) % mod;
}
void cov(int &x, int rt, LL l, LL r, LL ql, LL qr) {
	x = ++tot, tr[x] = tr[rt];
	if (ql <= l && qr >= r) return ptag(x, l, r);
	if (ql <= mid) cov(ls, tr[rt].lc, l, mid, ql, qr);
	if (qr > mid) cov(rs, tr[rt].rc, mid + 1, r, ql, qr);
	up(x, l, r); 
}
LL sum(int x, LL l, LL r, LL ql, LL qr, bool o) {
	if (!x) return o ? (min(qr, r) - max(ql, l) + 1) : 0;
	o |= tr[x].tag;
	if (ql <= l && qr >= r) return o ? (r - l + 1) : tr[x].cnt;
	LL res = 0;
	if (ql <= mid) res += sum(ls, l, mid, ql, qr, o);
	if (qr > mid) res += sum(rs, mid + 1, r, ql, qr, o);
	return res;
}
LL qry(int x, LL l, LL r, LL k, bool o) {
	if (!x) return l + k - 1;
	if (l == r) return l;
	o |= tr[x].tag;
	LL sl = o ? 0 : (mid - l + 1 - tr[ls].cnt);
	if (sl < k) return qry(rs, mid + 1, r, k - sl, o);
	else return qry(ls, l, mid, k, o); 
}
void ins(int x, int l, int r, int ql, int qr, int id) {
	if (ql <= l && qr >= r) return v[x].push_back(id), void();
	if (ql <= mid) ins(x << 1, l, mid, ql, qr, id);
	if (qr > mid) ins(x << 1 | 1, mid + 1, r, ql, qr, id);
}
void solve(int x, int l, int r) {
	int tp = tot;
	rt[x] = rt[x >> 1];
	for (int i : v[x]) {
		LL pos = qry(rt[x], 1, m, q[i].se + (q[i].fi - 1 - sum(rt[x], 1, m, 1, q[i].fi - 1, 0)), 0);
		cov(rt[x], rt[x], 1, m, q[i].fi, pos);
	}
	if (l == r) ans[l] = tr[rt[x]].val;
	else solve(x << 1, l, mid), solve(x << 1 | 1, mid + 1, r);
	for (int i = tp + 1; i <= tot; i++) tr[i] = (dat){ 0, 0, 0, 0, 0 };
	tot = tp;
}
signed main() {  
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for (int i = 1; i <= n; i++) {
		LL x, y; cin >> x >> y;
		q[i] = make_pair(x, y);
		if (lst[x]) ins(1, 1, n, lst[x], i - 1, lst[x]);
		lst[x] = i;
	}
	for (pi p : lst) ins(1, 1, n, p.se, n, p.se);
	solve(1, 1, n);
	for (int i = 1; i <= n; i++) cout << ans[i] << "\n"; 
    return 0; 
}

B. [USACO23FEB] Problem Setting P

Farmer John 出了 \(n\) 道题,聘了 \(m\) 个验题人来品鉴难度。难度只有简单和困难两种。

Farmer John 将从中选出若干道(至少一道),并以一定顺序排列,使得前一道题目中任意一个觉得此题困难的验题人也觉得后面一道题目困难。求有多少种选出来并排列的方案,答案对 \(10^9+7\) 取模。

\(n\le10^5\)\(m\le20\)


容易做出以下翻译:有一个 \(m\) 个元素的集合,给定其 \(n\) 个子集,选若干个子集并排序,使得相邻两项满足前一项是后一项的子集。

显然对于状态相同的可以放在一起考虑,容易计算出选出其中至少一个元素并确定顺序的方案数。关键在于如何按照顺序进行 DP 转移。

由于每个位置贡献独立,因此考虑对最高位 \(01\) 分治。记 \(S\) 结束的答案为 \(f_S\)\(S\) 所有子集的 \(f\) 之和为 \(g_S\)。假设已经计算完了左半部分的 \(f,g\),考虑如何贡献到右半部分:对于第 \(d\) 层分治,实际上前 \(d-1\) 位是确定的,因此对于右半部分,我们需要保证贡献到的位置前 \(d-1\) 位相同。其实这是把 \(f_S\) 拆成了 \(\log\) 个部分,分别表示前 \(i\) 位和 \(S\) 相同的答案。

讲得好抽象啊,可以参考代码理解一下。实现的时候可能要把这 \(\log\) 个部分分别记录,时间复杂度 \(\mathcal{O}(m2^m)\)

code
#include <bits/stdc++.h>
using namespace std;
const int N = 20, M = 1 << 20, mod = 1e9 + 7;
int n, m, ans, a[M], b[M], f[M], g[N][M];
void solve(int l, int r, int d) {
	if (l == r) {
		int coef = 0, val = 1;
		for (int j = 1; j <= b[l]; j++) val = 1LL * val * (b[l] - j + 1) % mod, coef = (coef + val) % mod;
		for (int j = 0; j <= d; j++) f[l] = (f[l] + g[j][l]) % mod;
		val = 1LL * (f[l] + 1) * coef % mod;
		ans = (ans + val) % mod;
		g[d - 1][l] = (g[d - 1][l] + val) % mod;
		return;
	}
	int mid = (l + r) >> 1;
	solve(l, mid, d + 1);
	for (int i = 0; l + i <= mid; i++) g[d][mid + 1 + i] = (g[d][mid + 1 + i] + g[d][l + i]) % mod;
	solve(mid + 1, r, d + 1);
	if (d) for (int i = l; i <= r; i++) g[d - 1][i] = (g[d - 1][i] + g[d][i]) % mod;
}
signed main() {  
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
		string s; cin >> s;
		for (int j = 1; j <= n; j++) if (s[j - 1] == 'H') a[j] |= 1 << i;
	}
	for (int i = 1; i <= n; i++) b[a[i]]++;
	solve(0, (1 << m) - 1, 0);
	cout << ans << "\n";
    return 0; 
}

C. [USACO23FEB] Watching Cowflix P

给定一棵 \(n\) 个节点的树,有些节点是关键的。你需要选取若干联通块 \(c_1,c_2,\dots,c_t\),支付 \(\sum\limits_{i=1}^t(|c_i|+k)\) 的代价,使得所有关键点都被某个联通块包含。不被指定的节点不必被包含。对于 \(1 \le k \le n\) 求最小代价。

\(n\le2\times10^5\)


其实我最先注意到的是如果两个关键点距离 \(\leq k\),那么他们可以直接被合并。这样合并完之后最多只有 \(\mathcal{O}(\frac{n}{k})\) 个关键点。然后发现好像还没最优呢,还可能在关键点的虚树上的某个非关键点处发生合并,那我们就求出虚树,然后树形 DP 一下,就做完了。因为只有 \(\mathcal{O}(\frac{n}{k})\) 个关键点,所以虚树上最多也只有 \(\mathcal{O}(\frac{n}{k})\) 个点,根据调和级数总时间复杂度 \(\mathcal{O}(n \log n)\)

这样说起来是很简单,但写起来还是有不少细节。代码中的实现方法是这样的:我们先求出虚树,然后预处理出每个点及每条边被合并的时间,以及每个点被某个联通块完全包裹在内部的时间。这样我们可以求出每个时刻 DP 前的连通块会新增多少个点,多少条边。用 set 维护当前剩下的点的 DFS 序以减小 DP 的常数,当一个点被某个连通块完全包裹在内部的时候就可以把它删掉了。具体的细节参见代码。

code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5, inf = 2e9;
int n, par[N], val[N], dep[N]; bool key[N], era[N];
set <int> e[N];
void remove(int u) {
	for (auto v : e[u]) {
		par[v] = u, val[v] = 1, dep[v] = dep[u] + 1;
		e[v].erase(e[v].find(u)), remove(v);
	}
	if (!key[u]) {
		bool ok = 1;
		for (auto v : e[u]) ok &= era[v];
		if (ok) era[u] = 1;
	}
}
int dis[N], _dis[N], tim[N], _tim[N], c[N], d[N], f[N][2], ans[N];
vector <int> buk[N];
struct dat {
	int i;
	bool operator < (const dat &p) const {
		return dep[i] != dep[p.i] ? dep[i] > dep[p.i] : i > p.i;
	}
};
set <dat> cur; 
void dfs(int u) {
	dis[u] = _dis[u] = (key[u] ? 0 : inf);
	for (int v : e[u]) {
		dfs(v);
		int L = dis[v] + val[v];
		if (L < dis[u]) _dis[u] = dis[u], dis[u] = L;
		else if (L < _dis[u]) _dis[u] = L;
	}
}
void calc(int u, int L) {
	_tim[u] = dis[u] + L;
	for (int v : e[u]) {
		int _L = (dis[v] + val[v] == dis[u]) ? _dis[u] : dis[u];
		calc(v, min(_L, L) + val[v]);
	}
}
signed main() {  
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    string s;
	cin >> n >> s;
	int root = -1;
	for (int i = 1; i <= n; i++) if (s[i - 1] == '1') key[i] = 1, root = i;
	for (int i = 1, x, y; i < n; i++) {
		cin >> x >> y;
		e[x].insert(y), e[y].insert(x);
	}
	remove(root);
	for (int i = 1; i <= n; i++) if (era[i]) e[par[i]].erase(e[par[i]].find(i))
	for (int i = 1; i <= n; i++) {
		if (!era[i] && !key[i] && e[i].size() == 1) { int u = i, v = -1; 
			for (auto z : e[u]) v = z;
			assert(v > 0);
			par[v] = par[u], val[v] += val[u], e[par[u]].erase(e[par[u]].find(u)), e[par[u]].insert(v);
			era[u] = 1;
		}
	}
	dfs(root), calc(root, 0);
	for (int i = 1; i <= n; i++) if (!era[i]) {
		cur.insert((dat){ i });
		tim[i] = key[i] ? 0 : _tim[i];
		int t = _tim[i];
		for (auto v : e[i]) tim[i] = min(tim[i], _tim[v]), t = max(t, _tim[v]);
		buk[t].push_back(i);
		c[tim[i]]++;
		if (dep[i] != 0) c[_tim[i]] += val[i] - 1, d[_tim[i]] += val[i];
	} 
	int C = c[0], D = 0;
	for (int k = 1; k <= n; k++) {
		for (int i : buk[k]) {
			auto it = cur.find((dat){ i });
			assert(it != cur.end());
			cur.erase(it);
		}
		C += c[k], D += d[k];
		ans[k] = k * (C - D) + C;
		for (auto z : cur) {
			int v = z.i;
			if (tim[v] <= k) f[v][0] = inf;
			else f[v][1] += k + 1;
			if (_tim[v] <= k) ans[k] += f[v][1];
			else {
				int u = par[v];
				f[u][0] += min(f[v][0], f[v][1]);
				f[u][1] += min(f[v][0], min(f[v][1], f[v][1] - k + (val[v] - 1)));
			}
			f[v][0] = f[v][1] = 0;
		}
	}
	for (int i = 1; i <= n; i++) cout << ans[i] << "\n";
    return 0; 
}
posted @ 2023-03-18 21:32  came11ia  阅读(86)  评论(0编辑  收藏  举报