- 寫在前面
- A
- B
- C
- D
- E
- F
- 寫在最後
寫在前面
比賽地址:https://codeforces.com/contest/1991
離上紫一步之遙了這場打完又掉下去了哈哈真是太搞
A
簽到。
對於每個位置檢查兩側數的數量是否為奇數,若為奇數則無法刪的只剩這個位置。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int a[1000];
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
int n; std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
int ans = 0;
for (int i = 1; i <= n; ++ i) {
if ((i - 1) % 2 == 1 || (n - i) % 2 == 1) continue;
ans = std::max(ans, a[i]);
}
std::cout << ans << "\n";
}
return 0;
}
B
構造。
考慮到 AND 運算等價於取兩個運算元二進位制位的公共 1 部分,一個顯然的想法是先構造 \(a_i = b_{i} \operatorname{OR} b_{i + 1}\) 然後如何分別取出其中的公共部分得到 \(b_i\) 和 \(b_{i + 1}\)。上述構造已經可以滿足 \(b_2\sim b_{n - 2}\) 的要求,僅需再令 \(a_1 = b_1, a_{n} = b_{n - 1}\),檢查下是否合法即可。
上述構造的原理是下列兩式:
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n, a[100010], b[100010];
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
for (int i = 1; i < n; ++ i) std::cin >> b[i];
for (int i = 1; i <= n; ++ i) a[i] = 0;
int flag = 1;
a[1] = b[1], a[n] = b[n - 1];
for (int i = 2; i < n; ++ i) a[i] = b[i - 1] | b[i];
for (int i = 1; i < n; ++ i) {
if ((a[i] & a[i + 1]) != b[i]) flag = 0;
}
if (!flag) std::cout << -1 << "\n";
else {
for (int i = 1; i <= n; ++ i) std::cout << a[i] << " ";
std::cout << "\n";
}
}
return 0;
}
C
模擬,構造
賽時手玩了下發現每次找到最大值 \(M\),並取 \(x=\frac{M}{2}\) 進行操作可保證每次值域縮小一半,一定在四十次內可行,寫了發交上去就過了呃呃。
一種更好寫的做法是依次取 \(x=2^{30}, 2^{29}, 2^{28}, \cdots, 2^0\)。可以保證每次操作後所有數均不大於這次操作的值,則值域是每次縮小至少一半的,保證最後會縮小到 0。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, a[kN];
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
std::priority_queue<int> q;
std::vector<int> ans;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
for (int i = 1; i <= n; ++ i) q.push(a[i]);
int flag = 0;
for (int i = 1; i <= 40; ++ i) {
if (q.top() == 0) {
flag = 1;
break;
}
int x = ((q.top() + 1) / 2);
ans.push_back(x);
std::queue<int> temp;
while (!q.empty()) temp.push(abs(q.top() - x)), q.pop();
while (!temp.empty()) q.push(temp.front()), temp.pop();
}
if (!flag) {
std::cout << -1 << "\n";
continue;
}
std::cout << ans.size() << "\n";
for (auto x: ans) std::cout << x << " ";
std::cout << "\n";
}
return 0;
}
D
構造,異或
樣例好有病媽的騙我去用 mex
構造糖丸了,給個大點的 \(n\) 答案立刻就出來了呃呃
眾所周知有四色定理:平面圖相鄰點顏色不同染色至多四色。於是猜測至多使用四種顏色就能搞定。
發現若不考慮質數 2,則所有邊 \((u, v)\) 的兩端點一定一奇一偶,僅需按照奇偶性染色就搞定了歐耶;再考慮上質數 2 的影響,則滿足有連邊的點 \(u, v\) 一定為下列情況之一:
- \(u, v\) 奇偶性不同,即二進位制位最後一位不同。
- \(u, v\) 奇偶性相同,但 \(u \oplus v = 2\),即二進位制位最後一位相同,倒數第二位不同。
發現僅需將所有點按照二進位制位最後兩位的取值情況(也即 \(\bmod 4\) 的值)分成四個點集,即可保證僅有四個點集之間有邊,點集內部沒有邊相連,則令著四個點集分別染色 1~4 即可。
另外一個思考的方向是差值為 4 的兩個數二進位制倒數後兩位一定相同,且倒數第三位上肯定是一方為 0 一方為 1,即異或值一定是 4 的倍數,一定不是質數則它們之間一定沒有邊。
特判下 \(n\le 5\) 的情況,對於 \(n\ge 6\) 按照 \(\bmod 4\) 取值染色即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n;
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "w", stdout);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
if (n == 1) {
std::cout << "1\n1";
} else if (n == 2) {
std::cout << "2\n1 2";
} else if (n == 3) {
std::cout << "2\n1 2 2";
} else if (n == 4) {
std::cout << "3\n1 2 2 3";
} else if (n == 5) {
std::cout << "3\n1 2 2 3 3";
} else if (n >= 6) {
std::cout << 4 << "\n";
for (int i = 1; i <= n; ++ i) {
std::cout << ((i % 4 == 0) ? 4 : (i % 4)) << " ";
}
}
std::cout << "\n";
}
return 0;
}
/*
int n = 100;
for (int i = 1; i <= n; ++ i) {
std::cout << i << ":(";
std::vector<int> ans;
for (int j = 1; j < i; ++ j) ans.push_back(i ^ j);
std::sort(ans.begin(), ans.end());
for (auto x: ans) if (x < i) std::cout << x << " ";
std::cout << ")\n";
}
*/
E
二分圖,構造
媽的怎麼又是構造做了四道構造了都就沒點傳統題讓我玩玩嗎還剩兩分鐘才發現 Bob 的操作沒法那麼簡單于是寄呃呃呃呃
發現 Alice 需要讓相鄰兩個點染上相同顏色才能獲勝,又至多用三種顏色,想到是不是與二分圖有關。手玩下容易發現 Alice 能夠獲勝當且僅當給定圖不為二分圖,Bob 能獲勝當且僅當給定圖為二分圖。於是先 dfs 黑白染色判定下是否為二分圖。
對於 Alice 的獲勝策略,非二分圖無法黑白染色,於是僅需不斷輸出 1 2
即可。
對於 Bob 的獲勝策略需要動點腦子,不能直接在 dfs 列舉點過程中,僅考慮上一個染色的點,按照每輪次的輸入進行染色,可能會被如下資料卡掉:
上述資料會卡掉錯誤做法,是因為 1 2
兩個同色點在與他們相連的不同色點還未被染完之前就被染上了不同的顏色。容易想到染完色之後一定是一種顏色與原圖上黑點對應,另外兩種顏色與原圖上白點對應。考慮到 Alice 給定的兩個數中一定含有 1 2 兩者中至少一個,於是想到令 1 對應黑點,令 2 對應白點,先僅使用 1 2 進行染色直至原圖上黑點白點中一方被染完,在此之後使用 3 和另一方對應數字將剩餘的點染色即可。這樣即可保證一定可以染完。
由上述必勝策略也可以證明是否為二分圖即為 Alice/Bob 獲勝的充要條件。
賽後寫了 5min 一發過了太搞了哈哈我草
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e4 + 10;
//=============================================================
int n, m, edgenum;
int head[kN], v[kN << 1], ne[kN << 1];
int vis[kN], color[kN];
//=============================================================
void init() {
edgenum = 0;
for (int i = 1; i <= n; ++ i) head[i] = vis[i] = 0;
}
void addedge(int u_, int v_) {
v[++ edgenum] = v_;
ne[edgenum] = head[u_];
head[u_] = edgenum;
}
bool dfs(int u_, int color_) {
vis[u_] = 1, color[u_] = color_;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (vis[v_]) {
if (color[v_] == color_) return false;
} else {
if (!dfs(v_, color_ ^ 1)) return false;
}
}
return true;
}
void alice() {
std::cout << "Alice\n";
std::cout.flush();
for (int i = 1; i <= n; ++ i) {
std::cout << "1 2\n";
std::cout.flush();
int x, y; std::cin >> x;
if (x == -1) exit(0);
std::cin >> y;
}
}
void bob() {
std::cout << "Bob\n";
std::cout.flush();
std::vector<int> node[3];
for (int i = 1; i <= n; ++ i) {
node[color[i] + 1].push_back(i);
}
for (int i = 1; i <= n; ++ i) {
int x, y; std::cin >> x;
if (x == -1) exit(0);
std::cin >> y;
if (x == 3) std::swap(x, y);
if (node[1].empty() || node[2].empty()) {
int p = (node[1].empty() ? 2 : 1);
if (x == p || y == p) std::cout << node[p].back() << " " << p << "\n";
else std::cout << node[p].back() << " " << 3 << "\n";
node[p].pop_back();
} else {
if (x != 3 && y != 3 && node[x].size() > node[y].size()) std::swap(x, y);
std::cout << node[x].back() << " " << x << "\n";
node[x].pop_back();
}
std::cout.flush();
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
// std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
if (n == -1) exit(0);
std::cin >> m;
init();
for (int i = 1; i <= m; ++ i) {
int u_, v_; std::cin >> u_ >> v_;
addedge(u_, v_), addedge(v_, u_);
}
if (dfs(1, 0)) bob();
else alice();
}
return 0;
}
F
大範圍結論小範圍暴力。
寫在最後
學到了什麼:
- D:
- E:造樣例卡啊
- F:發現給定限制在資料範圍較大時很好滿足,於是僅需做小範圍。