P9871 [NOIP2023] 天天爱打卡

原题链接

此题暴力会挺容易拿分的

暴力1:爆搜进行哪个挑战,判断一下是不是满足题目中给的要求就行。时间肯定会爆掉,计算时间复杂度为O(2^n)O(n2^n),应该可以得到8分。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (!isdigit(ch)) {
		if (ch == '-')f = -1;
		ch = getchar();
	}
	while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
	return x * f;
}
int c, T, n, m, k, d, f[N];
struct node {
	int l, r, v;
} q[N];
bool cmp(node a, node b) {
	if (a.r != b.r){
		return a.r < b.r;
	}
	return a.l < b.l;
}
signed main() {
	c = read(), T = read();
	while (T--) {
		n = read(), m = read(), k = read(), d = read();
		memset(f, -0x3f, sizeof(f));
		f[0] = 0;
		int ans = 0;
		for (int i = 1; i <= m; i++) {
			int x = read(), y = read(), v = read();
			q[i] = {x - y + 1, x, v};
			if (y <= k){
				ans += max(0ll, v - y * d);
			}
		}
		printf("%lld\n", ans);
	}
	return 0;
}

暴力2(即我暴力):可以写一个dp,需要对题意进行转化,对于第i个挑战,(li,ri,vi)表示从第li=xi-yi+1天开始,到ri=xi天为止连续跑步,能够得到的能量值为vi。

设f(i,0/1)分别表示第i天强制不选/不强制不选的最大能量。在附带一个数组arr(i,j)=∑rk​<j,lk​=i​vk来辅助。就会发现转移式子:

f(i,0)=max(f(i−1,0),f(i−1,1))

然后在实现辅助数组的时候,只需要开一维的arrj,然后每更新一次右端点i,就将满足rk​=i 的挑战k 提出来。

dp代码如下(36pts):

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return x * f;
}
const int maxn = 3e5 + 10;
int n, m, k, d;
int c,t;
int f[maxn][2], ls[maxn];
vector<pair<int, int> > q[maxn];
signed main() {
	c = read();//编号
	t = read();//组数
	while (t--) {
		n = read(), m = read(), k = read(), d = read();
		int x, y, v;
		for (int i = 1; i <= n; i++)q[i].clear();
		memset(f, 0, sizeof(f));
		memset(ls, 0, sizeof(ls));
		for (int i = 1; i <= m; i++) {
			x = read(), y = read(),  v = read();
			q[x].push_back(make_pair(x - y + 1, v));
		}
		int ans = 0;
		for (int i = 1; i <= n; i++) {
			int sum = 0;
			for (auto j : q[i]) {
				ls[j.first] += j.second;
			}
			for (int j = i; i - j < k && j > 0; j--) {
				sum += ls[j];
				f[i][1] = max(f[i][1], f[j - 1][0] + (j - 1) * d + sum - i * d);
			}
			f[i][0] = max(f[i - 1][0], f[i - 1][1]);
			ans = max(ans, max(f[i][0], f[i][1]));
		}
		printf("%lld\n", ans);
	}
	return 0;
}

然后...我们就要打开标签,偷偷瞟一眼了,动态规划+线段树。既然自己想的dp过不了,那就直接搞线段树呗,刚开始我愣是想不到这道题跟线段树有什么关系,后来看了题解才知道。在于我们每次转移都是暴力枚举左端点。而不难发现,每次枚举的左端点是一个区间,只要能够满足一个查询区间最大值的数据结构就能把复杂度降下去。但中途加入任务区间又会带来区间修改。等等,区间修改和区间最大值?这直接线段树不就行了!使用线段树来处理,每次二分最靠左的满足条件的 j(当然也可以双指针)时间复杂度O(mlogm)肯定能过的啊。

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
inline int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	return x * f;
}
bool st;
const int maxn = 9e5 + 10;
struct Segment_Tree {
	int n;
	struct node {
		ll maxx, tag;
		node(ll maxx = 0, ll tag = 0): maxx(maxx), tag(tag) {}
	} d[maxn << 2];
	node mergenode(node l, node r) {
		return node(max(l.maxx, r.maxx));
	}
	void pushdown(int p) {
		if (d[p].tag) {
			d[p << 1].tag += d[p].tag;
			d[p << 1].maxx += d[p].tag;
			d[p << 1 | 1].tag += d[p].tag;
			d[p << 1 | 1].maxx += d[p].tag;
			d[p].tag = 0;
		}
	}
	void build(int l, int r, int p) {
		if (l == r) {
			d[p] = node();
			return;
		}
		int mid = l + r >> 1;
		build(l, mid, p << 1);
		build(mid + 1, r, p << 1 | 1);
		d[p] = mergenode(d[p << 1], d[p << 1 | 1]);
	}
	void update(int l, int r, int s, int t, int p, ll add) {
		if (s <= l && r <= t) {
			d[p].tag += add;
			d[p].maxx += add;
			return;
		}
		int mid = l + r >> 1;
		pushdown(p);
		if (s <= mid)update(l, mid, s, t, p << 1, add);
		if (mid < t)update(mid + 1, r, s, t, p << 1 | 1, add);
		d[p] = mergenode(d[p << 1], d[p << 1 | 1]);
	}
	node query(int l, int r, int s, int t, int p) {
		if (s <= l && r <= t) {
			return d[p];
		}
		int mid = l + r >> 1;
		pushdown(p);
		if (t <= mid)return query(l, mid, s, t, p << 1);
		if (mid < s)return query(mid + 1, r, s, t, p << 1 | 1);
		return mergenode(query(l, mid, s, t, p << 1), query(mid + 1, r, s, t, p << 1 | 1));
	}
	void build(int x) {
		n = x;
		build(0, n + 1, 1);
	}
	int query(int l, int r) {
		return query(0, n + 1, l, r, 1).maxx;
	}
	void update(int l, int r, ll add) {
		update(0, n + 1, l, r, 1, add);
	}
} tree;
int n, m, k;
ll d, f[maxn][2];
vector<pair<int, int> > vec[maxn];
vector<pair<pair<int, int>, int> > tmp;
vector<ll> disc;
int Lk[maxn];
bool ed;
signed main() {
	int testcase = read(), T = read();
	while (T--) {
		n = read(), m = read(), k = read(), d = read();
		int x, y, v;

		disc.clear();
		tmp.clear();
		memset(f, 0, sizeof(f));

		for (int i = 1; i <= m; i++) {
			x = read(), y = read(),  v = read();
			tmp.push_back(make_pair(make_pair(x - y + 1, x), v));
			disc.push_back(x - y + 1);
			disc.push_back(x);
			disc.push_back(x - y);
		}
		disc.push_back(-1);
		sort(disc.begin(), disc.end());
		disc.erase(unique(disc.begin(), disc.end()), disc.end());
		int tot = disc.size() - 1;
		tree.build(tot);
		for (int i = 1; i <= tot; i++)vec[i].clear();
		int l = 1;
		for (int i = 0; i < m; i++) {
			tmp[i].first.first  = lower_bound(disc.begin(), disc.end(), tmp[i].first.first ) - disc.begin();
			tmp[i].first.second = lower_bound(disc.begin(), disc.end(), tmp[i].first.second) - disc.begin();
			vec[tmp[i].first.second].push_back(make_pair(tmp[i].first.first, tmp[i].second));
		}
		for (int i = 1; i <= tot; i++) {
			while (disc[i] - disc[l] >= k && l <= i)l++;
			Lk[i] = l;
		}
		ll ans = 0;
		for (int i = 1; i <= tot; i++) {
			for (auto j : vec[i]) {
				tree.update(1, j.first, j.second);
			}
			f[i][1] = tree.query(Lk[i], i) - disc[i] * d;
			f[i][0] = max(f[i - 1][0], f[i - 1][1]);
			ans = max(ans, max(f[i][0], f[i][1]));
			tree.update(i + 1, i + 1, f[i][0] + disc[i] * d);
		}
		printf("%lld\n", ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值