Order Capital Round 1 (Codeforces Round 1038, Div. 1 + Div. 2) ABCD题解
A. Greedy Grid
题意:
在一个网格中,一条路径被称为“贪心路径”,如果它从左上角的单元格出发,每一步只能向右或向下移动,并且每次总是移动到相邻的值更大的单元格(如果相等则任选其一)。
一条路径的价值是它经过的所有单元格的值之和,包括起点和终点。
是否存在一个 \(n×m\) 的非负整数网格,使得没有任何一条贪心路径能够取得所有向下/向右路径中的最大价值?
思路:
如果 $ n = 1 $ 或 $ m = 1 $,那么只有一条向下/向右的路径,因此它是显然贪婪且最大的。
如果 $ n = m = 2 $,任何路径在第一步之后都必须在两个邻居之间做出选择。显然,贪婪地移动总是会得到一条最大路径。
在所有其他情况下,这样的网格确实存在。假设 $ n \leq m $,从一个填充了1的 $ n \times m $ 网格开始,并修改左上角的 $ 2 \times 3 $ 子网格如下:
很容易验证,在这个网格中,没有贪婪路径能够达到可能的最大值。
代码
void solve() {
int n, m;
cin >> n >> m;
if (n == 1 || m == 1) {
cout << "NO" << "\n";
}else if (n == 2 && m == 2) {
cout << "NO" << "\n";
}else{
cout << "YES" << "\n";
}
}
B. Pile Shuffling
题意:
给定 n 个二进制堆,其中第 i 个堆顶部有 \(a_i\)个 0,底部有 \(b_i\)个 1。
每次操作中,你可以取出任意堆的顶部元素,并将其移动到任意堆的任意位置(包括原堆)。
计算最少需要多少次操作,才能使第 i 个堆形成顶部 \(c_i\) 个 0 和底部 \(d_i\)个 1 的目标状态。
思路:
先考虑 1,如果一个堆的 1 要减少,那么需要先把上面的 0 取走才能取出 1
再考虑 0,如果一个堆的 0 要减少,那么可以直接从顶部取走 0 然后插入到该插入的地方
因为“保证存在一系列操作可以将这些堆转换为目标状态”。所以只考虑减少的情况即可
代码
void solve() {
int n;
cin >> n;
ll zero = 0;
ll one = 0;
ll cnt = 0;
for (int i = 1; i <= n; ++i) {
ll a, b, c, d;
cin >> a >> b >> c >> d;
if (b > d) {
cnt += a + b - d;
}else if (a > c) {
cnt += a - c;
}
}
cout << cnt << "\n";
}
C. Manhattan Pairs
题意:
给你平面上的 n 个点(n 是偶数),你可以把这些点两两分成一组,共 \(\frac{n}{2}\)组。要求每一个组点对之间的曼哈顿距离之和最大,输出任意一种可能的方案。
思路:
将左上的点和右下的点配对,左下的点和右上的点进行配对
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int N = 2e5 + 10;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
struct node {
int x, y, id;
};
bool cmp (node a, node b) {
return a.x < b.x;
}
bool cmp2 (node a, node b) {
return a.y < b.y;
}
node a[N];
void solve() {
int n;
cin >> n;
vector<node> b (n / 2 + 1);
vector<node> c (n / 2 + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i].x >> a[i].y;
a[i].id = i;
}
sort (a + 1, a + 1 + n, cmp);
for (int i = 1; i <= n / 2; ++i) {
b[i] = {a[i].x, a[i].y, a[i].id};
}
int q = n / 2;
for (int i = q + 1; i <= n; ++i) {
c[i - q] = {a[i].x, a[i].y, a[i].id};
}
sort (b.begin () + 1, b.end (), cmp2);
sort (c.begin () + 1, c.end (), cmp2);
for (int i = 1; i <= n / 2; ++i) {
cout << b[i].id << " " << c[n / 2 - i + 1].id << "\n";
}
}
int main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
cin >> t;
while (t --) {
solve();
}
return 0;
}
D. Traffic Lights
题意:
我们需要计算从顶点1到顶点n的最小总时间,以及在最小总时间的前提下的最小等待时间。
在顶点u,当时间为t时,我们有两种操作:
1、等待1秒:时间变为t+1,等待时间增加1,令牌位置不变。
2、通过第(t mod deg(u) + 1)条边移动
我们需要从顶点1到顶点n的最短总时间,以及在总时间最小的前提下最少的等待时间
思路:
①考虑bfs:
记录当前节点,当前时间,等待时间三个参数,在每一层更新两种状态。
MLE on test case 5
主要原因是BFS未对状态进行有效剪枝,导致状态爆炸
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int N = 5e3 + 10;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int tt;
vector<int> w[N];
int n, m;
bool flag = false;
struct node {
ll u, t, tw;
};
void solve() {
flag = false;
cin >> n >> m;
for (int i = 1; i <= n; ++i) w[i].clear ();
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
w[x].push_back (y);
w[y].push_back (x);
}
queue<node> que;
ll rest = iinf, resw = iinf;
que.push ({1, 0, 0});
map<pair<int, int>, int> mp;
while (!que.empty ()) {
auto it = que.front ();
que.pop ();
if (it.u == n) {
if (it.t < rest) {
rest = it.t;
resw = it.tw;
}else if (it.t == rest && it.tw < resw) {
rest = it.t;
resw = it.tw;
}
}else{
if (it.t > rest) continue;
int nn = w[it.u].size ();
mp[{it.u, it.t % nn}] = 1;
que.push ({w[it.u][it.t % nn], it.t + 1, it.tw});
for (int i = 1; i <= nn; ++i) {
que.push ({it.u, it.t + i, it.tw + i});
}
}
}
cout << rest << " " << resw << "\n";
}
int main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
tt = 1;
cin >> tt;
while (tt --) {
solve();
}
return 0;
}
②考虑dp:
用dp[i]表示从起点1到达节点i所需的最小额外等待时间
时间迭代:外层循环j表示当前时间步(从0开始递增)
操作类型:
①等待操作:在当前节点多停留一个时间步(增加等待时间)
②移动操作:按当前时间j选择对应出边移动(不增加等待时间)
终止条件:当终点n的dp[n]首次不为INF时输出当前时间步j和等待时间dp[n]
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int N = 5e3 + 10;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
vector<int> w[N];
void solve() {
int n, m;
cin >> n >> m;
vector<ll> dp (n + 1, 0);
for (int i = 1; i <= n; ++i) w[i].clear (), dp[i] = iinf;
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
w[x].push_back (y);
w[y].push_back (x);
}
dp[1] = 0;
for (int i = 0; ; ++i) {
if (dp[n] != iinf) {
cout << i << " " << dp[n] << "\n";
return ;
}else{
vector<ll> new_dp = dp;
for (int j = 1; j <= n; ++j) {
if (new_dp[j] != iinf) {
new_dp[j] = dp[j] + 1;
}
}
for (int j = 1; j <= n; ++j) {
if (new_dp[j] == iinf) {
continue;
}else{
int nn = w[j].size ();
if (nn == 0) continue;
int ne = w[j][i % nn];
new_dp[ne] = min (new_dp[ne], dp[j]);
}
}
dp = new_dp;
}
}
}
int main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
cin >> t;
while (t --) {
solve();
}
return 0;
}