China Collegiate Programming Contest (CCPC) Jinan Site (The 3rd Universal Cup. Stage 17: Jinan) 题解

news/2025/11/8 20:53:24/文章来源:https://www.cnblogs.com/ycsblogs/p/19203031

目录
  • Problem A. The Fool
  • Problem B. The Magician
  • Problem C. The Empresso
  • Problem D. The Emperor
  • Problem E. The Chariot
  • Problem F. The Hermit
  • Problem G. The Wheel of Fortune
  • Problem H. Strength
  • Problem I. The Hanged Man
  • Problem J. Temperance
  • Problem K. The Devil
  • Problem L. The Towerr
  • Problem M. Judgement

我的评价是济南题就是恶心

整体上就是一种感觉:怎么感觉这个做法很暴力啊……唉复杂度怎么是对的……唉怎么就是这么做?比较无语(

虽然最后一题是个有趣的思维题,但是角度有点过于刁钻不是很好

优点是题面很用心,题解写的也很好。虽然题目都有点刁钻,但是看得出来出题人对这套题非常上心

Problem A. The Fool

写不出来退役吧。真的需要写题解吗?

#include <iostream>
#include <map>
#include <random>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e6 + 100;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}std::map<std::string, int> cnt;
std::map<std::string, std::pair<int, int>> pos;int main() {std::ios::sync_with_stdio(false);cin.tie(nullptr), cout.tie(nullptr), cerr.tie(nullptr);int n, m, k;cin >> n >> m >> k;for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {char ch;std::string str;for (int t = 1; t <= k; t++) {cin >> ch;str += ch;}cnt[str]++;pos[str] = {i, j};}}for (auto [str, c] : cnt) {if (c == 1) {cout << pos[str].first << ' ' << pos[str].second << endl;}}return 0;
}

Problem B. The Magician

我一开始看错题了。以为每张牌都有一个权值(捂脸)

恋人和死神显然是等价的。贪心用掉牌后一定只会剩下四组牌,且每组牌一定不多于四张。而且功能牌不管怎么操作,都最多最多为某个花色再凑成一次五张。

直接进行一个全排列枚举用来贪心表示优先满足哪些花色,这个贪心策略一定是正确的(你都枚举了所有情况了还有啥正确不正确的)

复杂度其实是常数。

#include <algorithm>
#include <iostream>
#include <random>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e6 + 100;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}int t, ans, max = 0;
int n;void solve(std::vector<int>& p, std::vector<int> now, std::vector<int> spe) {int tmp = 0;for (int i = 0; i < p.size(); i++) {int need = 5 - now[p[i]];// 如果无法满足,无论如何无法满足int sum = 0;if (need > spe[p[i]] * 3 + spe[0]) continue;for (int j = p.size() - 1; j > i; j--) sum += now[p[j]];if (sum < need) continue;if (need > spe[p[i]] * 3) {spe[0] -= need - spe[p[i]] * 3;spe[p[i]] = 0;}now[p[i]] += need;tmp++;for (int j = p.size() - 1; j > i; j--) {if (now[p[j]] >= need) {now[p[j]] -= need;need = 0;} else {need -= now[p[j]];now[p[j]] = 0;}}}upmax(max, tmp);
}void solve() {std::vector<int> spe(5, 0);std::vector<int> cnt(5, 0);cin >> n;for (int i = 1; i <= n; i++) {std::string str;cin >> str;char ch = str[1];int type = 0;if (ch == 'D') type = 1;if (ch == 'C') type = 2;if (ch == 'H') type = 3;if (ch == 'S') type = 4;cnt[type]++;}for (int i = 1; i <= 4; i++) {cin >> spe[i];}int x;cin >> spe[0] >> x;spe[0] += x;std::vector<int> p(4);for (int i = 0; i < p.size(); i++) p[i] = i + 1;int ans = 0;for (int i = 1; i <= 4; i++) ans += cnt[i] / 5, cnt[i] %= 5;max = 0;do {solve(p, cnt, spe);} while (std::next_permutation(p.begin(), p.end()));ans += max;cout << ans << endl;
}int main() {std::ios::sync_with_stdio(false);cin.tie(nullptr), cout.tie(nullptr), cerr.tie(nullptr);cin >> t;while (t--) solve();return 0;
}

Problem C. The Empresso

好玩的构造题,个人认为其实比 \(D\) 题简单,但是貌似通过的人其实比 \(D\) 题少。

假设前 \(t\) 行执行完后可以执行 \(f(t)\) 次并且栈刚好为空,那么在 \(t+1\) 行我们可以构造出来:\(f(t+1) = f(t)\times 2 + 2\) 或者 \(f(t+1) = f(t)+ 2\)。也就是加上类似:

POP a GOTO a + 1; PUSH a GOTO 1
POP a GOTO a + 1; PUSH a GOTO a

的命令即可实现这两种效果,方便讨论称作操作一和操作二。

也就是我们最后需要某个 \(t\) 使得 \(f(t) = k - 1\)。因为退出命令本身就算作一次,所以不妨直接将 \(k\)\(1\) 去讨论。

从二进制位的角度去观察 \(f(t)\) 的变化:

如果我们只有操作一,那么最后的数字一定形如:

11111111111110

这个时候来一次操作二就可以将其变成某个 \(2^x\)

我们可以将 \(k\) 进行二进制分解后,钦定最高位就是通过这种操作进行构造出来的,其他的位就是在某一位被操作二进来后不断乘 \(2\) 出来的。也就是类似:

k = 10100= 10000 +00100= 01110 +00100 +00010

这种分解方式是唯一的,\(f(t)\) 进行转移的时候,操作一相当于为所有数字末尾塞 \(0\) 并且为钦定位塞 \(1\),操作二则是添加一个新的 10 进去。

总结一下就是钦定一个位去消化掉 \(f(t) \times 2 + 2\) 转移中那个讨厌的 \(+2\)

太麻烦了有没有更简单的方法……好像没有(挠头

官方题解中貌似也没有完全消除掉操作一里那个讨厌的东西。

最劣的情况就是 \(2^{31} - 1\),上述构造只会出现 \(60\) 行命令。非常的充裕。

#include <iostream>
#include <random>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e6 + 100;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}// 记 f(x) 为子函数操作次数
// 则加一行以后,可以将操作次数变成 f(x) * 2 + 2 或者 f(x) + 2int k, tmp = 0;// 若去掉最后一位,那么每次相当于 t + 1 或者 t * 2 + 1
// 我们不放钦定某一位来享受所有的 1std::vector<std::pair<int, int>> ans;void add(int tot, int type) {ans.push_back({tot, type});
}void end(int tot) {ans.push_back({tot, -1});
}void print(int tot, int type) {if (type == -1) {cout << "HALT; PUSH 99 GOTO 1" << endl;return;}cout << "POP " << tot << " GOTO " << tot + 1 << "; PUSH " << tot << " GOTO " << (type ? 1 : tot) << endl;if (type == 1) {tmp = tmp * 2 + 2;} else {tmp = tmp + 2;}
}int main() {std::ios::sync_with_stdio(false);cin.tie(nullptr), cout.tie(nullptr), cerr.tie(nullptr);int tot = 1, first = -1;cin >> k;if (k == 1) {end(1);goto ans;}for (int i = 31; i >= 0; i--) {if ((k >> i) & 1) {int j;for (j = i; j >= 0; j--) {if (!((k >> j) & 1)) break;}i = j + 1;}}for (int i = 31; i >= 1; i--) {if (first != -1) {add(tot, 1), tot++;if ((k >> i) & 1) add(tot, 0), tot++;} else if ((k >> i) & 1) {first = i;}}// cerr << "NOW OUT" << endl;add(tot, 0), tot++;end(tot);
ans:cout << ans.size() << endl;for (auto [tot, type] : ans) {print(tot, type);}return 0;
}

Problem D. The Emperor

有一点点的,额,暴力?(事实上这套题这种感觉的题很多

我们直接进行一个记忆化的搜 \(f(u, a) = (v, cnt)\) 表示栈顶为 \(a\) 的时候,进入第 \(u\) 条命令,在刚好弹出这个栈顶的时候会到达哪里并且执行了多少次。

……

然后限制一下每个状态在递归栈里面的数量,限制一下递归深度或者限制一下某个循环次数……然后就可以判定是否死循环了……额……

然后就结束了……

比较无语的一个题……

不过这毕竟是一个典型的停机问题,所以事实上简单一点、数据松一点很正常吧。

#include <cassert>
#include <iostream>
#include <random>
#include <utility>
#include <vector>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e6 + 100;
const int MOD = 998244353;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}int n;
int type[kMaxN];
int opt[4][kMaxN];
std::vector<int> stack;int inc(int x, int y) {return (x + y >= MOD ? x + y - MOD : x + y);
}int dec(int x, int y) {return (x - y < 0 ? x - y + MOD : x - y);
}int bf() {int ans = 0;for (int i = 1;;) {ans++;if (type[i] == 1) {if (stack.empty() || stack.back() != opt[0][i]) {stack.push_back(opt[2][i]);i = opt[3][i];} else {assert(stack.back() == opt[0][i]);stack.pop_back();i = opt[1][i];}} else {if (stack.empty()) break;stack.push_back(opt[0][i]);i = opt[1][i];}}return ans;
}struct node {int v, cnt;
} dp[1055][1055];int dep = 0;
bool vis[1055][1055];
int in[1055][1055];// 进入 u 语句,此时栈顶是 a
// 结束后进入 v 语句,且进入前会弹出 a
node dfs(int u, int a) {// 如果递归深度过高,或者进入这个 dfs 次数有点多if (vis[u][a]) return dp[u][a];if (in[u][a] > 8) {cout << -1 << endl;exit(0);}in[u][a]++;int now = u, sum = 0;// cerr << "DFS IN " << u << ' ' << a << endl;int cnt = 0;while (1) {cnt++;// if (cnt >= n * 4) {//   cerr << "BAD!" << endl;// }sum = inc(1, sum);if (type[now] == 2) {if (a == 0) {cout << sum << endl;exit(0);return {0, 0};} else {auto [v, cnt] = dfs(opt[1][now], opt[0][now]);now = v, sum = inc(sum, cnt);}} else {if (opt[0][now] == a) {now = opt[1][now];break;} else {auto [v, cnt] = dfs(opt[3][now], opt[2][now]);now = v, sum = inc(sum, cnt);}}if (cnt > n * 4) {cout << -1 << endl;exit(0);}}// cerr << "OUT WITH " << u << ' ' << a << endl;in[u][a]--;vis[u][a] = true;return dp[u][a] = {now, sum};
}int main() {std::ios::sync_with_stdio(false);cin.tie(nullptr), cout.tie(nullptr), cerr.tie(nullptr);cin >> n;for (int i = 1; i <= n; i++) {std::string opt;cin >> opt;if (opt == "HALT;") {type[i] = 2;for (int j = 0; j < 2; j++) {cin >> opt;cin >> ::opt[j][i];}} else {type[i] = 1;cin >> ::opt[0][i] >> opt >> ::opt[1][i] >> opt >> opt;cin >> ::opt[2][i] >> opt >> ::opt[3][i];}}// cout << bf() << endl;dfs(1, 0);return 0;
}

Problem E. The Chariot

我当时还琢磨为什么这套题为什么有人写 python,直到我看到了这个题……

我甚至还写了半个小时的高精度板子才发现这套题可以……交 pythonjava……

基于一个贪心思路:究竟怎么走单价会比较优秀。

  • 如果 \(A\) 的单价足够优秀,那么一定是走一万个 \(A\),最后留下一部分走 \(A\),或者交给 \(B\)\(C\) 消耗。

  • 如果 \(B\) 的单价足够优秀,那么最后坐车的状态一定类似:\(ABABABABABAB...\)

    • 最后会留下来一部分,这一部分如果选择坐 \(A\),那么有可能会浪费掉一部分行程。这个时候从前面的 \(B\) 中去掉一部分路程可以最小化浪费。
    • 或者最后这部分用 \(C\) 来收尾。
  • 如果 \(C\) 的单价足够优秀,那么最后的坐车状态就可能是:\(ABCCCCCCCCCCCCCC...\)

容易发现决策数量非常少,直接全部跑一次然后取 \(\min\) 即可。

我讨厌 python 这诡异的语法

t = int(input())def solve():a, b, c, x, y, d = map(int, input().split())# 尽量用 a,最后接一个 b 或者 ctmp1 = d // xd2 = d - tmp1 * xsum = tmp1 * aans = 0if d2 != 0:ans = sum + aif tmp1 != 0:# 还有一些没有分配出去# print(tmp1 * y, ans)# print(sum)if d2 <= tmp1 * y:# 全部考虑给 bans = min({ans, sum + d2 * b})if d2 >= y:ans = min({ans, sum + y * b + (d2 - y) * c})else:ans = sum# print(ans)# 一辆车tmp2 = 0if d <= x:tmp2 = aelif d <= x + y:tmp2 = a + (d - x) * belse:tmp2 = a + y * b + (d - x - y) * cans = min({ans, tmp2})# print("NOW END TO A PLUS B", ans)# 尽量用 a + bround = d // (x + y)least = d - round * (x + y)cost = round * (a + y * b)if round != 0:ableToReduce = round * yans = min({ans, cost + least * c})if least <= x:weast = x - leastreduce = 0if (weast <= ableToReduce):reduce = weastelse:reduce = ableToReduceans = min({ans, cost + a - reduce * b})else:ans = min({ans, cost + a + (least - x) * b})print(ans)while t:t -= 1solve()

python 的官方标准缩进是四格,不好改还是懒得动了。

Problem F. The Hermit

居然还有黑神话悟空

如果一个集合需要被删除数字,那么必须删掉最小值,否则不可能合法。

基于这个思路出发,我们发现判定一组合法数到底是哪些集合的子集是比较麻烦的,不管是组合数还是冗斥都很诡异。

直接考虑一个数会被删除多少次。

容易发现一个数 \(x\) 要是一定会被删除,那么在集合中,小于它的数字构成一个以 \(x\) 结尾的倍数链,大于 \(x\) 的数字一定是 \(x\) 的倍数。

这样我们可以枚举 \(x\) 然后统计它会属于多少一定会被删除的集合内。倍数链 \(dp\) 处理,\(x\) 的倍数组合数可以算出来。

然后做完了。

#include <iostream>
#include <random>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e5 + 100;
const int MOD = 998244353;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}int inc(int x, int y) {return (x + y >= MOD ? x + y - MOD : x + y);
}int dec(int x, int y) {return (x - y < 0 ? x - y + MOD : x - y);
}int mul(int x, int y) {return 1ll * x * y % MOD;
}int pow(int x, int p) {int ans = 1;while (p) {if (p & 1) ans = mul(ans, x);x = mul(x, x), p >>= 1;}return ans;
}int m, n;
int fact[kMaxN], invf[kMaxN];
// 倍数链长度为c,并且以i结尾的方案数量
int f[20][kMaxN];int C(int n, int r) {return mul(fact[n], mul(invf[r], invf[n - r]));
}int solve(int x) {int ans = 0;// 表示能够挑出 count 个倍数int count = m / x - 1;for (int c = 1; c < 20; c++) {if (f[c][x] == 0) continue;if (c + count < n) continue;ans = inc(ans, mul(f[c][x], C(count, n - c)));}return ans;
}int main() {std::ios::sync_with_stdio(false);cin.tie(nullptr), cout.tie(nullptr), cerr.tie(nullptr);cin >> m >> n;fact[0] = 1;for (int i = 1; i < kMaxN; i++) fact[i] = mul(fact[i - 1], i);invf[kMaxN - 1] = pow(fact[kMaxN - 1], MOD - 2);for (int i = kMaxN - 2; i >= 0; i--) invf[i] = mul(invf[i + 1], i + 1);for (int i = 1; i <= m; i++) {f[1][i] = 1;}for (int i = 1; i < 20; i++) {for (int j = 1; j <= m; j++) {if (f[i][j] == 0) continue;for (int k = 2; k * j <= m; k++) {f[i + 1][k * j] = inc(f[i + 1][k * j], f[i][j]);}}}// 考虑 i 会被删除多少次int sum = 0;for (int i = 1; i <= m; i++) {sum = inc(sum, solve(i));// cerr << i << ' ' << solve(i) << endl;}int ans = dec(mul(n, C(m, n)), sum);cout << ans << endl;return 0;
}

Problem G. The Wheel of Fortune

噩梦的开始啊。

总结题解感觉好像挺短的,但是本人确实不太会计算几何所以这道题写了很久(而且写了很长。

这个题的题解写的其实不怎么好。

首先算出来原多边形的质心,然后可以算出来加上小磁铁后新质心可能在的位置。新质心一定在原质心的一个相似多边形内部。然后我们会发现,某个区域获奖概率一定与这个区域与相似多边形的交的有关。

将旋转中心视作原点平移一下。

某个区域和相似多边形的交一定只由一个凸包决定,这个凸包由两部分构成:

  • 夹在射线中间的顶点
  • 某个边和射线的交点
  • 如果原点在相似多边形里面,那么这个凸包应当还有原点

将相似多边形的顶点和射线一起进行极角排序,就可以获得夹在两条射线中间的点集。每个点一定对应两条边,而这两条边一定只会和相邻不超过三四条的射线有交,所以直接枚举可能相交的射线然后添加交点到对应的凸包点集中就可以了。

获得凸包以后可以用 \(O(n)\) 的算法计算凸包面积,当然如果比较懒狗也可以直接获取顶点平均值后极角排序再算凸包面积(逃。

#include <iostream>
#include <cassert>
#include <algorithm>
#include <cmath>
#include <vector>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e5 + 100;
const double pi = std::acos(-1);
const double eps = 1e-7;struct node {double x, y;bool operator==(const node& p) const { return x == p.x && y == p.y; }bool operator<(const node& p) const {if (x == p.x) return y < p.y;return x < p.x;}
};
node operator+(const node& a, const node& b) {return {a.x + b.x, a.y + b.y};
}
node operator-(const node& a, const node& b) {return {a.x - b.x, a.y - b.y};
}
node operator*(const node& a, double p) {return {a.x * p, a.y * p};
}
node operator*(double p, const node& a) {return a * p;
}
double operator*(node a, node b) {return a.x * b.y - a.y * b.x;
}
bool leq(double x, double y) {if (std::abs(x - y) <= eps) return true;return x < y;
}
bool le(double x, double y) {if (std::abs(x - y) <= eps) return false;return x < y;
}
double angle(double x, double y) {auto a = atan2(y, x);if (le(a, 0)) a += 2 * pi;return a;
}
struct line {node a, b;line() {}line(const node& p1, const node& p2) { a = p1, b = p2 - p1; }
};
node cross(const line& a, const line& b) {if (a.b * b.b == 0) return {-1, -1};// p1 + A * ((P3 - P1) * B) / (AB)return a.a + a.b * (((b.a - a.a) * b.b) / (a.b * b.b));
}double crossT(const line& a, const line& b) {if (a.b * b.b == 0) return -1;return (((b.a - a.a) * b.b) / (a.b * b.b));
}int n;
double w;
std::vector<node> v, img;
std::vector<int> bel[kMaxN];std::vector<node> set[kMaxN];// 确认每个扇形的顶点集合
// std::vector<int> point[(int)(1e5 + 10)];double calc(std::vector<node>& p) {std::sort(p.begin(), p.end());p.erase(std::unique(p.begin(), p.end()), p.end());node mid = {0, 0};for (auto& i : p) {mid = mid + i;}// cerr << "NOW I WANNA CALC " << endl;// cerr << p.size() << endl;if (p.size() == 0) return 0;// for (auto& i : p) {//   cerr << i.x << ' ' << i.y << endl;// }mid = mid * (1.0 / p.size());std::sort(p.begin(), p.end(), [&](const node& a, const node& b) {return angle(a.x - mid.x, a.y - mid.y) < angle(b.x - mid.x, b.y - mid.y);});double ans = 0, tmp;for (auto lst = p.back(); auto now : p) {ans += (tmp = (lst - mid) * (now - mid));// cerr << tmp << endl;// assert(leq(0, tmp));lst = now;}ans /= 2;return ans;
}int main() {std::ios::sync_with_stdio(false);cin.tie(nullptr), cout.tie(nullptr), cerr.tie(nullptr);cin >> n >> w;v.resize(n), img.resize(n);for (auto& [x, y] : v) {cin >> x >> y;}int ox, oy;cin >> ox >> oy;for (auto& [x, y] : v) {x -= ox, y -= oy;// cerr << x << ' ' << y << endl;}double S = 0;node m = {0, 0};for (auto lst = v.back(); auto now : v) {S += lst * now;m.x += (lst.x + now.x) * (lst * now);m.y += (lst.y + now.y) * (lst * now);lst = now;}S /= 2;m = m * (1.0 / (6 * S));// cerr << "GET THE POINT " << m.x << ' ' << m.y << ' ' << S << endl;for (int i = 0; auto u : v) {img[i] = (m * S + u * w) * (1.0 / (S + w));// cerr << img[i].x << ' ' << img[i].y << endl;i++;}int cnt = 0;for (auto lst = img.back(); auto now : img) {auto t = cross(line(lst, now), line({0, 0}, {0, -1}));auto [x1, y1] = lst;auto [x2, y2] = now;if (!leq(x1, x2)) std::swap(x1, x2);if (leq(x1, 0) && le(0, x2) && leq(t.y, 0)) {cnt ^= 1;}lst = now;}// cerr << cnt << endl;// 进行一遍极角排序,把射线也丢进去就可以直到两条射线中间夹的顶点std::vector<std::pair<node, int>> v;v.reserve(2 * n);for (int i = 0; i < n; i++) {v.push_back({img[i], i});}// 将射线也放进去for (int i = 0; i < n; i++) {v.push_back({::v[i], -i - 1});}std::sort(v.begin(), v.end(),[](const auto& a, const auto& b) { return angle(a.first.x, a.first.y) < angle(b.first.x, b.first.y); });auto nxt = [](int x) { return x + 1 >= 2 * n ? 0 : x + 1; };for (int i = 0, j, cnt = 0; i < 2 * n;) {if (v[i].second >= 0) {i = nxt(i);continue;}cnt++;for (j = nxt(i); v[j].second >= 0; j = nxt(j)) {bel[-v[i].second - 1].push_back(v[j].second);// cerr << "YOU ADD A POINT " << v[j].second << " TO " << -v[i].second - 1 << endl;}i = j;if (cnt == n) break;}// cerr << "NOW END" << endl;// 考虑交点for (int i = 0; i < n; i++) {// 考虑每个扇形内的点,每个点会对应两条线,然后这两条线一定只会和相邻有限的扇形有交点if (cnt) {set[i].push_back({0, 0});}// cerr << "NOW INIT LINE " << i << endl;for (auto id : bel[i]) {int pre = id - 1, nxt = id + 1;if (pre < 0) pre = n - 1;if (nxt == n) nxt = 0;set[i].push_back(img[id]);for (line l : {line(::img[nxt], ::img[id]), line(::img[pre], ::img[id])}) {auto x1 = l.a.x, x2 = l.a.x + l.b.x;auto y1 = l.a.y, y2 = l.a.y + l.b.y;auto begin = l.a, end = l.a + l.b;if (!leq(x1, x2)) std::swap(x1, x2);if (!leq(y1, y2)) std::swap(y1, y2);for (int j = i + 1, lst = i, cnt = 1; cnt <= n; j++, cnt++) {if (j == n) j = 0;auto t = cross(line({0, 0}, ::v[j]), l);auto v = crossT(line({0, 0}, ::v[j]), l);if (leq(x1, t.x) && leq(t.x, x2) && leq(y1, t.y) && leq(t.y, y2) && leq(0, v)) {set[lst].push_back(t);set[j].push_back(t);// cerr << "CROSS " << ::v[j].x << ' ' << ::v[j].y << endl;// cerr << t.x << ' ' << t.y << endl;// cerr << v << endl;} else {break;}lst = j;}for (int j = i, nxt = i - 1, cnt = 1; cnt <= n; nxt--, cnt++) {if (nxt == -1) nxt = n - 1;auto t = cross(line({0, 0}, ::v[j]), l);auto v = crossT(line({0, 0}, ::v[j]), l);if (leq(x1, t.x) && leq(t.x, x2) && leq(y1, t.y) && leq(t.y, y2) && leq(0, v)) {set[nxt].push_back(t);set[j].push_back(t);} else {break;}j = nxt;}}}}double sum = 0;std::vector<double> ans(n);for (int i = 0; i < n; i++) {ans[i] = calc(set[i]);sum += ans[i];}for (int i = 0; i < n; i++) {printf("%.9lf\n", ans[i] / sum);// cout << (ans[i] / sum) << endl;}return 0;
}

Problem H. Strength

挺简单的一个 \(dp\) 题目。但是状态设计要比较小心。

由于进位只会影响下一位,所以阶段划分是比较明显的。状态设计:\(dp_{i, j, k}\) 为考虑完末尾 \(i\) 位,同时数字是否小于等于 \(z\),第三维比较复杂:

  • \(k=0\),不会进位
  • \(k=1\),可以通过操作是否会对下一位进位
  • \(k=2\),一定会对下一位进位

考虑一下 \(4\) 这个比较抽象的角色,你会发现上一位是否进位、或者这一位先舍掉了,都会导致有可能进位也有可能不进位。所以要但开一个维度来应付这个抽象的东西(

状态转移的时候,记 \(x\) 当前位为 \(t\),只有 \(t=0\) 或者 \(t=1\) 的时候转移会相对诡异一点,只有这两种情况需要考虑是否会进位。其余情况比较平凡。枚举一下当前位的数然后考虑转移就可以了。

多转移几位然后答案就是 \(dp_{19, 1, 0}\) 了(。

#include <cassert>
#include <iostream>
#include <cstring>
#include <random>
#define int long longusing std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e6 + 100;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}long long dp[20][2][3];
long long p[20];int t;
long long x, z;void solve() {cin >> x >> z;cerr << x << ' ' << z << endl;dp[0][1][0] = 1;for (int i = 1; i <= 19; i++) {memset(dp[i], 0, sizeof(dp[i]));// 提取z的最后一位int zlst = z / p[i - 1], xlst = x / p[i - 1];zlst %= 10, xlst %= 10;// 枚举当前位置的原始数字for (int j = 0; j <= 1; j++) {for (int k = 0; k <= 2; k++) {for (int s = 0; s <= 9; s++) {bool leq = (s < zlst) | ((s == zlst) & j);if (xlst == 0) {if (k == 0) {dp[i][leq][s >= 5 ? 2 : 0] += dp[i - 1][j][k];} else if (k == 1 || k == 2) {if (s <= 3) dp[i][leq][0] += dp[i - 1][j][k];if (s == 4) dp[i][leq][1] += dp[i - 1][j][k];if (5 <= s) dp[i][leq][2] += dp[i - 1][j][k];}} else if (xlst == 1) {if (k == 0) {if (s != 1) continue;dp[i][leq][0] += dp[i - 1][j][k];} else if (k == 1 || k == 2) {if (s <= 4) dp[i][leq][0] += dp[i - 1][j][k];if (5 <= s) dp[i][leq][2] += dp[i - 1][j][k];}} else {if (k == 0 && s != xlst) continue;if (k == 1 && (s != xlst && s + 1 != xlst)) continue;if (k == 2 && s + 1 != xlst) continue;dp[i][leq][0] += dp[i - 1][j][k];}}}}}cout << dp[19][1][0] << endl;
}signed main() {cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);p[0] = 1;for (int i = 1; i <= 18; i++) p[i] = p[i - 1] * 10;cin >> t;while (t--) solve();return 0;
}

Problem I. The Hanged Man

才发现我这个题是稀里糊涂做对的……

写题解的时候才发现思路有点问题。

容易发现无刺图的一个必要条件是存在欧拉回路,也就是所有节点度数为偶数。所以可以先用这个性质判掉一些不合法情况。注意不是充要条件,一条边只能在一个简单环而且无重边自环。

当存在奇数个奇度点的时候,一定无法重新连边凑出欧拉回路,判掉。

题解给了一个动态规划的思路:

现在一定只剩下偶数个奇度点了。题目就变成了:是否可以在树上选取若干不重合链,且链的两段必须是奇度点。

\(dp\) 状态也很好设计,直接设计 \(dp_{u, 0/1}\) 表示 \(u\) 的子树中,是否以 \(u\) 为链的一段是否可行。若 \(dp_{root, 0}\)\(true\) 说明存在解。

但是这么 \(dp\) 要记录转移,导致逆推答案的时候会很复杂。

考虑构造。无论你如何操作,非根子树一定会留下来一个奇度点。

  • 考虑某个只有叶子节点的子树 \(u\),那么无论如何操作都一定剩下一个奇度点。对于 \(u\) 的父亲来说 \(u\) 和一个叶子没啥区别了。
  • 然后将这个步骤不断向上传递,就可以得到所有非根子树构造后一定留下来一个奇度点。

然后你将这个构造向上传递会发现,只有存在某个点度数为偶数的时候才有可能有解。找到那个节点然后当根,遍历一遍随便连连,这题就做完了。

#include <cassert>
#include <iostream>
#include <random>
#include <vector>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e6 + 100;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}// 合法的必要条件是所有点度数都是偶数
// 也就是是否存在欧拉回路std::vector<int> go[kMaxN];
std::vector<std::pair<int, int>> ans;
int cnt = 0;int dfs(int u, int fa) {int lst = 0;for (auto v : go[u]) {if (v == fa) continue;auto now = dfs(v, u);assert(now);if (lst == 0) {lst = now;} else {ans.push_back({lst, now});lst = 0;}}if (lst == 0) {assert(fa == 0 || (go[u].size() & 1));lst = u;}return lst;
}void solve() {int n;cin >> n;for (int i = 1; i <= n; i++) {go[i].clear();}for (int i = 1; i <= n - 1; i++) {int u, v;cin >> u >> v;go[u].push_back(v), go[v].push_back(u);}cnt = 0;ans.clear();for (int i = 1, flag = false; i <= n; i++) {if (go[i].size() & 1) {cnt++;} else if (!flag) {dfs(i, 0);flag = true;}}if (cnt & 1) return cout << -1 << endl, void();if (cnt == n) return cout << -1 << endl, void();cout << ans.size() << endl;for (auto [u, v] : ans) {cout << u << ' ' << v << endl;}// 任何节点不能和fa连边
}int main() {cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);int t;cin >> t;while (t--) solve();return 0;
}

Problem J. Temperance

反套路的简单题。我居然一开始没想到怎么做。

假设拔掉所有密度小于 \(d\) 的植物,会发现密度大于等于 \(d\) 的植物一定不会被因为这些被拔掉的植物影响。

想到这里就做出来了。

#include <iostream>
#include <algorithm>
#include <map>
#include <random>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e6 + 100;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}int t, n;
int a[kMaxN][3];void solve() {std::map<int, int> cnt[3];cin >> n;std::vector<int> d(n + 1);for (int i = 1; i <= n; i++) {for (int j = 0; j < 3; j++) cin >> a[i][j], cnt[j][a[i][j]]++;}for (int i = 1; i <= n; i++) {for (int j = 0; j < 3; j++) upmax(d[i], cnt[j][a[i][j]] - 1);// cerr << d[i] << endl;}std::sort(d.begin() + 1, d.end());int j = 1;for (int k = 0; k < n; k++) {while (j <= n && d[j] < k) j++;cout << j - 1 << ' ';}cout << endl;
}int main() {cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);cin >> t;while (t--) solve();return 0;
}

Problem K. The Devil

比较难绷的一个题。

首先要想到二分图匹配:每个缩写对应若干缩写,但是这些缩写可能有重合的。这个行为相当于一个二分图最小权匹配。你写匈牙利写费用流都是可以的。(但是我只会写费用流)

对于每个名字我们只需要保留长度最小的 \(n\) 个缩写。缩写和其他字符串最多重合 \(n-1\) 个,所以保留前 \(n\) 短的就可以。

接下来考虑如何获取每个名字的前 \(n\) 短的字符串。这个过程要求做到 \(O(n^3)\),但是正确的 \(trie\) 树实现可以做到 \(O(n^2)\)

  • 一个名字可以被分割成若干字符串 \(s_1, s_2...\),以此遍历每个字符串。同时遍历过程中仅保留最短的 \(n\) 个缩写,然后将 \(s_i\) 添加到缩写考虑范围内。

  • 在扩展新缩写过程中,一个新缩写只有两种可能出现:由上轮的缩写添加 \(s_{i, 0}\) 获得,或者已经拥有的缩写中向后扩展一位。这个过程可以同步维护一个队列和一个指针来进行较优秀的构造,能够做到 \(O(n)\)。如果比较麻烦也可以用一个优先队列来维护,能够做到 \(O(n \log n)\)

也就是说正确的缩写获取其实可以做到 \(O(n^3)\),而且空间复杂度大概也是这个数量级。

在这个二分图中,左边的点至多 \(O(n)\),右边的点至多 \(O(n^2)\),边数至多 \(O(n^2)\)。写一个匈牙利大概复杂度是 \(O(n^4)\)。由于二分图的性质比较好,正确的 \(dinic\) 费用流应该可以做到近似 \(O(n^3)\)也就是说这个题还能把数据往上面提

等什么时候有时间把 \(n\) 开到\(500\)去恶心别人

#include <cassert>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <random>
#include <vector>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 3e6 + 100;
const int N = 128;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}int n;int tot = 0;
std::map<char, int> trie[kMaxN];
int len[kMaxN], lst[kMaxN];
char ch[kMaxN];
bool count[kMaxN];int nxt(int p, char ch) {if (trie[p].count(ch)) return trie[p][ch];assert(tot < kMaxN);len[++tot] = len[p] + 1;lst[tot] = p;::ch[tot] = ch;return trie[p][ch] = tot;
}std::vector<int> insert(const std::string& str) {std::vector<std::string> list(1, "");for (auto& ch : str) {if (ch == ' ')list.push_back("");elselist.back().push_back(ch);}while (list.back().empty()) list.pop_back();// trie节点std::vector<int> node;node.push_back(0);for (auto& str : list) {// cerr << "YES BEGIN " << str.size() << endl;// 已经插入了str的前i个字符后的节点位置std::queue<std::pair<int, int>> q;std::vector<int> tmp;// cerr << "BEGIN WITH " << str.size() << endl;// cerr << node.size() << endl;auto record = [&](int p, int j) {p = nxt(p, str[j]);if (!count[p]) {count[p] = true, tmp.push_back(p);if (j + 1 == str.size()) return;q.push({p, j + 1});}};for (int j = 0; tmp.size() <= n && (j < node.size() || !q.empty());) {if (j == node.size()) {record(q.front().first, q.front().second), q.pop();} else if (q.empty()) {record(node[j], 0), j++;} else {if (len[node[j]] < len[q.front().first]) {record(node[j], 0), j++;} else {record(q.front().first, q.front().second), q.pop();}}}node.swap(tmp);for (int i = 0; i < node.size() - 1; i++) {assert(len[node[i]] <= len[node[i + 1]]);}std::sort(node.begin(), node.end(), [](int i, int j) { return len[i] < len[j]; });for (auto& i : node) count[i] = false;}return node;
}void print(int p) {if (p == 0) return;print(lst[p]), cout << ch[p];
}// MCMF 板子,由于要做二分图匹配,这个时候用费用流
int tot2;
int tonode[kMaxN];
int remap[kMaxN];
std::vector<int> node[kMaxN];
int s, t;struct edge {int u, v, cap, cst, nxt;
} e[kMaxN];int etot = 1;
int dis[kMaxN];
int head[kMaxN], cur[kMaxN];
bool vis[kMaxN], kill[kMaxN];void add(int u, int v, int cap, int cst) {auto _ = [](int u, int v, int cap, int cst) {e[++etot] = {u, v, cap, cst, head[u]};head[u] = etot;};_(u, v, cap, cst), _(v, u, 0, -cst);
}bool SPFA() {for (int i = 1; i <= t; i++) {cur[i] = head[i], vis[i] = false, kill[i] = false, dis[i] = 1e9;}std::queue<int> q;auto record = [&](int v, int d) {if (d >= dis[v]) return;dis[v] = d;if (!vis[v]) vis[v] = true, q.push(v);};record(s, 0);while (!q.empty()) {int f = q.front();q.pop(), vis[f] = false;for (int i = head[f]; i; i = e[i].nxt) {if (e[i].cap) record(e[i].v, e[i].cst + dis[f]);}}return dis[t] < 1e9;
}int all = 0;
int dinic(int u, int in) {if (u == t) return in;vis[u] = true;int out = 0;for (int& i = cur[u]; i; i = e[i].nxt) {if (int v = e[i].v; !vis[v] && !kill[v] && e[i].cap && e[i].cst + dis[u] == dis[v]) {int res = dinic(v, std::min(in, e[i].cap));in -= res, out += res;e[i].cap -= res, e[i ^ 1].cap += res;all += res * e[i].cst;if (in == 0) break;}}vis[u] = false;if (out == 0) kill[u] = true;return out;
}int dinic() {int out = 0;while (SPFA()) {while (int t = dinic(s, 1e9)) out += t;}return out;
}int main() {cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);cin >> n, cin.get();tot2 = n;for (int i = 1; i <= n; i++) {std::string str;std::getline(cin, str);// cerr << "YOU GET " << str << endl;node[i] = insert(str);for (auto& v : node[i]) {if (tonode[v] == 0) tonode[v] = ++tot2, remap[tot2] = v;}}int p = 0;for (int i = 1; i <= 128; i++) p = nxt(p, 'z');s = ++tot2, t = ++tot2;for (int i = 1; i <= n; i++) {add(s, i, 1, 0);for (auto v : node[i]) {add(i, tonode[v], 1, 0);// print(v);// nerr << v << ' ' << tonode[v] << endl;}}for (int i = n + 1; i < s; i++) add(i, t, 1, len[remap[i]]);// cerr << "HAS " << s - n - 1 << endl;int t = dinic();if (t != n) {return cout << "no solution" << endl, 0;}for (int i = 1; i <= n; i++) {for (int j = head[i]; j; j = e[j].nxt) {if (int v = e[j].v; n < v && v < s && e[j].cap == 0) {// cerr << "YES " << i << " LINK TO " << remap[v] << endl;print(remap[v]), cout << endl;}}}return 0;
}

Problem L. The Towerr

我讨厌这个题

很无聊的一个,线段树分裂合并板子。

而我更是弱智,直接对着一个范围 \(10^9\) 的线段树进行一个动态开点,然后再往这个动态开点的线段树上面怼线段树分裂……后果就是似的不知道是什么样子了。

我们只关心被询问的那些节点的情况,超过最后一个被询问节点的所有数值其实都不再关心了。然后这个题又允许我们做离线处理,所以直接将被查询节点进行离散化处理,然后对着这坨离散化后的数组建树,这样就避免了又是动态开点又是线段树分裂的悲惨状况(。

每一个操作一就相当于把线段树的一个前缀撕了下来开一个新的线段树,操作二就相当于把这个撕下来的线段树粘回去了。

同时我们保存一个指针(其实保存数组下标也彳亍)来记录被查询点最后落到的叶子节点,同时记录一下每个线段树的根对应哪个用户。这样从叶子节点不断上跳到根就可以得知这个叶子在哪个线段树了和哪个用户了。

……嗯做完了。就很暴力。因为操作二最多撕下来 \(\log^2 n\) (还是 \(\log n\))这个级别的节点数量,合并的复杂度也差不多是这样,所以合并和分裂复杂度是正确的。

唯一的问题就是空间问题,为了防止炸空间不能够往节点里面存左右界了,而且也不能用指针来表示儿子。回收节点是无法优化空间复杂度的。

#include <algorithm>
#include <iostream>
#include <random>
#include <vector>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 1e6 + 100;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}struct node {int size, fa;int son[2];node() { size = fa = son[0] = son[1] = 0; }
} nd[(int)(2e7)];int tot;
int head[kMaxN];
int a[kMaxN];
std::vector<int> v;
int map[kMaxN];
int n, m;
int s1[kMaxN], s2[kMaxN];void pushup(int p) {nd[p].size = nd[nd[p].son[0]].size + nd[nd[p].son[1]].size;nd[nd[p].son[0]].fa = nd[nd[p].son[1]].fa = p;
}void build(int& p, int l, int r) {p = ++tot;if (l == r) {nd[p].size = a[l] - a[l - 1], map[l] = p;return;}int mid = (l + r) >> 1;build(nd[p].son[0], l, mid), build(nd[p].son[1], mid + 1, r);pushup(p);
}void split(int& p, int q, int l, int r, int k) {if (k == 0) return;p = ++tot;if (nd[q].size <= k) {nd[p] = nd[q], nd[q] = node();nd[nd[p].son[0]].fa = nd[nd[p].son[1]].fa = p;return;}nd[p].size += k, nd[q].size -= k;if (l == r) return;int mid = (l + r) >> 1;if (int s = nd[nd[q].son[0]].size; s <= k) {nd[p].son[0] = nd[q].son[0], nd[q].son[0] = 0;split(nd[p].son[1], nd[q].son[1], mid + 1, r, k - s);} else {split(nd[p].son[0], nd[q].son[0], l, mid, k);}pushup(p);
}void merge(int& p, int q, int l, int r) {if (p == 0 || q == 0) {p += q;return;}if (l == r) {nd[p].size += nd[q].size;return;}int mid = (l + r) >> 1;merge(nd[p].son[0], nd[q].son[0], l, mid);merge(nd[p].son[1], nd[q].son[1], mid + 1, r);pushup(p), nd[q] = node();
}int usr[(int)(2e7)];int main() {cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);cin >> n;for (int i = 1; i <= n; i++) {cin >> s1[i] >> s2[i];if (s1[i] == 3) {v.push_back(s2[i]);}}if (v.empty()) return 0;std::sort(v.begin(), v.end());v.erase(std::unique(v.begin(), v.end()), v.end());for (int i = 0; i < v.size(); i++) {if ((i == 0 ? 1 : v[i - 1] + 1) < v[i]) a[++m] = v[i] - 1;a[++m] = v[i];}build(head[0], 1, m);int cnt = 0;for (int i = 1; i <= n; i++) {if (s1[i] == 1) {cnt++;split(head[cnt], head[0], 1, m, s2[i]);usr[head[cnt]] = cnt;} else if (s1[i] == 2) {merge(head[0], head[s2[i]], 1, m);} else {int x = std::lower_bound(a + 1, a + 1 + m, s2[i]) - a;x = map[x];while (nd[x].fa) x = nd[x].fa;cout << usr[x] << endl;}}return 0;
}

主要问题还是不会线段树分裂和线段树合并(

Problem M. Judgement

有点小逆天的一个思维题。代码不难实现。

这个题比较建议看官方题解,写到要好很多。

大本营不应当视作和旁边的联通块相连,因为任何一个颜色都无法覆盖大本营地。

非法只有四种情况:

  • 不联通
  • 某个颜色无法通过联通块到达自己的大本营
  • 当所有颜色刚好构成一个链的时候,颜色变化过多。也就是类似:
    RSRBTB 合法,但是 RSRRBRBBT 不合法。
  • 对于某一个独立的联通块,它刚好构成红蓝相间的情况。

非常诡异,非常非常非常诡异。如果考场上看到这个题我是写不出的。

至少我现在还不会证明这些性质

#include <iostream>
#include <queue>
#include <random>using std::cerr;
using std::cin;
using std::cout;const char endl = '\n';
const int kMaxN = 505;int rand(int l, int r) {static std::random_device seed;static std::mt19937_64 e(seed());std::uniform_int_distribution<int> rd(l, r);return rd(e);
}template <class type>
void upmin(type& a, const type& b) {a = std::min(a, b);
}template <class type>
void upmax(type& a, const type& b) {a = std::max(a, b);
}int t;
int a[kMaxN][kMaxN];
int vis[kMaxN][kMaxN];
int n, m;
bool used[kMaxN][kMaxN];
bool spe[kMaxN][kMaxN];int rx, ry, bx, by;
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};void col(int x, int y, int k) {spe[x][y] = true;std::queue<std::pair<int, int>> q;int cnt = 0;auto record = [&](int x, int y) {if (x < 1 || x > n || y < 1 || y > m || vis[x][y] & k || a[x][y] == 0 || spe[x][y]) return;q.push({x, y});vis[x][y] |= k;};q.push({x, y});vis[x][y] |= k;while (!q.empty()) {auto [x, y] = q.front();q.pop();for (int i : {-1, 1}) {record(x + i, y), record(x, y + i);}}
}bool isList = true;
int change = 0;
bool dfs(int x, int y) {bool flag = false;used[x][y] = true;int deg = 0;for (int i = 0; i < 4; i++) {int tx = dx[i] + x, ty = dy[i] + y;if (tx < 1 || tx > n || ty < 1 || ty > m) continue;if (a[tx][ty]) deg++;flag |= a[tx][ty] == a[x][y];if (used[tx][ty] || a[tx][ty] == 0) continue;change += a[tx][ty] != a[x][y];if (spe[tx][ty]) {continue;}flag |= dfs(tx, ty);}isList &= deg <= 2;return flag;
}bool solve() {static int cas = 0;cin >> n >> m;cin >> rx >> ry >> bx >> by;for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {char ch;cin >> ch;a[i][j] = ch == 'R' ? 1 : 2;if (ch == '.') a[i][j] = 0;spe[i][j] = used[i][j] = vis[i][j] = 0;}}if (a[rx][ry] != 1 || a[bx][by] != 2) return false;spe[rx][ry] = spe[bx][by] = true;col(rx, ry, 1), col(bx, by, 2);for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {if (a[i][j] != 0 && (vis[i][j] & a[i][j]) == 0) return false;}}bool ans = true;for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {if (a[i][j] == 0 || used[i][j] || spe[i][j]) continue;isList = true;change = 0;auto flag = dfs(i, j);cerr << "CHECK IN " << i << ' ' << j << endl;cerr << isList << endl;cerr << flag << endl;if (isList) {ans &= change <= 1;} else {ans &= flag;}}}return ans;
}int main() {cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);cin >> t;while (t--) {cout << (solve() ? "YES" : "NO") << endl;}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/959978.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LLM 训练基础概念与流程简介

1. LLM 训练基础概念 1.1 预训练(Pretrain) LLM 首先要学习的并非直接与人交流,而是让网络参数中充满知识的墨水,“墨水” 理论上喝的越饱越好,产生大量的对世界的知识积累。 预训练就是让 Model 先埋头苦学大量基…

完整教程:Suppr超能文献的zotero插件-github项目的介绍

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

TensorRT 和 ONNX Runtime 推理优化实战:10 个降低延迟的工程技巧

模型速度的瓶颈往往不在算法本身。几毫秒的优化累积起来就能让用户感受到明显的性能提升。下面这些技术都是在生产环境跑出来的经验,不需要重构代码实施起来也相对简单并且效果显著。https://avoid.overfit.cn/post/4…

csp-j/s历险记

csp-j/s比赛一直是一个十分SB优质的比赛 -在那个晴空万里的早上,与同学们共同坐地铁前往CSP-J考场-ye————————————————————————————————! -从从容容游刃有余 ——第一题—— AC! —…

深信服AC1700

备份软件下载地址:深信服技术支持-工具专区 恢复出厂设置方法:深信服社区-专业、开放、共享 升级包下载地址:行为管理AC-深信服技术支持当前升级包版本为Sangfor-AC-13.0.120共有5个app 开始升级:正在检测软件升级…

2025年FFS重膜包装机厂家综合实力排行榜TOP5

文章摘要 随着包装行业智能化转型加速,FFS重膜包装机市场呈现爆发式增长。2025年行业数据显示,全自动重袋包装设备需求同比增长32%,其中技术领先企业占据70%市场份额。本文基于权威数据和技术参数评测,为您呈现当前…

2025年重袋包装机品牌排行榜:十大实力厂家综合评测

文章摘要 2025年重袋包装机行业迎来智能化升级浪潮,本文基于技术实力、市场口碑和服务能力综合评测十大重袋包装机品牌,为行业用户提供权威选购参考。其中合肥徽达智能装备有限公司凭借全自动技术创新和一站式解决方…

2025年国内重袋包装机厂家权威推荐榜单

摘要 随着工业4.0智能化转型加速,重袋包装机行业迎来技术革新浪潮。2025年国内重袋包装机制造企业在技术创新、服务体系和产品质量方面呈现全面提升态势,本文基于市场调研数据、用户口碑评价和技术参数对比,为您呈现…

2025年国内重袋包装机品牌推荐榜单

摘要 重袋包装机行业在2025年迎来智能化转型高峰,随着制造业升级,全自动设备需求激增。本文基于市场调研和用户口碑,整理出2025年国内重袋包装机品牌前十排名,为采购商提供参考。榜单结合技术参数、客户案例和行业…

解析ui-setupUi(this)

ui->setupUi(this)是由.ui文件生成的类的构造函数,这个函数的作用是对界面进行初始化,它按照我们在Qt设计器里设计的样子把窗体画出来,把我们在Qt设计器里面定义的信号和槽建立起来。

深入解析:哺乳动物双等位基因表达的 “守护者”--解析 MSL2对基因剂量平衡与疾病机制的新启示--文献精读164

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

软考完结篇

今天终于考完软考了。下午一点入场,六点出来的,我还提前出来半个小时呢。 这考场压迫感太强了,即使我这从小考到大的也有点招架不住。 一坐坐5个小时,谁受得了。 考试和预期中差不多。有一些新的没见过的题,做不出…

深度学习优化算法深入分析:从 SGD 到 LAMB - 指南

深度学习优化算法深入分析:从 SGD 到 LAMB - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

记录一些生活。

最近常常在想,假如我有一个从小一起玩耍的异性伙伴,我的恋爱观会不会被改变。 假如我也有一个不会发展成恋爱关系的异性朋友,感情的道路上是不是会比较顺利? 但是男女间真的有纯友谊吗?《我可能不会爱你》早就给过…

2025大厂高频软件测试面试真题(附答案)

​ 一、接口测试面试题: 1.接口测试是怎么做的,如何分析数据? 接口测试实际跟一般测试不同就是测试用例的设计部分。 获取接口规范。 设计接口测试功能用例(主要从用户角度出发看接口能否实现业务需求,用例设计就…

visio绘制带公式图片作为latex插图

一般形式图片想要插入latex非常简单,只要保存为.svg形式,然后再用Inkscape转为.eps格式就可以插入latex中了 但是我们有时画的图片中会有公式,比如在visio中画的图片中会有mathtype编辑的公式,如果这时直接将这个带…

Jenkins Pipeline post指令详解 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

10.【Linux体系编程】缓冲区详解——库缓冲区 VS 内核缓冲区

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

102302106-陈昭颖-第2次作业

•作业①: 1.爬取中国气象网的七日天气预报 要求:在中国气象网(http://www.weather.com.cn)给定城市集的7日天气预报,并保存在数据库。核心代码 def city_weather(city):url = "http://www.weather.com.cn/we…

训练资源大合集

Public NOIP Round #8 Public NOIP Round #7 Public NOIP Round #6 Public NOIP Round #5 Public NOIP Round #4 Public NOIP Round #3 Public NOIP Round #2 Public NOIP Round #1