202411 题目合集(2)

P10217

  • 无论是最优化问题还是计数问题,如果没有想法->考虑判定。
  • 答案能否为 m
    • 这个 (x,y) 很容易让人想到向量。同时 imodn 是一个周期一样的东西。记给定的 n 个分别是向量 v0,v2,,vn1zj=i=0jvi。那么对于 x 仅仅考虑这些的位移就是 m1nzn1+z(m1)modn。要求能够通过加上一个 w 使得 m1nzn1+z(m1)modn+w=a。其中 a 为给定的向量。所以 w 是可以根据 m 唯一确定的。考虑这个 w 能否通过 m 次,每次横纵上的位移绝对值之和不超过 k。也就是说对于 w=(x,y) 只需要 |x|+|y|k×m 即可。我们得到了 O(1) 的判定方法。
  • 考虑优化。上面的式子根据 (m1)modn 的剩余系分类。设 r=(m1)modn,t=m1n。问题变成对于每一个 r 求出最小的 t 使得 azrtzn1=w|xw|+|yw|km。以下的 x,y 均是合成的向量的坐标,不是读入的值。|xw|=|xaxrtxn1|,|yw|=|yayrtyn1|,|xw|+|yw|=|xaxrtxn1|+|yayrtyn1|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 分别相加!
  • 对于绝对值和的小于等于,可以把它暴力拆成 2k 个不带绝对值的式子。

P1502

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

  • 直接考虑似乎不太行,于是考虑每一颗星星对答案的贡献。
  • 先特别考虑一下这个“在边框上不算在内”的条件。把最下面一行的星星放到框上,那么要求最上面一行的 yylow+h1。横向的
  • 我们可以利用右上角唯一确定一个矩形,同时也可以据此将每颗星星可能出现在的矩形的范围划分出来:对于星星 x,y,能够圈住它的右上角在 (x,y)(x+w1,y+h1) 构成的矩形内。
  • 我们拿这个右上角代表整个矩形,那么问题变成了:求出一个重叠贡献最大的区域。
  • 这个时候就可以扫描线了!令 y 上有一条 [x,x+w) 的线,边权为 +cy+h1 上有一条 [x,x+w) 的线,边权为 c。做普普通通的扫描线即可。
  • 这道题的线段树维护的元素有两种形式:一种是维护 [x1,x2)[x2,x3)[x3,x4) 一样的线段的权值。在这种情况下所离散化的点就是 (x+w),每次的编号也都要 1。而另一种是单独维护各个点:x1,x2,x3,x4,所以这个时候离散化下的点就是 (x+w1),线段树编号不需要 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[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?(tita

  • xixa 时,有 titaxixatixitaxa
  • xi<xa 时,有 titaxaxiti+xita+xa

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

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

因此令 tixi=xi,ti+xi=yi,那么 i 可以接住苹果 a 的充要条件就是 xixa,yiya。(这里的 x 和上面的 x 不是一样的!)这是一个二维偏序问题,但一般的二维偏序是计数,这里是最优化:将数对匹配。

回顾二维偏序的过程,是先对 x 进行排序,然后利用数据结构维护 y。这里我们也按照 x 排序以此消除 xixa 的约束。对于每个奶牛 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 的大小讨论一下的。因此少前置约束是一个值得的动机。
  • 从另一个角度,以少前置约束出发就是 tita|xixa|,等价于 titaxixa,titaxaxi 同时成立。即 x|y| 等价于 xy,xy。这个变形我之前似乎没有用过,记下来
  • 于是得到了一个类二维偏序的匹配问题。对于一个高维问题,最常用的思想是降维。三位偏序二维偏序如此,高维前缀和如此,高维统计也如此。
    • 高维问题
      • 降维
      • 独立维度
  • 接下来的贪心中,是从“难以匹配”的奶牛和苹果出发。这也启发对于匹配类的贪心问题应该从“难以匹配”的元素出发思考策略。
    • 这是因为一般来说匹配问题不带权(比如这道题万一苹果带权似乎就不好贪心了)。难以匹配的赶紧匹配,容易匹配的放到后面匹配也没关系。这是“决策包容性”。
    • 关于这块似乎还要结合几个线段贪心详细总结总结

P10161

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

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

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

x2x,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(log3n),可以状压。由于互相间没有相邻 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](i1) 的人都吃过了,接下来 ii+7 人内有 S 的人吃了,最后一个吃饭的人是 (i+k) 的最短等待时间。

转移并不难

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

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


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

  1. 需要考虑一个极长存在的前缀 1x,同时 (x+1) 不存在。这是因为 1x 前缀内放的一定合法,其它的都是插了 (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 开始首先会至少失去 ts 这一段的受益,其次从 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 @   SIXIANG32  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示