A.Toy Cars(Codeforces 545A)
思路
简单实现题。将表示碰撞结果的矩阵保存下来,然后检查每个车辆是否是“good car“即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
bool good;
int n, G[maxn][maxn];
vector <int> v;
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
scanf("%d", &G[i][j]);
}
good = true;
for(int j = 1; j <= n; j++) {
if(i != j && G[i][j] % 2) {
good = false;
}
}
if(good) v.push_back(i);
}
printf("%d\n", v.size());
for(int i = 0; i < v.size(); i++) {
printf("%d ", v[i]);
}
puts("");
return 0;
}
B.Equidistant String(Codeforces 545B)
思路
要构造一个与两个输入字符串的汉明距离都相等的字符串,只要从一个字符串出发“走过”两个字符串汉明距离的一半就可以了。具体地,假设输入的串为
A
和
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
char s[maxn], t[maxn];
int n, m, cnt;
vector <int> v;
int main() {
scanf("%s%s", s, t);
cnt = 0;
n = strlen(s);
for(int i = 0; i < n; i++) {
if(s[i] != t[i]) {
v.push_back(i);
cnt++;
}
}
if(cnt % 2) puts("impossible");
else {
m = cnt >> 1;
for(int i = 0; i < m; i++) {
s[v[i]] = t[v[i]];
}
printf("%s\n", s);
}
return 0;
}
C.Woodcutters(Codeforces 545C)
大意
在一条数轴上有若干棵树。每棵树有一个高度,问最多能砍多少棵树,使得没有倒下的树碰到其它的树。
思路
这题最直观的解法是搜索。搜索树的每一层都表示一次决策,即该层表示的树是往左倒还是往右倒。但由于
n
的规模太大,这个算法是不可行的。于是尝试用记忆化搜索或动态规划去优化它。假设我们考虑到第
- 第i棵树直立的时候。不论第
i
棵想要直立是可以“不看前一棵树的脸色”的,因此有
d[i][0]=max{d[i−1][j],0≤j≤2} 。 - 第i棵树向左倒的时候。第i棵树想要向左倒,就必须“看前一棵树的脸色”,也就是看前一棵树给它留下了多少空位。当前一棵树没有向右倒的时候有 d[i][1]=max(d[i−1][0],d[i−1][1])+1 ,当前一棵树向右倒的时候有 d[i][1]=max(d[i][1],d[i−1][2]+1) 。在实现的时候要注意判断位置是否足够让一棵树倒下。
- 第i棵树向右倒的时候。这种情况的最优值一定比第
i
棵树直立的时候要多
1 ,因此 d[i][2]=d[i][0]+1 。
最后的答案就是 d[n][2] 。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10, INF = 1e9;
int n, ans, x[maxn], h[maxn], d[maxn][3];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d%d", &x[i], &h[i]);
}
// 设置两棵虚拟的树
x[0] = - INF;
x[n+1] = INT_MAX;
for(int i = 1; i <= n; i++) {
// 直立
d[i][0] = max(max(d[i-1][0], d[i-1][1]), d[i-1][2]);
// 向左倒
if(h[i] < x[i] - x[i-1]) {
d[i][1] = max(d[i-1][0], d[i-1][1]) + 1;
}
if(h[i] + h[i-1] < x[i] - x[i-1]) {
d[i][1] = max(d[i][1], d[i-1][2] + 1);
}
// 向右倒
if(h[i] < x[i+1] - x[i]) {
d[i][2] = d[i][0] + 1;
}
}
printf("%d\n", d[n][2]);
return 0;
}
思路
其实本题也不是没有贪心策略。对于两端的树而言,左端的树往左倒,右端的树往右倒一定是最优的策略。对于其它的树(从左向右扫描的话),先考虑向左倒再考虑向右倒一定是最优策略。(证明方法暂时没想到)
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n, ans, x[maxn], h[maxn];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d%d", &x[i], &h[i]);
}
ans = 2;
for(int i = 2; i < n; i++) {
if(x[i] - x[i-1] > h[i]) {
ans++;
}
else if(x[i+1] - x[i] > h[i]) {
x[i] += h[i];
ans++;
}
}
printf("%d\n", n <= 2 ? n : ans);
return 0;
}
D.Queue(Codeforces 545D)
大意
有若干个人在排队等待服务,服务员服务每个人的时间都是不同的,对于一个人而言,若他前面的人的被服务总时间大于它自己被服务的时间,他就会感到失望。问最多可以让多少人感到不失望。
思路
拿到这道题,直觉告诉我应该是先对数据排序(因为感性认知告诉我忍耐值小的人放在前面似乎能够满足尽可能多的人)然后再微调(感性认知不一定是事实)。为了方便讨论我们用O表示排好序的队列中不会失望的某个人,用X表示排好序的队列中会失望的某个人。在微调的过程中,首先
OOO...O
形式的情况下,显然不应该调整任何人的顺序。其次在
OOO...OX
形式的情况下,
X
不应该向前调整,应为这样是没有任何好处的(为了满足一个人而让至少一个人失望)。相反地,
所以我们总结出算法:先对输入数据排序,然后扫描排序后数据的同时维护前缀和
sum
和不会失望的人数
cnt
。扫描经过的每个
O
的值都被加入
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n, cnt, sum, a[maxn];
int main() {
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
sort(a, a + n);
cnt = sum = 0;
for(int i = 0; i < n; i++) {
if(a[i] >= sum) {
sum += a[i];
cnt++;
}
}
printf("%d\n", cnt);
return 0;
}
E.Paths and Trees(Codefoeces 545E)
大意
在一个图
G
中找一棵“从
思路
这个问题同时具有最小生成树和最短路径的特点,但最小生成树无法算出起点到每个点的最短路径,因此只考虑用后者来解决问题。
假设我们已经在原图上跑了一遍
最后,在算法的实现上,我们需要跑一遍修改后的
Dijkstra
算法,让该算法支持记录权值最小的前驱边。然后再遍历一遍图中的点就能够求出答案了。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 优先队列用的结点
typedef pair <ll, int> p;
const int maxn = 3e5 + 10;
const ll INF = 1e15;
int n, m, u, v;
ll w, sum;
vector <int> ans;
// 存储边的信息的结构体
struct edge {
int from, to;
ll dist;
edge(int from, int to, ll dist): from(from), to(to), dist(dist) {}
};
// Dijkstra算法模板
struct Dijkstra {
// 保存最短路径距离
ll d[maxn];
// 保存点的前驱边
int f[maxn];
// 保存边集
vector <edge> edges;
// 邻接表
vector <int> G[maxn];
// 将一条边加入邻接表
void addEdge(int u, int v, ll w) {
G[u].push_back(edges.size());
edges.push_back(edge(u, v, w));
}
void solve(int s) {
// 初始化最短路径距离数组
fill(d + 1, d + n + 1, INF);
d[s] = 0;
// 初始化优先队列
priority_queue < p, vector<p>, greater<p> > pq;
pq.push(p(0, s));
while(!pq.empty()) {
p node = pq.top();
pq.pop();
int u = node.second;
if(node.first > d[u]) {
continue;
}
for(int i = 0; i < G[u].size(); i++) {
edge& e = edges[G[u][i]];
int v = e.to;
// 松弛的同时更新前驱边
if(d[v] > d[u] + e.dist) {
d[v] = d[u] + e.dist;
f[v] = G[u][i];
pq.push(p(d[v], v));
}
// 修改前驱边
if(d[v] == d[u] + e.dist && e.dist < edges[f[v]].dist) {
f[v] = G[u][i];
}
}
}
}
}o;
int main() {
scanf("%d%d", &n, &m);
while(m--) {
scanf("%d%d%I64d", &u, &v, &w);
o.addEdge(u, v, w);
o.addEdge(v, u, w);
}
scanf("%d", &u);
o.solve(u);
sum = 0;
for(int i = 1; i <= n; i++) {
if(i == u) {
continue;
}
sum += o.edges[o.f[i]].dist;
ans.push_back(o.f[i] / 2 + 1);
}
printf("%I64d\n", sum);
for(int i = 0; i < ans.size(); i++) {
printf("%d ", ans[i]);
}
return 0;
}