Codeforces Round 1038 A-E [250719]
官方题解很详细,这里就简单写一写我的思考路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <bits/stdc++.h> using namespace std;
int main() { cin.tie(nullptr)->sync_with_stdio(false);
int t; cin >> t;
while (t--) [] { int n, m; cin >> n >> m;
if (n == 1 || m == 1 || n == 2 && m == 2) { cout << "NO" << endl; } else { cout << "YES" << endl; } } ();
return 0; }
|
答案可以表示为 (移出去的 + 拿进来的) / 2。算一下至少移出去多少,至少拿进来多少。注意开 long long。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <bits/stdc++.h> using namespace std; using ll = long long;
int main() { cin.tie(nullptr)->sync_with_stdio(false);
int t; cin >> t;
while (t--) [] { int n; cin >> n;
ll res = 0; while (n--) { ll a, b, c, d; cin >> a >> b >> c >> d;
if (b > d) { res += a + c + abs(b - d); } else { res += abs(a - c) + abs(b - d); } } cout << res / 2 << endl; } ();
return 0; }
|
如果只有一个维度,以中位数为界分两部分就好了。但这里有二维,而且行列不独立,那可能要分别以x,y 中位数为界分四部分。
最简单的情况是,左上和右下配对,左下和右上配对。算一算是否一定可以配对好:
- 设左上、右上、右下和左下的点的个数分别为A,B,C,D。
- 左右两部分相等,得到A+D=B+C;
- 上下两部分相等,得到A+B=C+D。
- 这两个方程相加减,就能得到A=C,B=D。
这说明一定可以左上和右下配对,左下和右上配对。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <bits/stdc++.h> using namespace std;
int main() { cin.tie(nullptr)->sync_with_stdio(false);
int t; cin >> t;
while (t--) [] { int n; cin >> n;
vector<pair<int, int>> x(n), y(n); for (int i = 0; i < n; i++) { cin >> x[i].first >> y[i].first; x[i].second = y[i].second = i; } ranges::sort(x); ranges::sort(y);
vector<pair<int, int>> res(n); for (int i = 0; i < n; i++) { res[x[i].second].first = i < n / 2; res[y[i].second].second = i < n / 2; }
vector<int> ans[4]; for (int i = 0; i < n; i++) { ans[res[i].first * 2 + res[i].second].push_back(i); }
for (int i = 0; i < ans[0].size(); i++) { cout << ans[0][i] + 1 << " " << ans[3][i] + 1 << endl; } for (int i = 0; i < ans[1].size(); i++) { cout << ans[1][i] + 1 << " " << ans[2][i] + 1 << endl; } return; } ();
return 0; }
|
其实题目不难,可能大多数人(包括我)看到最短路就条件反射 Dijkstra 或 BFS。这里的最短路有两种限制,而且有优先级,直接塞在 priority_queue 里排序并不好处理。不妨换个思路,枚举其中一个限制,动态求另一个限制的最小值。
具体来说,设d(t,x) 表示t 秒后到达x 点最少需要停顿多少秒。外层循环枚举时间,内层循环枚举每条边。
尽管总边数m=n2,但每一时刻有用的边至多n 条,所以转移的复杂度总计O(n2)。
其实有点像 Bellman-Ford 的想法,令停顿的边权为 1,正常走的边权是 0,求起点到终点的恰好t 边最短路。传统的 Bellman-Ford 中,外层循环是轮次,这里我们为轮次赋予实际意义,即时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <bits/stdc++.h> using namespace std;
constexpr int inf = 0x3f3f3f3f;
int main() { cin.tie(nullptr)->sync_with_stdio(false);
int t; cin >> t;
while (t--) [] { int n, m; cin >> n >> m;
vector<vector<int>> adj(n); for (int i = 0; i < m; i++) { int x, y; cin >> x >> y; x--; y--; adj[x].push_back(y); adj[y].push_back(x); }
vector<int> f(n, inf); f[0] = 0; for (int t = 0; t < 2 * n; t++) { vector<int> nf(n, inf); for (auto x = 0; x < n; x++) { int y = adj[x][t % adj[x].size()]; nf[y] = min(nf[y], f[x]); nf[x] = min(nf[x], f[x] + 1); } f = nf; if (f[n - 1] != inf) { cout << t + 1 << " " << f[n - 1] << endl; return; } } assert(false); return; } ();
return 0; }
|
调了两个小时……
题目中说的限制,翻译成数学语言是:
- 如果bi>ai+1,那么这条路比另外一条路更好,必须bi+bi+1⩾ai+1+ai+2,bi+bi+1+bi+2⩾ai+1+ai+2+ai+3……
移项,转化为
- 如果ai+1−bi<0,那么必须bi−ai+1⩾(ai+2−bi+1),bi−ai+1⩾(ai+2−bi+1)+(ai+3−bi+2)……,也就是bi−ai+1⩾j>imax{k=i+1∑j(ak+1−bk)}
- 否则没有限制。
所以说i 位置bi 的填法只与后面有关,考虑倒着 DP。
除了位置i 以外,关键元素是j>imax{k=i+1∑j(ak+1−bk)}(这个式子是最大子段和的形式,也可以倒着递推求得),即ai+1−bi 的组合,于是考虑以ai+1−bi 这个整体做 DP。
- 设f(i,v) 表示,满足v=j>imax{k=i+1∑j(ak+1−bk)} 的方案数。
- 转移方向为f(i,max{v,v+ai+1−bi})←f(i+1,v)。转移时,外层循环枚举位置i ,内层枚举x=ai+1−bi 的值做转移。
- 注意,如果x=ai+1−bi<0 就有限制,需要控制循环的范围。
复杂度O(nk2)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <bits/stdc++.h> using namespace std; using Z = mull<u32, 998'244'353>;
int main() { cin.tie(nullptr)->sync_with_stdio(false);
int t; cin >> t;
while (t--) [] { int n, k; cin >> n >> k;
vector<int> a(n), b(n); for (auto& x : a) { cin >> x; } for (auto& x : b) { cin >> x; }
vector<Z> f(k + 1); f[0] = 1; for (int i = n - 2; i >= 0; i--) { vector<Z> nf(k + 1); vector<int> ways(2 * k); for (int bi = 1; bi <= k; bi++) { for (int aj = 1; aj <= k; aj++) { if (b[i] != -1 && b[i] != bi) continue; if (a[i + 1] != -1 && a[i + 1] != aj) continue; ways[aj - bi + k]++; } } for (int x = 1 - k; x < k; x++) { if (x < 0) { for (int v = 0; v <= -x; v++) { nf[max(0, x + v)] += f[v] * ways[x + k]; } } else { for (int v = 0; v <= k; v++) { nf[min(k, x + v)] += f[v] * ways[x + k]; } } } f = move(nf); } Z res = accumulate(f.begin(), f.end(), Z(0)); res *= (b.back() == -1 ? k : 1); res *= (a[0] == -1 ? k : 1); cout << res << endl; return; } ();
return 0; }
|