202411 题目合集(2)

P10217

  • 无论是最优化问题还是计数问题,如果没有想法->考虑判定。
  • 答案能否为 \(m\)
    • 这个 \((x, y)\) 很容易让人想到向量。同时 \(i\bmod n\) 是一个周期一样的东西。记给定的 \(n\) 个分别是向量 \(\boldsymbol{v_0, v_2,\dots, v_{n - 1}}\)\(\boldsymbol{z_j} = \sum\limits_{i = 0}^{j}\boldsymbol{v_i}\)。那么对于 \(x\) 仅仅考虑这些的位移就是 \(\lfloor\dfrac{m - 1}{n}\rfloor z_{n -1} + z_{(m - 1) \bmod n}\)。要求能够通过加上一个 \(\boldsymbol{w}\) 使得 \(\lfloor\dfrac{m - 1}{n}\rfloor \boldsymbol{z_{n - 1}} + \boldsymbol{z_{(m - 1)\bmod n}} + \boldsymbol{w} = \boldsymbol{a}\)。其中 \(\boldsymbol{a}\) 为给定的向量。所以 \(\boldsymbol{w}\) 是可以根据 \(m\) 唯一确定的。考虑这个 \(\boldsymbol{w}\) 能否通过 \(m\) 次,每次横纵上的位移绝对值之和不超过 \(k\)。也就是说对于 \(\boldsymbol{w} = (x, y)\) 只需要 \(|x| + |y| \le k\times m\) 即可。我们得到了 \(O(1)\) 的判定方法。
  • 考虑优化。上面的式子根据 \((m - 1)\bmod n\) 的剩余系分类。设 \(r = (m - 1) \bmod n, t = \lfloor \dfrac{m - 1}{n}\rfloor\)。问题变成对于每一个 \(r\) 求出最小的 \(t\) 使得 \(\boldsymbol{a} - \boldsymbol{z_r} - t\boldsymbol{z_{n - 1}} = \boldsymbol{w}\)\(|x_w| + |y_w| \le km\)。以下的 \(x, y\) 均是合成的向量的坐标,不是读入的值。\(|x_w| = |x_a - x_r - tx_{n - 1}|, |y_w| = |y_a - y_r - ty_{n - 1}|, |x_w| + |y_w| = |x_a - x_r - tx_{n - 1}| + |y_a - y_r - ty_{n - 1}| \le km = k(nt+r)\)
  • 真是讨厌的绝对值!但是由于这里绝对值就是这个式子可能的最大的,所以可以直接暴力去掉绝对值,列出关于 \(t\) 的四个不等式,求出 \(t\) check(方法见上)即可。
#include <bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
const int N = 1e6;
struct line {
	ll x, y;
} Q[N + 10];
il ll Abs(ll x) {
	return ((x > 0) ? (x) : (-x));
}

int n;
ll k, edx, edy;
il bool check(ll p) {
	if(!p) {
		if(!edx && !edy) return 1;
		else return 0;
	}
	if(p < 0) return 0;
	ll wz = (p - 1) / n, wp = (p - 1) % n;
	line tmp = (line){Q[n - 1].x * wz + Q[wp].x, Q[n - 1].y * wz + Q[wp].y};
//	cout << tmp.x << ' ' << tmp.y << endl;
	ll dist = Abs(tmp.x - edx) + Abs(tmp.y - edy);
	return (dist <= p * k);
}
il ll ceildiv(ll n, ll m) {
	if(n * m < 0) {
		n = Abs(n), m = Abs(m);
		return -(n / m);
	}
	else {
		n = Abs(n), m = Abs(m);
		return n / m + ((n % m == 0) ? (0) : (1));
	} 
}
il ll floordiv(ll n, ll m) {
	if(n * m < 0) {
		n = Abs(n), m = Abs(m);
		return -ceildiv(n, m); 
	}
	else {
		n = Abs(n), m = Abs(m);
		return n / m; 
	}
}

vector <ll> vec;
const int D[2] = {1, -1};
int cnt = 0;
il ll calc(int p) {
	ll xz = Q[n - 1].x, yz = Q[n - 1].y;
	ll xr = edx - Q[p].x, yr = edy - Q[p].y;
	vec.clear();
	for(int a = 0; a < 2; a++) {
		for(int b = 0; b < 2; b++) {
			ll faca = D[a] * xz + D[b] * yz - n * k;
			ll facb = k * (p + 1) + D[a] * xr + D[b] * yr;
			if(!faca) continue;
			vec.push_back(Abs(ceildiv(facb, faca)) * n + p + 1);
		}
	}
	sort(vec.begin(), vec.end());
	for(int i = 0; i < vec.size(); i++)
		if(check(vec[i])) return vec[i];
	return -1; 
}
void init() {
	cin >> n >> k >> edx >> edy;
	cin >> Q[0].x >> Q[0].y;
	for(int i = 1; i < n; i++) {
		ll xx, yy; cin >> xx >> yy;
		Q[i] = (line){Q[i - 1].x + xx, Q[i - 1].y + yy};
	}
	
	if(!edx && !edy) {
		cout << 0 << endl;
		return ;
	}
	ll minn = -1;
////	cout << calc(0) << endl;
//	cout << check(399999999) << endl;
//	cout << check(399999998) << endl; 
	for(int i = 0; i < n; i++) {
//		cout << i << endl;
		ll rest = calc(i);
		if(rest == -1) continue;
		else {
			if(minn == -1) minn = rest;
			else minn = min(minn, rest);
		}
	}
	cout << minn << '\n';
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int T; cin >> T;
	while(T--) init();
}

总结

  • 可以利用向量来刻画 \(x, y\) 分别相加!
  • 对于绝对值和的小于等于,可以把它暴力拆成 \(2^k\) 个不带绝对值的式子。

P1502

发现不会扫描线典题耶( •̀ ω •́ ),糖丸了

  • 直接考虑似乎不太行,于是考虑每一颗星星对答案的贡献。
  • 先特别考虑一下这个“在边框上不算在内”的条件。把最下面一行的星星放到框上,那么要求最上面一行的 \(y \le y_{low} +h - 1\)。横向的
  • 我们可以利用右上角唯一确定一个矩形,同时也可以据此将每颗星星可能出现在的矩形的范围划分出来:对于星星 \(x, y\),能够圈住它的右上角在 \((x, y)\)\((x + w - 1, y + h - 1)\) 构成的矩形内。
  • 我们拿这个右上角代表整个矩形,那么问题变成了:求出一个重叠贡献最大的区域。
  • 这个时候就可以扫描线了!令 \(y\) 上有一条 \([x, x + w)\) 的线,边权为 \(+c\)\(y+h - 1\) 上有一条 \([x, x + w)\) 的线,边权为 \(-c\)。做普普通通的扫描线即可。
  • 这道题的线段树维护的元素有两种形式:一种是维护 \([x_1, x_2)[x_2, x_3)[x_3, x_4)\) 一样的线段的权值。在这种情况下所离散化的点就是 \((x+w)\),每次的编号也都要 \(-1\)。而另一种是单独维护各个点:\(x_1, x_2, x_3, x_4\),所以这个时候离散化下的点就是 \((x + w - 1)\),线段树编号不需要 \(-1\)。这是一个需要注意的细节!我小时候应该是抄题解写的,非常不厚道。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int N = 1e5;
struct seg {
	int l, r, x, val;
} Q[N * 2 + 10];

int cmax[N * 4 + 10], tag[N * 4 + 10];
il int ls(int x) {
	return 2 * x;
}
il int rs(int x) {
	return 2 * x + 1;
}
il void push_up(int x) {
	cmax[x] = max(cmax[ls(x)], cmax[rs(x)]);
}
il void push_tag(int now, int val) {
	cmax[now] += val;
	tag[now] += val;
}
il void push_down(int now) {
	if(tag[now] != 0) {
		push_tag(ls(now), tag[now]);
		push_tag(rs(now), tag[now]);
		tag[now] = 0;
	}
}
void upd(int ql, int qr, int s, int t, int now, int val)  {
	if(ql <= s && t <= qr) {
		push_tag(now, val);
		return ;
	}
	int mid = (s + t) >> 1;
	push_down(now);
	if(ql <= mid) upd(ql, qr, s, mid, ls(now), val);
	if(qr > mid) upd(ql, qr, mid + 1, t, rs(now), val);
	push_up(now);
}
int qry(int ql, int qr, int s, int t, int now) {
	if(ql <= s && t <= qr) return cmax[now];
	int mid = (s + t) >> 1, maxn = 0;
	push_down(now);
	if(ql <= mid) maxn = max(maxn, qry(ql, qr, s, mid, ls(now)));
	if(qr > mid) maxn = max(maxn, qry(ql, qr, mid + 1, t, rs(now)));
	return maxn;
}
bool cmp(seg &x, seg &y) {
	if(x.x == y.x) return x.val > y.val;
	else return x.x < y.x;
}
int pos[N * 2 + 10];

int n, w, h;
void init() {
	memset(cmax, 0, sizeof(cmax));
	memset(tag, 0, sizeof(tag));
	cin >> n >> w >> h;
	for(int i = 1, x, y, val; i <= n; i++) {
		cin >> x >> y >> val;
		Q[2 * i - 1] = (seg){x, x + w, y, val};
		Q[2 * i] = (seg){x, x + w, y + h - 1, -val};
		pos[2 * i - 1] = x, pos[2 * i] = x + w;
	}
	sort(Q + 1, Q + 2 * n + 1, cmp);
	sort(pos + 1, pos + 2 * n + 1);
	int ans = 0;
	int len = unique(pos + 1, pos + 2 * n + 1) - pos - 1;
	for(int i = 1; i <= 2 * n; i++) {
		Q[i].l = lower_bound(pos + 1, pos + len + 1, Q[i].l) - pos;
		Q[i].r = lower_bound(pos + 1, pos + len + 1, Q[i].r) - pos;
		upd(Q[i].l, Q[i].r - 1, 1, len, 1, Q[i].val);
		ans = max(ans, cmax[1]);
	}
	cout << ans << endl;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int T; cin >> T;
	while(T--) init();
}

P9190

这题没做出来还是太唐诗了吧 TAT

暴力 dp:\(f[i, j]\) 代表能否让前 \(i\) 个文本串匹配到模式串第 \(j\) 位(模式串看成是 bessie 反复重合,因为它没有 border,这样是可行的),发现 \(j\) 只有最后对 6 取余不同的 6 个才有用,所以设 \(f[i, j](j \in [0, 6])\) 代表目前匹配到 bessie 的第 \(j\) 位出现了多少 bessie,再带上一个最小删除权值即可。因为它是满足最优子结构性质的。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5, inf = 2e8;
struct node {
	int len, val;
} f[N + 10][10];
int a[N + 10], n;
string str;
string b = "#bessie";

int main() {
	cin >> str; str = "#" + str;
	n = str.size() - 1;
	for(int i = 1; i <= n; i++)
		cin >> a[i];
		
	f[0][0] = (node){0, 0};
	for(int i = 1; i <= 6; i++)
		f[0][i] = (node){-inf, inf};
	for(int i = 1; i <= n; i++) {
		f[i][0] = (node){f[i - 1][0].len, f[i - 1][0].val}; 
		for(int j = 1; j <= 6; j++) {
			f[i][j] = (node){f[i - 1][j].len, f[i - 1][j].val + a[i]};
			if(str[i] == b[j]) {
				if(f[i - 1][j - 1].len < f[i][j].len) continue;
				else if(f[i - 1][j - 1].len == f[i][j].len) f[i][j].val = min(f[i][j].val, f[i - 1][j - 1].val);
				else f[i][j] = (node){f[i - 1][j - 1].len, f[i - 1][j - 1].val};
			}
		}
		if(f[i][6].len + 1 > f[i][0].len)
			f[i][0] = (node){f[i][6].len + 1, f[i][6].val};
		else if(f[i][6].len + 1 == f[i][0].len)
			f[i][0].val = min(f[i][6].val, f[i][0].val);
		
//		for(int j = 0; j <= 6; j++)
//			cout << f[i][j].len << ' ' << f[i][j].val << endl;
//			
//		cout << endl;
	}
	cout << f[n][0].len << '\n' << f[n][0].val << '\n';
}

总结

  • 要及时删除不必要的状态。显然,这题中大部分 \(j\) 都是无用的,只有最后 7 个 \(j\) 才有用。
  • 对于 dp 如果满足最优子结构性质,不妨设一个二元组。这其实和 dp 本身的设计逻辑不相悖,即便他不常见。

P8272

模拟赛被创死,展现出普及组选手 cfm 强大的普及水准。

什么时候 \(i\) 可以接住苹果 \(a\)?(\(t_i \ge t_a\)

  • \(x_i \ge x_a\) 时,有 \(t_i - t_a \ge x_i - x_a\)\(t_i - x_i \ge t_a - x_a\)
  • \(x_i < x_a\) 时,有 \(t_i - t_a \ge x_a - x_i\)\(t_i + x_i \ge t_a + x_a\)

赛时的时候列出了后面两个式子,但是仍然并不会这道题。

我们注意到后面两个式子一定同时成立!这一点在移向前的式子非常明显,因为 \(t_i - t_ a\ge |x_i - x_a|\)。但是移向后似乎就没有这么显然了(upd:其实也非常显然,上下两式相加得到 \(t_i \ge t_a\)……,反过来只要减一下就好了……)

因此令 \(t_i - x_i = x_i, t_i + x_i = y_i\),那么 \(i\) 可以接住苹果 \(a\) 的充要条件就是 \(x_i \ge x_a, y_i \ge y_a\)。(这里的 \(x\) 和上面的 \(x\) 不是一样的!)这是一个二维偏序问题,但一般的二维偏序是计数,这里是最优化:将数对匹配。

回顾二维偏序的过程,是先对 \(x\) 进行排序,然后利用数据结构维护 \(y\)。这里我们也按照 \(x\) 排序以此消除 \(x_i \ge x_a\) 的约束。对于每个奶牛 \(i\) 考虑把能吃掉的苹果放到集合 \(S\) 中。从 \(x\) 小的奶牛往 \(x\) 大的奶牛考虑。不考虑删除时这个 \(S\) 是在增长的。接下来考虑删除,显然 \(y\) 越小的苹果越难被匹配。所以从 \(y\) 尽量小的奶牛开始匹配。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
struct node {
	int opt, x, y, num;
	bool operator < (const node &other) const {
		return y < other.y;
	}
} Q[N + 10];
bool cmp(node &x, node &y) {
	return x.x > y.x;
}
multiset <node> S;

int n;
vector <node> vec[3];
int main() {
	cin >> n;
	for(int i = 1, t, x; i <= n; i++) {
		cin >> Q[i].opt >> t >> x >> Q[i].num;
		Q[i].x = t - x, Q[i].y = t + x;
		vec[Q[i].opt].push_back(Q[i]);
	}
	
	sort(vec[1].begin(), vec[1].end(), cmp);
	sort(vec[2].begin(), vec[2].end(), cmp);
	
	int ia = 0, sum = 0;
	for(int i = 0; i < vec[1].size(); i++) {
		while(ia < vec[2].size() &&
			vec[2][ia].x >= vec[1][i].x) {
				S.insert(vec[2][ia]);
				ia++;
			}
			
		while(1) {
			if(!vec[1][i].num) break;
			set <node>::iterator it = S.lower_bound(vec[1][i]);
			if(it == S.end()) break;
			
			node tmp = (*it);
			if(tmp.num >= vec[1][i].num) {
				sum += vec[1][i].num;
				tmp.num -= vec[1][i].num;
				vec[1][i].num = 0;
				S.erase(it);
				S.insert(tmp);
				break;
			}
			else {
				sum += tmp.num;
				vec[1][i].num -= tmp.num;
				S.erase(it);
			}
		}
	}
	cout << sum << endl;
}

总结

真的是一个很值得反思反思的题目。

复盘过程

  • 在得到充要条件的一步,因为一般来讲是要根据 \(x\) 的大小讨论一下的。因此少前置约束是一个值得的动机。
  • 从另一个角度,以少前置约束出发就是 \(t_i - t_a \ge |x_i - x_a|\),等价于 \(t_i - t_a \ge x_i - x_a, t_i - t_a \ge x_a - x_i\) 同时成立。即 \(x \ge |y|\) 等价于 \(x \ge y, x \ge -y\)。这个变形我之前似乎没有用过,记下来
  • 于是得到了一个类二维偏序的匹配问题。对于一个高维问题,最常用的思想是降维。三位偏序二维偏序如此,高维前缀和如此,高维统计也如此。
    • 高维问题
      • 降维
      • 独立维度
  • 接下来的贪心中,是从“难以匹配”的奶牛和苹果出发。这也启发对于匹配类的贪心问题应该从“难以匹配”的元素出发思考策略。
    • 这是因为一般来说匹配问题不带权(比如这道题万一苹果带权似乎就不好贪心了)。难以匹配的赶紧匹配,容易匹配的放到后面匹配也没关系。这是“决策包容性”。
    • 关于这块似乎还要结合几个线段贪心详细总结总结

P10161

  • 考虑对一个串有多少合法子串进行计数。

  • 不妨考虑增量(类似与一个区间 dp 的思想,划分子问题)

    • 如果往外套一层 (),那么合法子串数量 +1
    • 如果往外面加一个 (),令目前这一层有 \(k\) 个 ()()()(),那么合法子串数量 +k
  • 可以想到这样求出能够构造 \(i\) 个合法子串的最少序列长度 \(f_i\):每次先构造出 ()()()() 的,然后给外面套层 (()()()()),接下来同理。转移只要考虑套一层 (),外面增加 \(j-1\)(),一共会增加 \((j + 1)j/2\) 个。dp 即可。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5;
ll nd[N + 10], rt[N + 10];
int getn(int x) {
	int l = 1, r = 1000, ans = 0;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(mid * (mid + 1) / 2 <= x) ans = mid, l = mid + 1;
		else r = mid - 1;
	}
	return ans;
}
void prep() {
	for(int i = 1; i <= N; i++) {
		int w = getn(i);
		nd[i] = N * 10;
		for(int j = 1; j <= w; j++) {
			int ww = (j + 1) * j / 2;
			if(nd[i - ww] + 2 * j < nd[i]) {
				rt[i] = j;
				nd[i] = nd[i - ww] + 2 * j;
			}
		}
	}
}
void print(int x) {
	if(x == 1) {
		putchar('(');
		putchar(')');
		return ;
	}
	if(x == 0)
		return ;

	int w = rt[x];
	putchar('(');
	print(x - (w + 1) * w / 2);
	putchar(')');
	for(int i = 1; i < w; i++)
		putchar('('), putchar(')');
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	prep();
	int T; cin >> T;
	while(T--) {
		int n, qk;
		cin >> n >> qk;
		if(n >= nd[qk]) {
			print(qk);
			for(int i = nd[qk] + 1; i <= n; i++)
				putchar('(');
			puts("");
		}
		else puts("-1");
	}
}

总结

  • 这道题我一开始想贪心去了……确实不应该,我怎么知道这道题能不能贪?
  • 不要把题目想太复杂,大胆一些。
  • 感觉这题很萌?

P3226

\(x\)\(2x, 3x\) 的约束关系形成一颗树,问题变成树上最大独立集——错啦!比如 \(1->2, 1->3, 2->6, 3->6\)——是 DAG 上最大独立集。我会网络流!

这道题 n = 1e5 网络流不了,怎么办,考虑图的性质。发现 1->2->6,1->3->6 构成一个很有意思的矩形,实际上如果把 1->2->4->...->x->2x,3->6->...->x->2x 之类列出来,可以得到一个矩形,问题变成了矩形上的最大独立集。列数最多 \(O(\log_3 n)\),可以状压。由于互相间没有相邻 \(1\) 的数量并不大,所以跑得动。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 16, M = 11, V = 1e5;
const ll Mod = 1e9 + 1;
ll f[N + 3][(1 << M) + 3];
int num[N + 10], n, tot = 0;

bool flag[V + 10];
vector <int> sc;
void prepsc() {
	for(int S = 0; S < (1 << 16); S++) {
		bool flag = 0;
		for(int i = 0; i < 15; i++)
			if(((S >> i) & 1) && ((S >> (i + 1)) & 1)) {
				flag = 1;
				break;
			}
		if(flag) continue;
		else sc.push_back(S);
	}
}
ll calc() {
	f[0][0] = 1;
	for(int i = 1; i < tot; i++) {
		for(int s1 = 0; s1 < sc.size(); s1++)
			f[i][s1] = 0;

		for(int s0 = 0; s0 < sc.size(); s0++) {
			if(sc[s0] > (1 << num[i - 1]) - 1) break;
			for(int s1 = 0; s1 < sc.size(); s1++) {
				if(sc[s1] > (1 << num[i]) - 1) break;
				int S0 = sc[s0], S1 = sc[s1];
				if((S0 & S1) > 0) continue;
				f[i][s1] = (f[i][s1] + f[i - 1][s0]) % Mod;
			}
		}
	}

	ll sum = 0;
	for(int S = 0; S < sc.size(); S++)
		sum = (sum + f[tot - 1][S]) % Mod;
	return sum;
}
void work() {
	ll ans = 1;
	for(int i = 1; i <= n; i++) {
		if(!flag[i]) {
			tot = 1;
			for(int j = i; j <= n; j *= 2) {
				if(flag[j]) break;
				num[tot] = 0;
				for(int k = j; k <= n; k *= 3) {
					flag[k] = 1;
					num[tot]++;
				}
				tot++;
			}

			ans = ans * calc() % Mod;
		}
	}
	cout << ans << endl;
}
int main() {
	cin >> n;
	prepsc();
	work();
}

总结

  • 用图刻画“能否同时出现”的信息。因为这类信息都具有一定传递性(?)或者相互影响。
  • 无法继续往下做怎么办?——分析性质!这道题的性质在于每个点的出边都是 \(2\),最多入边也都是 \(2\),所以可以类似的考虑一个矩形上的独立集计数。

P2157

状态设 \(f[i, S, k]\)\((i - 1)\) 的人都吃过了,接下来 \(i \sim i + 7\) 人内有 \(S\) 的人吃了,最后一个吃饭的人是 \((i + k)\) 的最短等待时间。

转移并不难

  • 如果 \(i\) 没有吃,那么就在 \(S\) 内选择 \(x\not\in S\) 作为最后一个吃饭的,转移到 \(f[i, S + \{x\}, x - i]\)。要特别判断插队的不能互相间有影响。
  • 如果 \(i\) 吃了(即 \(S\) 中包含 \(i\)),那么转移到 \(f[i + 1, S - \{i\}, k - 1]\)

如果设出来了状态,转移相当平凡,但是怎么样设计出这个状态?


首先考虑:有很多排列的方案。于是可以从增量的角度考量一个队伍:最后一个放谁?

  1. 需要考虑一个极长存在的前缀 \(1\sim x\),同时 \((x + 1)\) 不存在。这是因为 \(1\sim x\) 前缀内放的一定合法,其它的都是插了 \((x + 1)\) 的队。插了队的也互相间没有影响,所以关键就在 \((x + 1)\) 合不合法。
    • 这就启发我们将这个 \(x\) 作为阶段。
  2. 接下来一切都顺其自然:因为我们需要知道队伍的全集,它可以用 \(x, S\) 来刻画。同时我们需要知道最后一个人计算代价,它可以用 \(k\) 刻画。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000, B = 8;
const int inf = 1e9;
int t[N + 10], b[N + 10], n;
int f[N + 3][(1 << B) + 3][B * 2 + 3];
void upd(int &x, int y) {
	x = min(x, y);
}
void init() {
	cin >> n;
	for(int i = 1; i <= n; i++)
		cin >> t[i] >> b[i];

	memset(f, 0x3f, sizeof(f));
	for(int i = 0; i <= b[1]; i++)
		f[1][(1 << i)][i + B] = 0;
	for(int i = 1; i <= n; i++) {
		for(int S = 0; S < (1 << B); S++) {

			for(int P = 0; P <= 2 * B; P++) {
				int p = i + P - B;
				if(p <= 0) continue;
				for(int x = i; x <= i + b[i]; x++) {
					bool flag = 0;
					
					for(int j = (x - i); j <= 7; j++)
						if((S >> j) & 1)
							if(j + i - x > b[x]) {//以防选 S 内插队的出现不合法
								flag = 1;
								break;
							}
					if(flag) continue;
					if(!((S >> (x - i)) & 1)) {
						upd(f[i][S | (1 << (x - i))][x - i + B],
							f[i][S][P] + (t[x] | t[p]) - (t[x] & t[p]));
					}
				}
				if((S & 1) && P > 0) upd(f[i + 1][S >> 1][P - 1], f[i][S][P]);
			}
		}
	}
	// cout << f[1][2][1 + B] << endl;
	// cout << f[1][10][3 + B] << endl;
	// cout << f[1][14][2 + B] << endl;
	// cout << f[1][46][5 + B] << endl;
	// cout << f[1][62][4 + B] << endl;
	// cout << f[1][190][7 + B] << endl;
	// cout << f[1][191][B] << endl;
	int minn = inf;

	for(int i = max(n - 7, 1); i <= n; i++) {
		for(int P = 0; P <= 2 * B; P++) {
			int S = (1 << (n - i + 1)) - 1;
			// if(f[i][S][P] == 9) {
			// 	cout << i << ' ' << S << ' ' << P << endl;
			// 	cout << f[i][S][P] << endl;
			// }
			minn = min(minn, f[i][S][P]);
		}
	}
	cout << minn << endl;
}
int main() {
	int T; cin >> T;
	while(T--) init();
}

总结

  1. 无论是计数问题还是最优化问题,在没有思路的时候都应该考虑判定。
  2. 如果考虑判定没有什么启发(这道题就没有),可以尝试增量(这道题就是通过对”增加最后一个元素“考量分析的)
  3. 我 noip 炸了,这是 noip 后第一篇总结。真的不能再摆烂了,我与 cfm 共勉。

P4107

  • 对于菊花图
    • 显然是从 \(son(v) + c(v)-1\) 尽量小的点向父亲合并。
  • 对于链的合并。
    • 贪心的从链底向上合并。
    • 为什么是正确的。
      • 对于菊花图实际上就是按照 \(c(v)\) 大小排序。
      • 反证法,假设不是正确的,即从链底 \(t\) 开始一直向上最多可以合并到 \(f\),但是从另外一个点 \(s\) 开始最多可以合并到更高的 \(h\)。那么从 \(s\) 开始首先会至少失去 \(t\)\(s\) 这一段的受益,其次从 \(f\) 在网上可以继续合并到 \(h\)。失去的收益(没有删除的点)至少为 \(1\),而增加的收益(删除了的点)最多为 \(1\)(也就是 \(f\) 可以被删除),因此是不优的,可以直接贪。
  • 对于全局的合并,就是直接按照深度从大往小 \(son(v) + c(v) - 1\) 大小向父亲合并即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6;
vector <int> gra[N + 10];
int dep[N + 10], fat[N + 10], son[N + 10];
int n, m, c[N + 10];
void dfs(int u) {
	dep[u] = dep[fat[u]] + 1;
	for(int i = 0; i < gra[u].size(); i++) {
		int v = gra[u][i];
		dfs(v);
	}
}
struct node {
	int to, cval;
	bool operator < (const node &other) const {
		return cval > other.cval;
	}
};

int ord[N + 10];
bool cmp(int &x, int &y) {
	return dep[x] > dep[y];
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= n; i++) 
		cin >> c[i];
	for(int i = 1; i <= n; i++) {
		int x; cin >> son[i];
		ord[i] = i;
		for(int j = 1; j <= son[i]; j++) {
			cin >> x;
			fat[x + 1] = i;
			gra[i].push_back(x + 1);
		}
	}

	dfs(1);

	int cnt = 0, last = 1;
	sort(ord + 1, ord + n + 1, cmp);
	priority_queue <node> que;
	for(int i = dep[ord[1]]; i >= 2; i--) {
		while(dep[ord[last]] == i && last <= n)
			que.push((node){ord[last], c[ord[last]] + son[ord[last]] - 1}), last++;

		while(!que.empty()) {
			node fr = que.top(); que.pop();
			int u = fr.to, cv = fr.cval;
			int f = fat[u];
			if(son[f] + c[f] + cv <= m) {
				c[f] += c[u];
				son[f] = son[f] - 1 + son[u];
				cnt++;
			}
		}
	}
	cout << cnt << endl;
}

总结

  1. 对于树上最优化问题,如果没办法 dp(这东西看上去就不像 dp)可以考虑贪心
  2. 对于树上贪心策略
    1. 先从菊花图考虑,再从链考虑。
    2. 整体的框架很多时候都是从拓扑序开始。
    3. 菊花图的情况是为了得到策略,而链的情况是为了说明拓扑序更新的正确性。
    4. 正所谓:贪心向爹合并(X

P3594

答案显然具有单调性。于是二分。考虑判定。

如何判定一个区间最后可以不可以合法?只需要在其中取得长度为 \(d\) 和最大的一个子区间即可。这个单调队列就好了。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e6;
ll a[N + 10], S[N + 10], p;
int n, d;

int fr = 1, tl = 0;
int que[N + 10];
bool check(int x) {
	int i = 1;
	fr = 1, tl = 0;
	for(int l = 1; l + x - 1 <= n; l++) {
		while(fr <= tl && que[fr] < l) fr++;
		while(i <= l + x - d) {
			while(fr <= tl && S[que[tl] + d - 1] - S[que[tl] - 1] <=
							S[i + d - 1] - S[i - 1]) tl--;
			que[++tl] = i;
			i++;
		}

		int u = que[fr];
		if((S[l + x - 1] - S[l - 1]) - (S[que[fr] + d - 1] - S[que[fr] - 1]) <= p)
			return 1;
	}
	return 0;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> p >> d;
	for(int i = 1; i <= n; i++)
		cin >> a[i], S[i] = S[i - 1] + a[i];

	int L = d + 1, R = n, ans = d;
	while(L <= R) {
		int mid = (L + R) >> 1;
		if(check(mid)) ans = mid, L = mid + 1;
		else R = mid - 1;
	}
	cout << ans << endl;
}

P1954

什么?CSP2023T4?(大雾

这道题的策略也是类似的:按照拓扑序从 \(t\) 最短的出发。证明

posted @ 2024-12-04 17:27  CatFromMars  阅读(162)  评论(0)    收藏  举报