NOIP 复习题之动态规划

AT_joi2022ho_c 選挙で勝とう

首先要先把协作者买出来,再对于之后的州把买的协作者全部用上。则我们可以先枚举需要的协作者数量 \(x\),可以知道的是:我们枚举选择哪些 \(x\) 个协作者,再在剩下的州中选择 \(A_i\) 最小的 \(K-x\) 个州即可。则考虑 dp。我们对 \(B_i\) 进行排序后,协作者就只在前 \(K\) 个进行买卖。

我们可以发现:当两个协作州之间有反对州时,将反对州与后面一个协作州位置交换一定会更优。则定义 \(f_{i,j}\) 表示前 \(i\) 个州,选 \(j\) 个协作州的最小代价,且前 \(i\) 个州要么协作要么投票。

dp 复杂度 \(O(n^2)\),总复杂度 \(O(n^3)\)

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)

const int N = 505;
int n, k, sum[N][N], cnt;
double dp[N][N], res = 2e9;

struct edge {
	int a, b;
}ed[N];

signed main() {
	cin >> n >> k;
	_for(i, 1, n) cin >> ed[i].a >> ed[i].b;
	_for(i, 1, n) {
		cnt += (ed[i].b != -1);
		if (ed[i].b == -1) ed[i].b = 2e9;
	}
	sort(ed + 1, ed + n + 1, [](edge x, edge y) {
		return x.b < y.b;
	});
	memset(sum, 0x3f, sizeof sum);
	_for(i, 1, n + 1) sum[i][0] = 0;
	_pfor(i, n, 1) {
		_for(j, 1, n - i + 1) {
			sum[i][j] = min(sum[i + 1][j], sum[i + 1][j - 1] + ed[i].a);
		}
	}
	cnt = min(cnt, k);
	_for(cas, 0, cnt) {
		memset(dp, 0x7f, sizeof dp);
		dp[0][0] = 0;
		_for(i, 1, k) {
			_for(j, 0, cas) {
				dp[i][j] = min(dp[i][j], dp[i - 1][j] + 1.0 * ed[i].a / (cas + 1));
				if (j) dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + 1.0 * ed[i].b / j);
			}
		}
		_for(i, cas, k) res = min(res, dp[i][cas] + 1.0 * sum[i + 1][k - i] / (cas + 1));
	}
	printf("%.15lf", res);
}

AT_joisc2015_c たのしいたのしい家庭菜園

考虑最后剩下的最高的树,往左边走单调递减,往右边走单调递减。也就是最后剩下了一个上升子序列和下降子序列。

考虑 dp。定义 \(f_i\) 表示前 \(i\) 颗树,第 \(i\) 颗树保留的最大收益。

\(f_i=f_j+p_i-\sum_{k=j+1}^{i-1} [h_k>h_j] \times c_k\),且 \(h_j<h_i\)

仿照 LIS 的线段树优化思路,难以操作的是后面的一堆 sum。可以看出,每个 \(h_k\) 都对之后的 \(h_j<h_k\)\(-c_k\) 的贡献,进而对于 \([1,h_k-1]\) 减去 \(c_k\)

#include <bits/stdc++.h>
using namespace std;

#define ls p << 1
#define rs p << 1 | 1
#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
#define int long long
const int N = 3e5 + 5, INF = -2e18;

int n, b[N], tot, ans = -2e18, f[N], g[N];

struct edge {
	int H, P, C;
}ed[N]; 

struct ss {
	struct stu {
		int l, r, maxn, lazy;
	}tree[N * 4];
	
	void push_up(int p) {
		tree[p].maxn = max(tree[ls].maxn, tree[rs].maxn);
	}
	
	void push_down(int p) {
		if (tree[p].lazy) {
			tree[ls].maxn += tree[p].lazy;
			tree[rs].maxn += tree[p].lazy;
			tree[ls].lazy += tree[p].lazy;
			tree[rs].lazy += tree[p].lazy;
			tree[p].lazy = 0;
		}
	}	
	
	void build(int p, int l, int r) {
		tree[p].lazy = 0; tree[p].maxn = INF; tree[p].l = l, tree[p].r = r;
		if (l == r) return;
		int mid = (l + r) >> 1;
		build(ls, l, mid);
		build(rs, mid + 1, r);
	}
	
	void modify(int p, int l, int r, int val) {
		if (l <= tree[p].l && tree[p].r <= r) {
			tree[p].maxn += val;
			tree[p].lazy += val;
			return;
		}
		push_down(p);
		int mid = (tree[p].l + tree[p].r) >> 1;
		if (l <= mid) modify(ls, l, r, val);
		if (r > mid) modify(rs, l, r, val);
		push_up(p);
	}
	
	void modify_max(int p, int x, int val) {
		if (tree[p].l == tree[p].r) {
			tree[p].maxn = max(tree[p].maxn, val);
			return;
		}
		push_down(p);
		int mid = (tree[p].l + tree[p].r) >> 1;
		if (x <= mid) modify_max(ls, x, val);
		else modify_max(rs, x, val);
		push_up(p);
	}
	
	int query(int p, int l, int r) {
		if (l <= tree[p].l && tree[p].r <= r) return tree[p].maxn;
		int mid = (tree[p].l + tree[p].r) >> 1, res = INF;
		push_down(p);
		if (l <= mid) res = max(res, query(ls, l, r));
		if (r > mid) res = max(res, query(rs, l, r));
		return res;
	}
}tr;

signed main() {
	cin >> n;
	_for(i, 1, n) cin >> ed[i].H >> ed[i].P >> ed[i].C, b[++tot] = ed[i].H;
	sort(b + 1, b + tot + 1);
	tot = unique(b + 1, b + tot + 1) - b - 1;
	_for(i, 1, n) ed[i].H = lower_bound(b + 1, b + tot + 1, ed[i].H) - b;
	tr.build(1, 0, tot);
	tr.modify_max(1, 0, 0);
	_for(i, 1, n) {
		f[i] = tr.query(1, 0, ed[i].H) + ed[i].P;
		tr.modify_max(1, ed[i].H, f[i]);
		tr.modify(1, 0, ed[i].H - 1, -ed[i].C);
	}
	tr.build(1, 0, tot);
	tr.modify_max(1, 0, 0);
	_pfor(i, n, 1) {
		g[i] = tr.query(1, 0, ed[i].H) + ed[i].P;
		tr.modify_max(1, ed[i].H, g[i]);
		tr.modify(1, 0, ed[i].H - 1, -ed[i].C);
	}
	_for(i, 1, n) ans = max(ans, f[i] + g[i] - ed[i].P);
	cout << ans << endl;
}

CF1515E Phoenix and Computers

考虑最后的电脑操作形态:\(1\sim X_1\) 手动开启,\(X_1+1\) 自动开启,\(X_1+1\sim X_2\) 手动开启,\(X_2+1\) 自动开启,\(X_{n-1}+1\sim X_n\) 手动开启。

先考虑一个子问题:有 \(n\) 台电脑,都需要手动开启,那么方案数是多少?第一次开第 \(x\) 台电脑,那么 \(x+1,x+2,\dots, n\) 只能相对顺序开启,\(x-1,x-2,\dots,1\) 只能相对顺序开启。那么其余 \(n-1\) 台电脑按相对顺序混杂在一起,等价于其余 \(n-1\) 台电脑,选 \(x-1\) 台电脑的方案数。则总方案数 \(C_{n-1}^{0}+C_{n-1,1}+\dots+C_{n-1}^{n-1}=2^{n-1}\)

则定义 \(f_{i,j}\) 表示前 \(i\) 台电脑,有 \(j\) 台手动开启的方案数。

转移有:\(f_{i+k+1,j+k}=f_{i,j}\times 2^{k-1}\times C_{j+k}^{k}\)。其中 \(2^{k-1}\) 子问题中解释过,\(C_{j+k}^{k}\) 就是新的和旧的混在一起。

#include <bits/stdc++.h>
using namespace std;

#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
#define int long long

const int N = 405;
int n, mod, b2[N], C[N][N], f[N][N], res;

void init() {
	b2[0] = C[0][0] = 1;
	_for(i, 1, N - 5) b2[i] = b2[i - 1] * 2 % mod;
	_for(i, 1, N - 5) {
		_for(j, 0, N - 5) {
			C[i][j] = C[i - 1][j];
			if (j) C[i][j] = (C[i][j] + C[i - 1][j - 1]) % mod;
		}
	}
}

signed main() {
	cin >> n >> mod;
	init(); 
	_for(i, 1, n) {
		f[i][i] = b2[i - 1];
		_for(j, 0, i) {
			_for(k, 1, n - i - 1) {
				f[i + k + 1][j + k] = (f[i + k + 1][j + k] + f[i][j] * b2[k - 1] % mod * C[j + k][j] % mod) % mod;
			}
		}
	}
	_for(i, 0, n) res += f[n][i], res %= mod;
	cout << res << endl;
}

P10375 [AHOI2024 初中组] 计数

考虑一个可删除区间包含另一个小的可删除区间,那么只保留最大的。而且一个区间如果可删除,一定满足 a....ab....b....z.....z 的形式。

考虑一个数加入但是无效的情况:a.....ab,此时如果加入 \(c\),想要可删除,则必须加入 \(a\)\(b\),则意味着 \(c\) 被覆盖了。此时情况可以概括为:原序列不能被删除,然后加入了一个不能使原序列被删除的字符。

则可以定义 dp 为 \(f_{i,j,k}\) 表示前 \(i\) 个字符,可以加入 \(j\) 个字符使得序列可以完全删除,\(k=0/1\) 表示当前是否能够删除。

则转移有:

  • \(f_{i+1,j,1}=f_{i,j,1}\times j\)
  • \(f_{i+1,j+1,0}=f_{i,j,1}\times (m-j)\)
  • \(f_{i+1,j,1}=f_{i,j,0}\times j\)
  • \(f_{i+1,j,0}=f_{i,j,0}\times (m-j)\)。此情况就是加入但无效果。
#include <bits/stdc++.h>
using namespace std;

#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
#define int long long

const int N = 3005, mod = 1e9 + 7;
int n, m, f[N][N][2], res;

signed main() {
	cin >> n >> m;
	f[0][0][1] = 1;
	_for(i, 0, n - 1) {
		_for(j, 0, i) {
			f[i + 1][j][1] = (f[i + 1][j][1] + f[i][j][1] * j % mod) % mod;
			f[i + 1][j + 1][0] = (f[i + 1][j + 1][0] + f[i][j][1] * (m - j) % mod) % mod;
			f[i + 1][j][1] = (f[i + 1][j][1] + f[i][j][0] * j % mod) % mod;
			f[i + 1][j][0] = (f[i + 1][j][0] + f[i][j][0] * (m - j) % mod) % mod;
		}
	}
	_for(i, 0, n) res = (res + f[n][i][1]) % mod;
	cout << res << endl; 
}
posted @ 2024-09-14 15:51  Otue  阅读(16)  评论(0)    收藏  举报