Codeforces Round 949 (Div. 2)
写在前面
比赛地址:https://codeforces.com/contest/1981
妈的昨晚硬撑打了场 edu 上午实验下午爬山考试困困困
妈的什么二进制场,C 吃了个爽呃呃写得什么史山
A
二进制。
显然最优的操作方案是每次除掉一个质因数,则应选择区间 \([l, r]\) 中质因数次数之和最大的数。
特别指出了限制 \(2l < r\),即区间 \([l, r]\) 中一定有至少一个 2 的幂,则显然最好的选择是 \(x=2^{\log_{2}^{r}}\),答案即为 \(\log_2 r\)。
//
/*
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 l, r; std::cin >> l >> r;
std::cout << (int) log2(r) << "\n";
}
return 0;
}
B
二进制。
上来就打表的话可能会误入歧途。
首先手玩下容易发现,询问 \((n, m)\) 的结果即为 \(\max(0, n - m) \sim n + m\) 的二进制或。
那么如何求得一段连续区间 \([l, r]\) 的二进制或呢?考虑将区间贡献,看做对一个从 \(l\) 开始不断加 1 直至 \(r\) 的变量不断取二进制或。首先取上两端点 \(l, r\) 的贡献,然后可以发现其他贡献均来自于进位。若出现累加后进位 \(2^{i}\) 说明末尾 \(i\) 位变为了连续的 1,此时末尾的 \(i+1\) 位均会产生贡献,值为 \(2^{i + 1} - 1\)。
于是问题变为如何找到最高位的进位,显然有 \(l\) 中该位为 0,\(r\) 中该位为 1,于是仅需找到 \(l\oplus r\)(\(\oplus\) 为按位异或运算)中最高位即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
const int kN = 1e5 + 10;
int a[kN], b[kN];
//=============================================================
LL get(int n_, int m_) {
int l = std::max(0, n_ - m_), r = n_ + m_, ret = l | r;
for (int i = 30; i >= 0; -- i) {
int val = (1 << i);
if (val & (l ^ r)) {
ret |= (val | (val - 1));
break;
}
}
return ret;
}
//=============================================================
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, m; std::cin >> n >> m;
if (m == 0) std::cout << n << "\n";
else std::cout << get(n, m) << "\n";
}
return 0;
}
C
二进制,构造。
先特判下整个数列均为 -1 的情况,直接构造 1 2 1 2 ...
即可。然后发现 \(a'_i \not= i\) 的位置实际上将原数列分为了若干互不影响的段,仅需考虑如何构造每段 \([l, r]\)。保证了 \(a_i\le 10^8 < \frac{10^9}{2}\),于是第一段和最后一段分别构造为 \(\cdots a_{l}, 2\times a_{l}, a_{l}\) 和 \(a_{r}, 2\times a_r, a_r \cdots\) 即可。考虑其他的有两个端点 \(a_l, a_r\) 限制的段如何构造。
先直接特判掉长度为 2 的段是否合法。
发现一种很重要的特殊情况是 \(a_{l} = a_{r}\),此时仅需检查区间长度的奇偶性即可判断是否有解,若有解仅需构造 \(a_l, 2\times a_l, a_l, \cdots, a_r, 2\times a_r, a_r\) 即可。
然后考虑一般情况。一个显然的想法是分别从两端 \(l, r\) 开始向内构造,找到某个位置 \(p\) 使得从两端开始构造在此处的元素相等。发现对于已知的 \(x\),若元素 \(y\) 与它可以相邻,则一定是下列三种情况之一:
- \(y = \left\lfloor\frac{x}{2}\right\rfloor\),由 \(x\) 删去二进制最后一位得到。
- \(y = 2\times x\),由 \(x\) 在二进制最后添加 0 得到。
- \(y = 2\times x + 1\),由 \(x\) 在二进制最后添加 1 得到。
显然从两端开始构造时,比较好的方案是首先不断进行第一种构造,删去 \(a_l, a_r\) 的末位直至两者出现公共前缀后,才可以使用另外两种构造。此时即可套用 \(a_l = a_r\) 的做法了。
实现时可以先对 \(a_l, a_r\) 进行二进制分解,对二进制串求最长公共前缀即可。如果在此过程中出现该段不够长的情况,或套用 \(a_l = a_r\) 做法时出现奇偶性不合法的则无解。
上述做法需要对 \(O(n)\) 数量级的元素进行二进制分解,总时间复杂度上界 \(O(n\log v)\) 级别。
实际上述做法还可以简化,并不需要限定删去 \(a_l, a_r\) 的末位直至两者出现的公共前缀一定是最长公共前缀。可以分别从两端开始一直删直到 1,并考虑此时构造了多少位出来。若构造的位数大于区间总长仅需把多余的忽略,若不足区间总长再套用 \(a_l = a_r\) 做法。
特别的,因为没有最小化删去数量并判断奇偶性,需要最后再枚举构造出的数列检查合法性。
实现起来相当好写,而且复杂度降为线性。可以见这发提交:https://codeforces.com/contest/1981/submission/263497967。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
const int kLim = 1e9;
//=============================================================
int n, a[kN], ans[kN];
//=============================================================
std::string get(int val_) {
// if (val_ >= kLim || val_ <= 0) while(1);
std::string s;
while (val_) {
s.push_back(val_ % 2 + '0');
val_ /= 2;
}
std::reverse(s.begin(), s.end());
return s;
}
bool solve(int l_, int r_) {
ans[l_] = a[l_], ans[r_] = a[r_];
if (l_ == 0 && r_ == n + 1) {
for (int i = 1; i <= n; ++ i) {
if (i % 2 == 1) ans[i] = 1;
else ans[i] = 2;
}
return true;
} else if (l_ == 0) {
for (int i = r_ - 1; i; -- i) {
if ((r_ - i) % 2 == 1) ans[i] = 2 * ans[i + 1];
else ans[i] = ans[i + 1] / 2;
}
return true;
} else if (r_ == n + 1) {
for (int i = l_ + 1; i < r_; ++ i) {
if ((i - l_) % 2 == 1) ans[i] = 2 * ans[i - 1];
else ans[i] = ans[i - 1] / 2;
}
return true;
}
if (r_ == l_ + 1) return (a[l_] / 2 == a[r_]) || (a[r_] / 2 == a[l_]);
int logl = (int) log2(a[l_]), logr = (int) log2(a[r_]), d = abs(logr - logl);
if (d > r_ - l_) return false;
if ((r_ - l_) % 2 != d % 2) return false;
if (ans[l_] != ans[r_]) {
int maxlen = 0;
std::string sl = get(a[l_]), sr = get(a[r_]);
for (int i = 0; i < std::min(logl, logr) + 1; ++ i) {
if (sl[i] != sr[i]) break;
++ maxlen;
}
for (int i = 1; i <= logl - maxlen + 1 && l_ <= n; ++ i) {
++ l_, ans[l_] = ans[l_ - 1] / 2;
}
for (int i = 1; i <= logr - maxlen + 1 && r_; ++ i) {
-- r_, ans[r_] = ans[r_ + 1] / 2;
}
if (l_ > r_) return false;
}
for (int i = l_ + 1; i < r_; ++ i) {
if ((i - l_) % 2 == 1) ans[i] = 2 * ans[i - 1];
else ans[i] = ans[i - 1] / 2;
}
return true;
}
//=============================================================
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 >> a[i];
int lst = 0, flag = 1;
for (int i = 1; i <= n; ++ i) {
if (a[i] == -1) continue;
flag &= solve(lst, i);
lst = i;
}
flag &= solve(lst, n + 1);
if (!flag) std::cout << -1 << "\n";
else {
for (int i = 1; i <= n; ++ i) std::cout << ans[i] << " ";
std::cout << "\n";
}
}
return 0;
}
D
构造,数论。
妈的好诡异的题拿到这题我都不知道我要干啥呃呃
考虑到每个合数均为若干质数的乘积,则若构造方案中有合数,可以将合数替换为质数从而减少使用的权值的种类数,于是仅需考虑使用质数构造,则此时并不需要考虑具体使用了哪些质数,且此时 \(a_{i}\times a_{i + 1} = a_{j}\times a_{j + 1}\) 的充要条件即为无序对 \((a_{i}, a_{i + 1}) = (a_{j}, a_{j + 1})\)。该限制和无重边很像。则若已知需要 \(m\) 种质数进行构造,问题等价于在一张 \(m\) 个点的带自环的完全图中,找到一条边数为 \(n - 1\) 的欧拉路径。使用 Hierholzer 算法即可 \(O(m^2)\) 地解决。
于是考虑至少需要多少种质数才可构造出 \(n-1\) 条边的欧拉路径。由于自环的贡献显然可以全部得到,于是仅需考虑非自环边的影响。若 \(m\) 为奇数则所有点度数均为偶数,显然最长欧拉路径可以包括所有非自环边,数量为 \(\frac{m(m - 1)}{2}\);若 \(m\) 为偶数则所有点度数均为奇数,则需要屏蔽一些边使得该图存在欧拉路径,使非零度点是连通的,且有不多于 2 个奇度点。发现删去一条边至多使奇度数点减 2,则至少需要删除 \(\frac{m}{2} - 1\) 条边,且使这些边无交点。则钦定删除 \((2, 3), (4, 5), \cdots, (m - 2), (m - 1)\) 即可,则最长欧拉路径包括的非自环边数为 \(\frac{m(m - 1)}{2} - \frac{m}{2} + 1\)。
发现上面的式子关于 \(m\) 是单调的,则可以二分求得至少需要多少种质数。发现 \(n=10^6\) 时 \(m\approx 1100\),显然不会超过上界 \(3\times 10^5\)。
另外先尝试了不显式地建图然而比用 vector
显式建图怎么慢十倍啊我草
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kLim = 1e6;
const int kM = 1e4 + 10;
const int kN = 4e6 + 10;
//=============================================================
int n, m, ans[kN];
int pnum, prime[kM + 10], vis[kN];
int edgenum, top, st[kN];
std::vector<pii> g[kM];
//=============================================================
void getPrime() {
for (int i = 2; i <= kLim; ++ i) {
if (!vis[i]) prime[++ pnum] = i;
for (int j = 1; j <= pnum && i * prime[j] <= kLim; ++ j) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
bool check(int m_) {
if (m_ % 2 == 1) {
return 1ll * m_ * (m_ - 1) / 2ll + m_ >= n - 1;
} else {
return 1ll * m_ * (m_ - 1) / 2ll - m_ / 2 + 1 + m_ >= n - 1;
}
}
void getM() {
int l = 1, r = kLim, ret = kLim;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
ret = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
m = ret;
}
bool haveEdge(int u_, int v_) {
if (u_ > v_) std::swap(u_, v_);
return !(m % 2 == 0 && u_ % 2 == 0 && v_ == u_ + 1);
}
// int edge(int u_, int v_) {
// if (u_ > v_) std::swap(u_, v_);
// return (u_ - 1) * m + v_;
// }
void dfs(int u_) {
// for (int v_ = 1; v_ <= m; ++ v_) {
// int e = edge(u_, v_);
// if (!haveEdge(u_, v_) || vis[e]) continue;
// vis[e] = 1;
// dfs(v_);
// }
while (!g[u_].empty()) {
pii e = g[u_].back(); g[u_].pop_back();
if (vis[e.second]) continue;
vis[e.second] = 1;
dfs(e.first);
}
st[++ top] = prime[u_];
}
void getAns() {
edgenum = top = 0;
for (int i = 1; i <= m * m; ++ i) vis[i] = 0;
for (int i = 1; i <= m; ++ i) g[i].clear();
for (int i = 1; i <= m; ++ i) {
for (int j = i; j <= m; ++ j) {
if (!haveEdge(i, j)) continue;
g[i].push_back(mp(j, ++ edgenum));
g[j].push_back(mp(i, edgenum));
}
}
dfs(1);
std::reverse(st + 1, st + n + 1);
for (int i = 1; i <= n; ++ i) std::cout << st[i] << " ";
std::cout << "\n";
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
getPrime();
int T; std::cin >> T;
while (T --) {
std::cin >> n;
getM();
// std::cout << m << "--\n";
getAns();
}
return 0;
}
/*
1
787
*/
写在最后
参考:
学到了什么:
- A:特别指出的反常限制————有诈!
- B:考虑贡献来自于什么过程。
- C:从二进制角度考虑。
- D:根据相邻关系构造数列转成构造路径。