Codeforces Round 963 (Div. 2)
写在前面
比赛地址:https://codeforces.com/contest/1993
妈的睡到 22:35 整点起床,刚下床就开了妈的太刺激!
为了保证队长当前是 1k9 这个事实不变方便劝诱新大神,于是上小号了呃呃,D 调出来了不是太烂感觉暑假肯定能把小号也打上紫嘻嘻
唉反正小号随便打了呃呃
置顶广告:中南大学 ACM 集训队绝赞招新中!
有信息奥赛基础,获得 NOIP 省一等奖并达到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我与校队队长联系,免选拔直接进校集训队参加区域赛!
没有达到该水平但有志于 XPCX 赛事请关注每学年开始的 ACM 校队招新喵!
到这个时候了还缺队友实在不妙!求求求求快来个大神带我呜呜呜呜
A
签到。
//
/*
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 n; std::cin >> n;
std::string s; std::cin >> s;
int ans = 0, cnt1 = 0, cnt[4] = {n, n, n, n};
for (auto ch: s) {
if (ch == '?') ++ cnt1;
else if (cnt[ch - 'A']) -- cnt[ch - 'A'], ++ ans;
}
std::cout << ans << "\n";
}
return 0;
}
B
模拟。
呃呃反正我是模拟写的,应该有更简单的直接判定的方法。
先特判一开始就合法。然后考虑到奇数+偶数=奇数,则考虑如何将所有偶数变为奇数。观察样例可知,操作次数不会超过偶数个数 +1 次,因为可以选择任意奇数与最大的偶数操作两次,可以造出一个比任何偶数都大的奇数,再使用该奇数操作即可。
则操作次数仅可能为偶数个数,或偶数个数+1。
考虑答案能否取到下界。操作次数为 \(O(n)\) 级别则可直接模拟。显然每次操作一定会使用当前最大的奇数进行操作,考虑记录所有偶数并升序排序,然后按顺序使用最大的奇数对所有数操作,并更新最大奇数即可。若在此过程中较小值一直为偶数一方则可取到下界。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define int long long
const int kInf = 1e9 + 2077;
//=============================================================
//=============================================================
//=============================================================
signed 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;
std::vector<int> even;
int cnt[2] = {0}, maxodd = 0;;
for (int i = 1; i <= n; ++ i) {
int x; std::cin >> x;
if (x % 2 == 1 && x > maxodd) maxodd = x;
if (x % 2 == 0) even.push_back(x);
++ cnt[x % 2];
}
if (cnt[0] == 0 || cnt[1] == 0) {
std::cout << 0 << "\n";
continue;
}
std::sort(even.begin(), even.end());
int flag = 0;
for (auto x: even) {
if (x < maxodd) maxodd = x + maxodd;
else flag = 1;
if (x > even.back()) break;
}
std::cout << cnt[0] + flag << "\n";
}
return 0;
}
/*
1
5
999999996 999999997 999999998 999999999 1000000000
*/
C
乱搞。
感觉有一万种方法能过呃呃,我这种属于是最烂的。
发现有 \(k\le n\),则第一次全部灯亮一定出现在 \(\operatorname{max}_i a_i\) 时刻之后,所有灯变化次数不超过 1 次的时间范围内。于是直接记录每盏灯下一个状态变化的时刻,考虑时刻的变化大力模拟了呃呃,推荐指数不推荐。
复杂度 \(O(n)\) 级别。
正确性应该没错吧大概不会被叉吧呃呃呃,反正 system testing 是过了哈哈不过这个 system testing \(O(nk)\) 都放过去了真是不好说、、、
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, k, a[kN];
std::vector<int> on, off;
//=============================================================
//=============================================================
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 >> k;
on.clear(), off.clear();
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
std::sort(a + 1, a + n + 1);
for (int i = 1; i <= n; ++ i) {
int d = a[n] - a[i], t = d / k;
if (t % 2 == 0) on.push_back(a[n] + k - d % k);
else off.push_back(a[n] + k - d % k);
}
if (on.size() == n) {
std::cout << a[n] << "\n";
continue;
}
std::sort(on.begin(), on.end());
std::sort(off.begin(), off.end());
int ans = -1, szon = on.size(), szoff = off.size();
int realszon = szon, realszoff = szoff;
for (int now = 1, i = 0, j = 0, t = a[n]; now <= n; ++ now) {
if (i == szon) {
t = off[j];
on.push_back(off[j] + k);
++ szon;
++ realszon, -- realszoff;
++ j;
} else if (j == szoff) {
t = on[i];
off.push_back(on[i] + k);
++ szoff;
++ realszoff, -- realszon;
++ i;
} else {
if (on[i] < off[j]) {
t = on[i];
off.push_back(on[i] + k);
++ szoff;
++ realszoff, -- realszon;
++ i;
} else {
t = off[j];
on.push_back(off[j] + k);
++ szon;
++ realszon, -- realszoff;
++ j;
}
}
int ok = 1;
if (i < szon) if (on[i] == t) ok = 0;
if (j < szoff) if (off[j] == t) ok = 0;
if (ok && realszon == n) {
ans = t;
break;
}
}
std::cout << ans << "\n";
}
return 0;
}
D
二分答案,最长上升子序列。
令下标从 0 开始编号。每次删保证删除的是长度为 \(k\) 的子区间,即每次删除的数的下标 \(\bmod k\) 恰好构成了 \(\bmod k\) 的一个剩余系,手玩下很容易发现,若最终剩下 \(n'\) 个数,其下标 \(\bmod k\) 按顺序一定为 \(0, 1, \cdots, n'-1\)。
中位数为 \(x\) 的必要条件是数列中不小于 \(x\) 的数不少于 \(\left\lceil\frac{n'}{2}\right\rceil\) 个,发现答案有单调性,考虑二分答案并每次检查枚举量 \(\operatorname{lim}\) 是否合法,即检查是否存在某个子序列 \(L = \{a_{l_0}, a_{l_1}, \cdots, a_{l_{n'-1}}\}\),有 \(\forall 0\le i\le n'-1, l_i\bmod k = i, l_{i - 1}< l_i\),且 \(L\) 中有不少于 \(\left\lceil\frac{n'}{2}\right\rceil\) 个不小于 \(\operatorname{lim}\) 的数。
发现 \(l_{i - 1}< l_i\) 等价于选择的数的下标 \(i\bmod k\) 一个 \(0\sim n'-1\) 的最长上升子序列。则选择不小于 \(\operatorname{lim}\) 的数时,仅需保证它们的下标 \(i\bmod k\) 的值是严格单调递增的,即可保证存在一种操作方案使它们出现在同一个 \(L\) 中。
于是每次 check 时,考虑对 \(a\) 中所有不小于 \(\operatorname{lim}\) 且 \(i\bmod k\le n'-1\) 的位置 \(i\),记录 \(i\bmod k\) 的值并求最长上升子序列,其长度 \(\operatorname{len}\) 即为子序列 \(L\) 中不小于 \(\operatorname{lim}\) 的数的最大数量,检查是否有 \(\operatorname{len}\ge \left\lceil\frac{n'}{2}\right\rceil\) 即可。
总时间复杂度 \(O(n\log v\log n)\) 级别,常数非常小大概不会被叉哈哈。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
//=============================================================
int n, k, nn;
LL a[kN], ans;
//=============================================================
namespace Bit {
#define lowbit(x) ((x)&(-x))
const int kLim = kN << 1;
int lim, nowtime, t[kLim], tim[kLim];
void Init(int lim_) {
lim = lim_, ++ nowtime;
}
void Insert(int pos_, int val_) {
for (int i = pos_; i <= lim; i += lowbit(i)) {
if (tim[i] != nowtime) t[i] = 0, tim[i] = nowtime;
t[i] = std::max(t[i], val_);
}
}
int Max(int pos_) {
int ret = 0;
for (int i = pos_; i; i -= lowbit(i)) {
if (tim[i] != nowtime) t[i] = 0, tim[i] = nowtime;
ret = std::max(ret, t[i]);
}
return ret;
}
}
bool check(LL lim_) {
std::vector<int> pos {-1};
for (int i = 0; i < n; ++ i) {
if (a[i] >= lim_ && i % k < nn)
pos.push_back(i % k + 1);
}
int len = 0;
Bit::Init(k + 1);
for (int i = 1, sz = pos.size(); i < sz; ++ i) {
int ret = Bit::Max(pos[i] - 1) + 1;
Bit::Insert(pos[i], ret);
len = std::max(len, ret);
}
return len >= (nn / 2 + 1);
}
//=============================================================
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 >> k;
for (int i = 0; i < n; ++ i) std::cin >> a[i];
if (n % k == 0) nn = k;
else nn = (n % k);
ans = 0;
for (LL l = 1, r = 1e9; l <= r; ) {
LL mid = (l + r) >> 1ll;
if (check(mid)) {
l = mid + 1;
ans = mid;
} else {
r = mid - 1;
}
}
std::cout << ans << "\n";
}
return 0;
}
/*
1
4 3
3 9 9 2
5 3
3 2 5 6 4
7 1
5 9 2 6 5 4 6
8 2
7 1 2 6 8 3 4 5
4 5
3 4 5 6
*/
E
结论,异或,状压 DP
先考虑一维的情况。
若只有一维,每次操作的结果和 [AGC016D] XOR Replace 是一样的。对 \(a_i\) 进行一次操作相当于令 \(a_i:=\oplus_{1\le i\le n} a_i\),再对 \(j\) 进行一次操作相当于令 \(a_j:= a_i\)。
则题意等价于有一个长度为 \(n + 1\) 的数列 \(a\),\(a_{n + 1} = \oplus_{1\le i\le n} a_i\),可以任意交换 \(a_i\) 与 \(a_{n + 1}\),对于 \(a_1\sim a_n\) 求相邻两项差的绝对值之和的最小值。
发现数据范围很小,且贡献仅与相邻元素有关,考虑状压 DP 构造数列,记 \(f_{s, i}\) 表示当前填入的数列元素集合为 \(s\),填入的最后一个数是 \(a_i\) 时美丽值的最小值。初始化 \(f_{s, i} = \infin, f_{\{i\}, i} = 0\),则有显然的转移:
记全集为 \(S\),答案即为:
总时间复杂度 \(O(n^2 2^n)\) 级别。
扩展到两维,发现若先进行一次行操作再进行一次列操作,等价于将交点位置修改为整个矩阵的异或和。于是考虑扩展上述做法,题意等价于有一个大小为 \((n + 1)\times (m + 1)\) 的矩阵,第 \(n+1\) 行为各列的异或和,第 \(m+1\) 列为各行的异或和(\((n + 1, m + 1)\) 即整个矩阵异或和),每次操作可以交换两行/两列,求左上角 \(n\times m\) 矩阵的美丽值的最小值。
考虑预处理任意两行/两列相邻时的贡献。发现两维的贡献是独立的,不同维的交换并不影响另一维的贡献。发现若枚举了哪一行被放在了 \(n+1\) 行上,则对列的贡献的计算就可以直接套用一维的做法了。于是考虑转移时处理 \(\operatorname{sum}(i, j)\) 表示将第 \(i\) 行第 \(j\) 列时的最小美丽值,取最小值即可。
在一维做法的基础上仅需再多枚举一维即可,总时间复杂度 \(O(n^2m 2^n + nm^2 2^m) \approx O(n^3 2^n)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 16 + 2;
const int kS = (1 << 16) + 10;
const LL kInf = 1e18;
//=============================================================
int n, m, a[kN][kN];
LL ans, all, row[kN][kN], col[kN][kN], f[kS][kN];
LL sum[kN][kN];
//=============================================================
void init() {
a[n + 1][m + 1] = 0;
for (int i = 1; i <= n; ++ i) {
a[i][m + 1] = 0;
for (int j = 1; j <= m; ++ j) {
a[i][m + 1] ^= a[i][j];
a[n + 1][m + 1] ^= a[i][j];
}
}
for (int j = 1; j <= m; ++ j) {
a[n + 1][j] = 0;
for (int i = 1; i <= n; ++ i) {
a[n + 1][j] ^= a[i][j];
}
}
for (int i = 1; i <= n + 1; ++ i) {
for (int j = 1; j <= n + 1; ++ j) {
row[i][j] = 0;
for (int k = 1; k <= m + 1; ++ k)
row[i][j] += abs(a[i][k] - a[j][k]);
}
}
for (int i = 1; i <= m + 1; ++ i) {
for (int j = 1; j <= m + 1; ++ j) {
col[i][j] = 0;
for (int k = 1; k <= n + 1; ++ k)
col[i][j] += abs(a[k][i] - a[k][j]);
}
}
}
void DP() {
for (int i = 1; i <= n + 1; ++ i) {
for (int j = 1; j <= m + 1; ++ j) {
sum[i][j] = 0;
}
}
all = (1 << (n + 1));
for (int lst = 1; lst <= m + 1; ++ lst) {
for (int s = 1; s < all; ++ s) {
for (int i = 1; i <= n + 1; ++ i) {
f[s][i] = kInf;
}
}
for (int i = 1; i <= n + 1; ++ i) f[1 << (i - 1)][i] = 0;
for (int s = 1; s < all; ++ s) {
for (int i = 1; i <= n + 1; ++ i) {
if ((s >> (i - 1) & 1) == 0) continue;
for (int j = 1; j <= n + 1; ++ j) {
if (i == j || (s >> (j - 1) & 1)) continue;
f[s | (1 << (j - 1))][j] = std::min(f[s | (1 << (j - 1))][j],
f[s][i] + row[i][j] - abs(a[i][lst] - a[j][lst]));
}
}
}
for (int i = 1; i <= n + 1; ++ i) {
LL minf = kInf;
for (int j = 1; j <= n + 1; ++ j) {
if (i == j) continue;
minf = std::min(minf, f[(all - 1) ^ (1 << (i - 1))][j]);
}
sum[i][lst] += minf;
}
}
all = (1 << (m + 1));
for (int lst = 1; lst <= n + 1; ++ lst) {
for (int s = 1; s < all; ++ s) {
for (int i = 1; i <= m + 1; ++ i) {
f[s][i] = kInf;
}
}
for (int i = 1; i <= m + 1; ++ i) f[1 << (i - 1)][i] = 0;
for (int s = 1; s < all; ++ s) {
for (int i = 1; i <= m + 1; ++ i) {
if ((s >> (i - 1) & 1) == 0) continue;
for (int j = 1; j <= m + 1; ++ j) {
if (i == j || (s >> (j - 1) & 1)) continue;
f[s | (1 << (j - 1))][j] = std::min(f[s | (1 << (j - 1))][j],
f[s][i] + col[i][j] - abs(a[lst][i] - a[lst][j]));
}
}
}
for (int i = 1; i <= m + 1; ++ i) {
LL minf = kInf;
for (int j = 1; j <= m + 1; ++ j) {
if (i == j) continue;
minf = std::min(minf, f[(all - 1) ^ (1 << (i - 1))][j]);
}
sum[lst][i] += minf;
}
}
ans = kInf;
for (int i = 1; i <= n + 1; ++ i) {
for (int j = 1; j <= m + 1; ++ j) {
ans = std::min(ans, sum[i][j]);
}
}
}
//=============================================================
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 >> m;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
std::cin >> a[i][j];
}
}
init();
DP();
std::cout << ans << "\n";
}
return 0;
}
/*
1
1 2
1 3
*/
F1
待补。
考虑镜像操作,将移动范围扩大到 \(2w \times 2h\) 的区域,并将区域平移到第一象限,则仅需考虑统计经过 \((k_1\times 2w,k_2\times 2h)\) 多少次,\(k_1, k_2\) 任取。
写在最后
学到了什么:
- D:考虑剩余系。
你说的对按照常理来说现在又是夹带私货环节:
结尾广告:中南大学 ACM 集训队绝赞招新中!
有信息奥赛基础,获得 NOIP 省一等奖并达到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我与校队队长联系,免选拔直接进校集训队参加区域赛!
没有达到该水平但有志于 XPCX 赛事请关注每学年开始的 ACM 校队招新喵!
到这个时候了还缺队友实在不妙!求求求求快来个大神带我呜呜呜呜