Codeforces Round #521 (Div. 3) + DP

Codeforces链接:https://codeforces.com/contest/1077

A. Frog Jumping (签到)

  题意 :一开始有一只青蛙在 \(0\) 号位置,然后它有 \(k\) 次跳跃的机会,奇数次是向右跳,偶数次是向左跳。

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

typedef long long ll;

int main() {
	int T; scanf("%d", &T);
	while (T --) {
		int a, b, k; scanf("%d%d%d", &a, &b, &k);
		ll ans = 0, res = a - b;
		if (k & 1) ans += k / 2 * res + (ll)a;
		else ans += k / 2 * res;
		printf("%lld\n", ans);
	}
	return 0;
}

B. Disturbed People (贪心)

  题意 :有 n 个灯泡,每一个灯泡都有 \(0\) \(1\) 两种状态,如果让这些灯泡中间没有 \(1\) \(0\) \(1\) 的情况,我们最少需要这个灯泡多少次。
  思路 :不满足题意是,贪心的删除右边的灯泡即可。

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

typedef long long ll;
const int maxn = 2e5 + 10;
int a[maxn];
bool vis[maxn]; 

int main() {
	int n; scanf("%d", &n);
	for (int i = 0; i < n; ++ i) scanf("%d", &a[i]);
	int ans = 0;
	for (int i = 1; i < n - 1; ++ i) {
		if (!a[i] && a[i - 1] && a[i + 1]) {
			a[i + 1] = 0;
			++ ans;
		}
	}
	printf("%d\n", ans);
	return 0;
}

C. Good Array (枚举)

  题意 :给我们一个数组\([a, b, c, d, e]\),我们可以删除某一个数字 \(a\),然后剩下的数字满足 \(b + c + d = e\) 的情况就说明删除这个数字是合法的。问这样的数字有多少个?
  思路 :首先我们先求出所有数字的和,然后将这些数字按照从小到大排序,并且记录每一个数字的位置,然后遍历数组,每一次都删除当前的数字,然后剩下的数字中,在去掉最大的数字,再剩下的数字和等于最大的数字,那么这个数字就满足题意。

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

typedef long long ll;
const int maxn = 2e5 + 10;
int ans[maxn];

struct Node{
	int val, pos;
	bool operator < (const Node & node) const {
		if (val != node.val)
			return val < node.val;
		else return pos < node.pos;
	}
}node[maxn];

int main() {
	int n; scanf("%d", &n);
	ll sum = 0;
	for (int i = 0; i < n; ++ i) {
		scanf("%d", &node[i].val);
		node[i].pos = i + 1;
		sum += (ll)node[i].val;
	}
	sort(node, node + n);
	//for (int i = 0; i < n; ++ i) cout << node[i].val << " ";
	int len = 0;
	for (int i = 0; i < n; ++ i) {
		if (i != n - 1) {
			if (sum - (ll)(ll)node[i].val - (ll)node[n - 1].val == node[n - 1].val) ans[len ++] = node[i].pos;
		} else {
			if (sum - (ll)node[i].val - (ll)node[n - 2].val == node[n - 2].val) ans[len ++] = node[i].pos;
		}
	}
	printf("%d\n", len);
	for (int i = 0; i < len; ++ i) printf("%d%c", ans[i], i == len - 1 ? '\n' : ' ');
	return 0;
}

D. Cutting Out (二分)

  题意 :给出一组数据,让我们找到 k 个数字,保证这些数字在这个数组中出现的次数都相同,此题是让我们最大化这个次数。
  思路 :我们二分次数,然后在数组中找到满足这个次数的数字有多少个,找到这个次数以后,那么大于这个次数的数字就可以放入答案中,需要有一个注意点就是答案数组可能超过 \(k\) 个数字,那么我们只需要找出 \(k\) 个即可,或者只输出 \(k\) 个。

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

typedef long long ll;
const int maxn = 2e5 + 10;
int s[maxn], number[maxn], ans[maxn];
 
int n, k;

bool check(int mid) {
	int sum = 0;
	for (int i = 1; i <= maxn; ++ i) {
		sum += number[i] / mid;
	}
	return sum >= k;
}

int main() {
	scanf("%d%d", &n, &k);
	for (int i = 0; i < n; ++ i) {
		scanf("%d", &s[i]);
		number[s[i]] ++;
	}
	int l = 1, r = maxn, maxCnt = 0;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (check(mid)) {
			maxCnt = mid;
			l = mid + 1;
		} else r = mid - 1;
	}
	//cout << maxCnt << endl;
	int cnt = 0;
	for (int i = 0; i < n; ++ i) {
		if (number[s[i]] >= maxCnt) {
			ans[cnt ++] = s[i];
			number[s[i]] -= maxCnt;
		}
		if (cnt == k) break;
	}
	for (int i = 0; i < cnt; ++ i) printf("%d%c", ans[i], i == cnt - 1 ? '\n' : ' ');
	return 0;
} 

E. Thematic Contests (思维 + 二分)

  题意 :现在有 \(n\) 个问题,然后每个问题都有一些主题,现在我们需要举办一些竞赛,要求 :每个竞赛的主题互不相同,一个竞赛中的所有问题主题都相同,后面一场比赛的问题的数量是前面一场比赛的问题的数量的 2 倍,问最多可以安排多少问题。第一场比赛的问题数目是随意的,可以自己定义的。
  思路 :首先我们输入了每一个问题的主题,然后统计每一个主题出现了多少次,因为同一个主题的问题必然出现在同一场比赛中,然后我们将这些次数放在一个数组中,然后进行排序,因为我们不知道第一场比赛的问题数目,所以这里我们就枚举第一场比赛的问题数量,这个最大值也就是经过我们处理的数组中的最大值,因为问题的最大数量不可能超过这个,在枚举第一场比赛的问题的数量的时候我们还需要记录它是属于哪一个主题的,方便后面我们进行二分的操作,在确定第一场题目数量的时候,我们就可以在剩下的区间中寻找是否有大于其二倍的主题,然后再次记录这个主题,再在剩下的区间里面去找到再多二倍的主题,一直重复这样的操作,直到我们找不到合法的主题了为止,在重复此过程的时候我们就可以记录我们拿到的合法问题的数量,对于每一个第一场的问题数量,我们取一个最大值输出即可。

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

typedef long long ll;
const int maxn = 2e5 + 10;
int a[maxn], cnt[maxn];
map<int, int>m;

int main() {
	int n; scanf("%d", &n);
	for (int i = 1; i <= n; ++ i) {
		scanf("%d", &a[i]);
		m[a[i]] ++;
	}
	int num = 0;
	for (map<int, int> :: iterator it = m.begin(); it != m.end(); ++ it) {
		cnt[num ++] = it -> second;
	}
	sort(cnt, cnt + num);
	//int inx = lower_bound(cnt + 1, cnt + num, 1) - cnt;
	//cout << inx << endl;
	int ans = 0, pos = 0;//记录当前是哪一场竞赛 
	for (int i = 1; i <= cnt[num - 1]; ++ i) {
		while (i > cnt[pos]) pos ++;
		int x = i << 1, sum = i, inx = pos + 1;
			while (1) {
			inx = lower_bound(cnt + inx, cnt + num, x) - cnt;
			if (inx == num) break;
			++ inx; sum += x; x <<= 1; 
		}
		ans = max(ans, sum);
	}
	printf("%d\n", ans);
	return 0; 
} 

F. Pictures with Kittens (单调队列优化DP)

  题意 :给我们一个数组,然后让我们在这些数组中选择 \(x\) 个数字,然后最大化这些数字的和。在这个数组中,\(k\) 个相邻的数字之中必然有一个或以上的已经被选择的数字。
  思路 :我们设 \(dp[i][j]\) 表示在前 \(i\) 个数字中选择 \(j\) 个数字的和的最大值,假设我们每次都选择当前的值作为答案的某一个贡献,那么在这之前的 \(k - 1\) 个数字就能够保证这个 \(k\) 区间有合法的数字,然后我们只要找到前面 \(k - 1\) 个数字中,选择 \(j - 1\) 个的情况的最大值,然后加上这种情况即可。对于找到这个最大值的情况,在 \(easy\) 版本中我们枚举这个 \(k\) 区间就可以得到最终结果,在最后的 \(k\) 区间,如果我们能够找到 \(x\) 个这样的数字,那么在这个区间内必然会出现一个,所以之后只需要在最后的 \(k\) 区间找寻结果即可。对于 \(hard\) 版本,我们可以优化的地方就是这个找到那个 \(k\) 区间的最大值的情况,这个时候我们在找最大值的时候,我们可以使用单调队列来优化,我们维护一个单调递减的一个队列,对于每一次的选择我们尝试把每一个 \(i\) 都放入队列中,如果这个 \(i\) 的头不在规定的区间内就弹出,然后如果这个队列的尾部所对应的数字和小于当前的点所对应的和我们就可以更新这个队列,将小的弹出,当前的加进去,这样枚举每一个点的时候,队列中的首部都是前面 \(k - 1\) 个数所对应的最大值。对于每多一次的选择,我们要记得清空队列。

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

typedef long long ll;
const int maxn = 1e3 + 10;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int a[maxn];
ll dp[maxn][maxn];

int main() {
	int n, k, x; scanf("%d%d%d", &n, &k, &x);
	for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
	for (int i = 0; i <= n; ++ i) {
		for (int j = 0; j <= n; ++ j) dp[i][j] = -inf;
	}
	dp[0][0] = 0;
	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= max(i, x); ++ j) {
			for (int jj = max(0, i - k); jj <= i - 1; ++ jj) {
				if (dp[jj][j - 1] != -inf) dp[i][j] = max(dp[i][j], dp[jj][j - 1] + (ll)a[i]);
			}
		}
	}
	/*for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= x; ++ j) {
			cout << dp[i][j] << " ";
		}
		cout << endl;
	}*/
	ll ans = 0;
	//for (int i = 1; i <= n; ++ i) cout << dp[i][x] << " "; cout << endl; 
	for (int i = n - k + 1; i <= n; ++ i) ans = max(ans, dp[i][x]);
	if (ans) printf("%lld\n", ans);
	else puts("-1"); 
	return 0;
} 
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 5e3 + 10;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int a[maxn];
ll dp[maxn][maxn];
deque<int>dq;

int main() {
	int n, k, x; scanf("%d%d%d", &n, &k, &x);
	for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
	for (int i = 0; i <= n; ++ i) {
		for (int j = 0; j <= n; ++ j) dp[i][j] = -inf;
	}
	dp[0][0] = 0;
	for (int j = 1; j <= x; ++ j) {
		dq.push_back(0);
		for (int i = 1; i <= n; ++ i) {//依次添加每一个 i,如果此时值比较小就弹出队列 
			while (dq.size() && dq.front() < i - k) dq.pop_front();
			dp[i][j] = dp[dq.front()][j - 1] + (ll)a[i];//当前的最大值 
			while (dq.size() && dp[dq.back()][j - 1] < dp[i][j - 1]) dq.pop_back();
			dq.push_back(i); 
		}
		dq.clear();
	}
/*	for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= x; ++ j) {
			cout << dp[i][j] << " ";
		}
		cout << endl;
	}*/
	ll ans = 0;
	//for (int i = 1; i <= n; ++ i) cout << dp[i][x] << " "; cout << endl; 
	for (int i = n - k + 1; i <= n; ++ i) ans = max(ans, dp[i][x]);
	if (ans) printf("%lld\n", ans);
	else puts("-1"); 
	return 0;
} 

Longest Ordered Subsequence (最长上升子序列)

题目链接 :http://poj.org/problem?id=2533
  思路 :经典 \(DP\) 问题,暴力即可。这里我使用了二分的方法来优化。

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>

typedef long long ll;
const int maxn = 1e3 + 10;
int a[maxn], dp[maxn];//长度为 i 时的最小值

int main() {
	int n; scanf("%d", &n);
	for (int i = 0; i < n; ++ i) scanf("%d", &a[i]);
	dp[0] = a[0]; int len = 1;
	for (int i = 1; i < n; ++ i) {
		if (a[i] > dp[len - 1]) dp[len ++] = a[i];
		else {
			int inx = std::lower_bound(dp, dp + len, a[i]) - dp;
			dp[inx] = a[i]; 
		}
	}
	printf("%d\n", len);
	return 0;
}

龟兔赛跑 (线性DP)

题目链接 :http://acm.hdu.edu.cn/showproblem.php?pid=2059
  思路 :也是一个比较经典的 \(DP\) 问题,类似于加油站的问题。我们假设在当前的点充电,然后依次递推出每一个状态下的最短时间即可。需要注意的就是在 0 号点进行充电的时候我们是不需要花费时间的。

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

typedef long long ll;
const int maxn = 100;
const int inf = 0x3f3f3f3f;
int p[maxn];
double dp[maxn];

int main() {
	int L;
	while (~scanf("%d", &L)) {
		int n, c, t; scanf("%d%d%d", &n, &c, &t);
		int vr, vt1, vt2; scanf("%d%d%d", &vr, &vt1, &vt2);
		for (int i = 1; i <= n; ++ i) scanf("%d", &p[i]);
		for (int i = 0; i <= n + 1; ++ i) dp[i] = inf;
		p[0] = 0, p[n + 1] = L, dp[0] = 0;
		for (int i = 0; i <= n + 1; ++ i) {
			for (int j = i + 1; j <= n + 1; ++ j) {
				double resLen1 = p[j] - p[i] <= c ? p[j] - p[i] : c; 
				double resLen2 = p[j] - p[i] <= c ? 0 : p[j] - p[i] - c;
				dp[j] = min(dp[j], resLen1 / vt1 + resLen2 / vt2 + (i ? (double)t : 0) + dp[i]);
			}
		}
		double tr = L * 1.0 / vr;
		if (dp[n + 1] > tr) puts("Good job,rabbit!");
		else puts("What a pity rabbit!");
	}
	return 0;
}
posted @ 2020-04-25 18:00  菜鸭丶  阅读(126)  评论(0编辑  收藏  举报