Codeforces Round 948 (Div. 2)
写在前面
比赛链接:https://codeforces.com/contest/1977
好久没打了状态拉了呃呃、、、最近被各种大学傻逼事儿搞得心力憔悴、、、整个周末浑浑噩噩地想爆肝一波又肝不动明明一直坐在电脑前两天实际工作时间还不到 6h 发现状态实在垃圾得一批于是决定把手上的最恶心的杀软人工智能实验摆了,属于是触发熔断机制了、、、然后感觉心情突然好了于是就打了这场。
傻逼大学能不能退学啊还有一周多就高考了我也不考了我直接会老家进轮胎厂去
A
签到。
检查操作数量是否足够,若足够检查多余的操作能否凑出 0 即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
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 a, b; std::cin >> b >> a;
if (b >= a && (b - a) % 2 == 0) std::cout << "Yes\n";
else std::cout << "No\n";
}
return 0;
}
B
二进制。
套路地先将 \(x\) 二进制分解。发现题意要求不能有两个连续位非 0,一个显然的想法是考虑如何消去 \(x\) 中连续的 1。发现对于 \(x\) 中的一段连续的 1:\(2^{l}\sim 2^{r}\),可以转换为 \(2^{r + 1} - 2^{l}\),仅需两个位置变为非 0 即可。
然而经过上述转化后,仍可能有相邻两个位置非 0,于是再进一步讨论 \(a_i, a_{i + 1}\):
- \(a_i = a_{i + 1} \not= 0\):上述转化后不可能出现,不需要考虑。
- \(a_{i} = -1, a_{i + 1} = 1\):令 \(a_{i} = 1, a_{i + 1} = 0\)。
- \(a_{i} = 1, a_{i + 1} = -1\):令 \(a_{i} = -1, a_{i + 1} = 0\)。
容易证明经过上述转化后一定不会出现连续的非 0 位置,输出此时各位即可。
实现时偷懒了直接每次询问都输出 32 位。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
LL x, ans[40];
bool yes[40];
//=============================================================
//=============================================================
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 >> x;
for (LL i = 0; i <= 31; ++ i) {
yes[i] = 0;
if (x >> i & 1) yes[i] = 1;
}
for (LL i = 0; i <= 31; ++ i) ans[i] = 0;
for (LL i = 0; i <= 31; ++ i) {
if (yes[i] == 1) {
int len = 0;
while (i <= 31 && yes[i] == 1) ++ i, ++ len;
ans[i - len] = -1, ans[i] = 1;
}
}
for (LL i = 0; i < 31; ++ i) {
if (ans[i] == -1 && ans[i + 1] == 1) {
ans[i + 1] = 0, ans[i] = 1;
}
if (ans[i] == 1 && ans[i + 1] == -1) {
ans[i + 1] = 0, ans[i] = -1;
}
}
std::cout << 32 << "\n";
for (int i = 0; i <= 31; ++ i) std::cout << ans[i] << " ";
std::cout << "\n";
}
return 0;
}
/*
0 0 0 0 1
0 -1 0 0 0
0 1 1 1 0
11
1 1 0 1
*/
C
数论。
先对 \(a\) 升序排序,然后特判答案是否为 \(n\),即判断 \(\operatorname{lcm}_{1\le i\le n} a_i \not\in \{a_i\}\) 是否成立。若不成立,说明有 \(\operatorname{lcm}_{1\le i\le n} a_i = a_n\),即所有数均为 \(a_n\) 的因数。则此时任意子序列的 \(\operatorname{lcm}\) 均为 \(a_n\) 的因数。
于是枚举 \(a_n\) 的所有因数 \(d\) 检查是否没有在原数列中出现,且存在一个子序列满足 \(\operatorname{lcm} = d\),即检查数列中所有 \(d\) 的因数是否满足 \(\operatorname{lcm} = d\)。
用 map
判断权值是否出现,则总时间复杂度 \(O(n\sqrt{\max a}\log n)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2010;
//=============================================================
int n, ans, a[kN];
std::map <LL, int> cnt;
//=============================================================
LL gcd(LL x_, LL y_) {
return y_ ? gcd(y_, x_ % y_) : x_;
}
LL lcm(LL x_, LL y_) {
return x_ / gcd(x_, y_) * y_;
}
void Check(LL d_) {
int nowans = 0;
LL nowlcm = 1;
for (int i = 1; i <= n; ++ i) {
if (a[i] == a[n]) break;
if (d_ % a[i] == 0) ++ nowans, nowlcm = lcm(nowlcm, a[i]);
}
if (nowlcm == d_) ans = std::max(ans, nowans);
}
void Solve() {
LL alllcm = 1;
for (int i = 1; i <= n; ++ i) {
alllcm = lcm(alllcm, a[i]);
if (alllcm > a[n]) {
ans = n;
break;
}
}
if (ans != n && !cnt.count(alllcm)) ans = n;
for (LL d = 1; d * d <= a[n]; ++ d) {
if (a[n] % d != 0) continue;
if (!cnt.count(d)) Check(d);
if (d * d != a[n] && !cnt.count(a[n] / d)) Check(a[n] / d);
}
}
//=============================================================
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;
cnt.clear();
ans = 0;
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
cnt[a[i]] = cnt[a[i]] + 1;
}
std::sort(a + 1, a + n + 1);
Solve();
std::cout << ans << "\n";
}
return 0;
}
D
枚举。
考虑枚举每列是否会做出贡献。手玩下发现当某列有贡献,且已知该列中 1 出现在某行,发现此时的操作方案就已经被唯一确定了。进一步地发现可以令某列做出贡献的操作方案数仅有 \(O(n)\) 种(每行该列位置为 1 其余行该列位置为 0 的方案数)。
于是考虑对于每列都构造出令该列有贡献的操作方案,并记录该方案对应二进制串的的哈希值。则最后出现频率最高的哈希值即为最终操作方案,且该方案的贡献和即为其出现次数。
发现有贡献的方案数仅有 \(O(nm)\) 种,则哈希值数量级为 \(O(nm)\) 级别,偷懒用 map
实现总时间复杂度 \(O(nm\log nm)\) 级别。
实现详见代码。
另外第二个点就把我单哈希卡掉了太掉了哈哈
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 3e5 + 10;
const LL c1 = 233;
const LL c2 = 2333;
const LL p1 = 1e9 + 7;
const LL p2 = 998244353;
//=============================================================
int n, m, ans1;
std::string s[kN], ans2;
std::vector<int> position[kN];
LL pow1[kN], pow2[kN];
std::map <pr<LL, LL>, int> count;
std::map <pr<LL, LL>, pr<int,int> > solution;
//=============================================================
void init() {
std::cin >> n >> m;
count.clear();
solution.clear();
for (int i = 0; i < m; ++ i) position[i].clear();
pow1[0] = pow2[0] = 1;
for (int i = 1; i < n; ++ i) {
pow1[i] = pow1[i - 1] * c1 % p1;
pow2[i] = pow2[i - 1] * c2 % p2;
}
for (int i = 0; i < n; ++ i) {
std::cin >> s[i];
for (int j = 0; j < m; ++ j) {
if (s[i][j] == '1') position[j].push_back(i);
}
}
}
pr<LL, LL> getHash(std::string &s_) {
LL ret1 = 0, ret2 = 0;
for (int i = 0; i < n; ++ i) {
ret1 = (c1 * ret1 + s_[i]) % p1;
ret2 = (c2 * ret2 + s_[i]) % p2;
}
return mp(ret1, ret2);
}
pr<LL, LL> modifyHash(std::string &s_, pr<LL, LL> val_, int pos_) {
LL ret1 = val_.first;
ret1 -= pow1[n - 1 - pos_] * s_[pos_] % p1;
ret1 += pow1[n - 1 - pos_] * (s_[pos_] ^ 1) % p1;
ret1 = (ret1 + p1) % p1;
LL ret2 = val_.second;
ret2 -= pow2[n - 1 - pos_] * s_[pos_] % p2;
ret2 += pow2[n - 1 - pos_] * (s_[pos_] ^ 1) % p2;
ret2 = (ret2 + p2) % p2;
return mp(ret1, ret2);
}
void solve() {
for (int j = 0; j < m; ++ j) {
std::string t;
for (int i = 0; i < n; ++ i) t.push_back('0');
for (auto p: position[j]) t[p] = '1';
pr<LL, LL> val = getHash(t);
for (int i = 0; i < n; ++ i) {
pr<LL, LL> ret = modifyHash(t, val, i);
if (!solution.count(ret)) solution[ret] = mp(i, j);
count[ret] = count[ret] + 1;
}
}
}
void getAns() {
pr<pr<LL, LL>, int> ans = mp(mp(0, 0), 0);
for (auto x: count) {
if (x.second > ans.second) ans = x;
}
ans1 = ans.second;
pr<int,int> sol = solution[ans.first];
std::string t;
for (int i = 0; i < n; ++ i) t.push_back('0');
for (auto p: position[sol.second]) t[p] = '1';
t[sol.first] ^= 1;
ans2 = t;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
init();
solve();
getAns();
std::cout << ans1 << "\n" << ans2 << "\n";
}
return 0;
}
E
构造,图论。
给定的限制很有趣,限制一保证了原图一定是 DAG,限制二实际上保证了原图由至多两个孤立的联通块组成。需要保证对于所有同色节点之间,编号大与所有编号小于它的联通,一个显然的想法是考虑增量法,维护当前确定了染色点集 \(1\sim i\) 中编号最大的黑/白点编号,分别记为 \(B, W\)。每次将编号 \(i + 1\) 加入点集,分别检查它与 \(B, W\) 是否联通以确定 \(i+1\) 的颜色:
情况一,\(i + 1\) 与 \(B, W\) 均不联通:由限制二可知这是不可能的。
情况二,\(i + 1\) 仅与 \(B, W\) 中一方联通:则 \(i + 1\) 的颜色即可唯一确定,直接染色即可。发现若仅出现这种情况,则可以保证 \(B, W\) 之间一定是相互不联通的。
情况三,\(i + 1\) 与 \(B, W\) 均联通:则此时无法确定 \(i + 1\) 的颜色,考虑将其压入栈中暂缓其颜色的确定。继续递增地枚举节点 \(j\),若节点与 \(i + 1\) 联通,说明 \(j\) 仍然与 \(B,W\) 均联通则直接压入栈中,直至找到与该点不联通的第一个节点 \(j'\)。假设在此之前仅出现了情况二使得 \(B, W\) 之间相互不联通,则由限制一可知,\(j'\) 一定且仅与 \(B, W\) 中的一方联通,则可以唯一地确定 \(j'\) 的颜色,然后将栈中所有节点弹出并染为相反颜色即可。
发现经过上述操作后仍然满足 \(B, W\) 相互不联通的性质,于是上述情况三的处理方式可以推广到整个图。
上述过程中每次加入新节点 \(i + 1\) 时:
- 若为情况二:则需 2 次询问。
- 若为情况三:与栈顶联通需 1 次询问,与栈顶不联通再加 1 次询问(一定且仅与 \(B, W\) 中的一方联通)。
则可在 \(2n\) 次询问内得到答案。又每个节点至多会被询问常数次,且仅入出栈一次,总时间复杂度 \(O(n)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 110;
//=============================================================
int n, black, white, p;
int ans[kN];
std::stack<int> st;
//=============================================================
bool query(int x_, int y_) {
std::cout << "? " << x_ << " " << y_ << "\n";
std::cout.flush();
std::string ret; std::cin >> ret;
return (ret[0] == 'Y');
}
void solve1() {
bool flagst = query(st.top(), p);
if (flagst) {
st.push(p);
return ;
}
bool flagw = query(white, p);
if (flagw) {
white = p, ans[p] = 0;
black = st.top();
while (!st.empty()) ans[st.top()] = 1, st.pop();
} else {
black = p, ans[p] = 1;
white = st.top();
while (!st.empty()) ans[st.top()] = 0, st.pop();
}
}
void solve2() {
bool flagw = query(white, p), flagb = query(black, p);
if (flagw && flagb) st.push(p);
else if (flagw) white = p, ans[p] = 0;
else black = p, ans[p] = 1;
}
void init() {
std::cin >> n;
while (!st.empty()) st.pop();
}
void solve() {
black = 1, ans[1] = 1, white = 0, p = 2;
while (p <= n && query(black, p)) black = p, ans[p] = 1, ++ p;
white = p, ans[p] = 0, ++ p;
while (p <= n) {
if (!st.empty()) {
solve1();
} else {
solve2();
}
++ p;
}
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
// std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
init();
solve();
std::cout << "! ";
for (int i = 1; i <= n; ++ i) std::cout << ans[i] << " ";
std::cout << "\n";
}
return 0;
}
写在最后
参考:
学到了什么:
- C:先特判,考虑特判后是否等价于新增了什么重要的条件。
- D:字符串的值传递是全串复制呃呃;考虑转换有贡献的对象
- E:限制的多次应用。
我是废物好相似啊傻逼大学