2025 XCPC 浙江省赛 FLM
F. Challenge NPC III
多源最短路、次短路。
对同种颜色跑BFS,过程中维护每条路的起点,由于同种颜色自己到自己就是最短,所以最后判断次短路是否小于 \(k\) 即可。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n, m, k;
cin >> n >> m >> k;
vector c(50, vector<int>());
for (int i = 0; i < n; i += 1) {
int x;
cin >> x;
c[--x].push_back(i);
}
vector e(n, vector<int>());
for (int i = 0; i < m; i += 1) {
int u, v;
cin >> u >> v;
u -= 1, v -= 1;
e[u].push_back(v);
}
const int inf = 1 << 30;
for (int i = 0; i < 50; i += 1) {
if (c[i].size() <= 1) {
continue;
}
vector<array<int,2>> dis(n, {inf, inf}), vis(n, {-1,-1});
queue<int> Q;
for (auto &u : c[i]) {
dis[u] = {0, inf};
vis[u] = {u, -1};
Q.push(u);
}
while (Q.size()) {
auto u = Q.front();
Q.pop();
for (auto &v : e[u]) {
bool f = 0;
for(int i : {0, 1}){
for(int j : {0, 1}){
if(vis[u][i] == vis[v][j] || vis[u][i] < 0) continue;
if(dis[u][i] + 1 < dis[v][j] && vis[v][j ^ 1] != vis[u][i]){
dis[v][j] = dis[u][i] + 1;
vis[v][j] = vis[u][i];
f = 1;
}
}
}
if(f){
Q.push(v);
}
}
}
for (auto &u : c[i]) {
if (dis[u][1] < k) {
cout << "NO\n";
return;
}
}
}
cout << "YES\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
L. Nailoongs Always Lie
基环树 \(dp\)。
\(n\) 个点,\(n\) 条边,那么一定会形成环,所以这是一个基环树森林,将环上一条边断开,就是两次树上 \(dp\),然后对多个联通块的最大值取和即可。
如果 \(u\) 是奶龙,那么 \(v\) 一定不是奶龙;如果 \(u\) 不是奶龙,那么 \(v\) 可能是也可能不是。
设 \(dp_{u,0/1}\) 表示 \(u\) 是不是奶龙时的最大答案,则有转移方程:
\[dp_{u,1}=\sum_{v \in Sub_u}dp_{v,0}
\]
\[dp_{u,0}=\sum_{v \in Sub_u}\max(dp_{v,0},dp_{v,1})
\]
注意断边时的 \(dp\) 值处理。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector e(n + 1, vector<int>());
auto fe = e;
for (int i = 1; i <= n; i += 1) {
int x;
cin >> x;
e[i].push_back(x);
fe[x].push_back(i);
}
int mark = 0;
vector<int> vis(n + 1);
vector<array<int,2>> dp(n + 1);
auto calc = [&](auto &&self, int u)->int{
vis[u] = 1;
dp[u] = {0, 1};
for (auto &v : fe[u]) {
if (v == mark) {
dp[v][1] = -1E9;
continue;
}
self(self, v);
dp[u][1] += dp[v][0];
dp[u][0] += max(dp[v][1], dp[v][0]);
}
return max(dp[u][0], dp[u][1]);
};
auto dfs = [&](auto &&self, int u)->void{
vis[u] = 1;
if (e[u].empty()) {
return;
}
if (vis[e[u][0]]) {
mark = u;
}
else{
self(self, e[u][0]);
}
};
int ans = 0;
for (int i = 1; i <= n; i += 1) {
if (vis[i]) {
continue;
}
dfs(dfs, i);
if (mark == 0) {
continue;
}
int tmp = calc(calc, mark);
mark = e[mark][0];
tmp = max(tmp, calc(calc, mark));
ans += tmp;
}
cout << ans << "\n";
return 0;
}
M. Master of Both VII
思维。
考虑询问所有 \(3 \leq i \leq n-1\) 的 \((1,i)\),结果为 \(d_i\)。对于 \(d_i = 0\) 直接加入答案。
对于一条边 \((x,y)\),其会对 \(x < i < y\) 的询问有 1 的贡献。
由于这些边构成了一些不会在非端点处相交的区间,按照 \(2 \leq i \leq n\) 的顺序维护一个栈。
- 如果 \(d_i > d_{i-1}\),那么入栈 \(d_i - d_{i-1}\) 个 \(i-1\)。
- 如果 \(d_i < d_{i-1}\),那么弹出 \(d_{i-1} - d_i\) 个栈顶,构成边 \((st_{top}, i)\)。
由于不会同时存在 \(i-1\) 开始的区间与 \(i\) 结束的区间,可以证明这个做法的正确性。
时间复杂度 \(O(n)\)。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
vector<array<int,2>> edge;
auto ask = [](int l, int r)->int{
cout << "? " << l << " " << r << endl;
int res;
cin >> res;
return res;
};
auto answer = [&]()->void{
cout << "! ";
for (auto &[x, y] : edge) {
cout << x << " " << y << " ";
}
cout << endl;
};
stack<int> st;
int lst = 0;
for (int i = 3; i <= n; i += 1) {
int x = 0;
if (i < n) {
x = ask(1, i);
}
if (x == 0 && i < n) {
edge.push_back({1, i});
while(!st.empty()){
edge.push_back({st.top(), i});
st.pop();
}
lst = x;
continue;
}
if (x > lst) {
for (int j = 0; j < x - lst; j += 1) {
st.push(i - 1);
}
}
else if (x < lst) {
for (int j = 0; j < lst - x; j += 1) {
edge.push_back({st.top(), i});
st.pop();
}
}
lst = x;
}
answer();
cin >> n;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号