2.18 CW 模拟赛 赛时记录
前言
考一场古老 \(\rm{NOIP}\) , 甚至是 \(3\) 题
策略定好正常考即可, 别想太多
看题
\(\rm{T1}\)
- 定义操作 (约束) 和开销 / 收益, 要求最值化开销 / 收益
- 推式子计算约束条件
- 模拟操作情况, 找到最好开销, 注意最大和最小, 一般来说可以贪贪心 (将简单情况先处理, 然后在基础上处理最值)
- 考虑操作对答案的影响 (推式子) , 据此对操作进行排序
- 往往利用 \(\rm{dp}\) , 结合约束处理当前方案
- 列出合法情况需要满足的表达式, 在原序列中贪心选择最优情况
\(\rm{T2}\)
啊?
\(\rm{T3}\)
不好是概率, 数论大佬要发力了, 那我怎么办
总的来说, 今天每道题先给 \(30 \sim 40 \textrm{ min}\) , 然后有部分分可以给 \(20 \textrm{ min}\) 打, 绝对不能贪
然后对数据一定要有检验, 不行多打暴力
争取做到无遗憾就行
\(\rm{T1}\)
思路
题意
定义一个好的序列为: 不存在相邻两个数都是奇数/偶数
希望对于一个长度为 的序列, 可以重新排列, 将其变为一个好的序列
定义一种重排方式的花费是重排前后每个数字位置差的绝对值之和
希望最小化花费, 输出字典序最小的一组结果
最初输入的 \(a\) 可以看做是一个 \(01\) 串, 要求不能有连续的 \(0/1\)
所以不难确定最终的形式, \(n\) 为偶数时可能有两种形式
问题转化为怎么填数花费最小, 字典序最小
猜测奇偶性相同的数中按照顺序分配花费最小, 因为不这样一定不优
但是怎么让字典序最小?
先想什么情况可以让操作次数不变, 但是交换序列中的数
打个代码验证一下想法
会了一点, 就是差不多的, 赶快先开后面的, 这个再想
现在遇到的问题
- 如何交换最优
- 如何处理重数
到时候冲一下 \(n = 5000\) 差不多了
好的, 还剩下 \(30 \textrm{ min}\) 过来磕一下
代码
#include <bits/stdc++.h>
#define int long long
const int MAXN = 5206;
int n;
int C, CC;
int a[MAXN], tmp;
int b[MAXN], c[MAXN];
std::vector<int> split[2]; int pos[2][MAXN];
int cpos[2][MAXN];
signed main()
{
scanf("%lld", &n);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]), tmp += (a[i] % 2), split[a[i] % 2].push_back(a[i]), pos[a[i] % 2][split[a[i] % 2].size() - 1] = i; // 重数问题
if (n <= 200) CC = 500;
else if (n <= 2000) CC = 20;
else if (n <= 5000) CC = 5;
C = CC;
/*本 if 中需要添加一些取优解*/
if (n % 2 == 0) {
/*先奇数情况*/
for (int i = 1; i <= n; i += 2) b[i] = split[1][i / 2];
for (int i = 2; i <= n; i += 2) b[i] = split[0][(i - 1) / 2];
for (int i = 0; i <= n; i++) cpos[0][i] = pos[0][i], cpos[1][i] = pos[1][i];
while (C--) {
for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) {
if (b[i] < b[j] || (b[i] % 2) != (b[j] % 2)) continue;
if (std::max(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]) <= i || j <= std::min(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2])) std::swap(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]), std::swap(b[i], b[j]);
}
}
for (int i = 0; i <= n; i++) pos[0][i] = cpos[0][i], pos[1][i] = cpos[1][i];
C = CC;
/*先偶数情况*/
for (int i = 1; i <= n; i += 2) c[i] = split[0][i / 2];
for (int i = 2; i <= n; i += 2) c[i] = split[1][(i - 1) / 2];
while (C--) {
for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) {
if (c[i] < c[j] || (c[i] % 2) != (c[j] % 2)) continue;
if (std::max(pos[c[i] % 2][(i - 1) / 2], pos[c[j] % 2][(j - 1) / 2]) <= i || j <= std::min(pos[c[i] % 2][(i - 1) / 2], pos[c[j] % 2][(j - 1) / 2])) std::swap(pos[c[i] % 2][(i - 1) / 2], pos[c[j] % 2][(j - 1) / 2]), std::swap(c[i], c[j]);
}
}
/*取优解*/
bool tag = 0; //0 b; 1 c
for (int i = 1; i <= n; i++) {
if (b[i] != c[i]) {
if (b[i] > c[i]) tag = 1;
else tag = 0;
break;
}
}
if (tag) {
for (int i = 1; i <= n; i++) printf("%lld ", c[i]);
} else {
for (int i = 1; i <= n; i++) printf("%lld ", b[i]);
}
} else {
/*先奇数情况*/
if (tmp > n - tmp) {
for (int i = 1; i <= n; i += 2) b[i] = split[1][i / 2];
for (int i = 2; i <= n; i += 2) b[i] = split[0][(i - 1) / 2];
while (C--) {
for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) {
if (b[i] < b[j] || (b[i] % 2) != (b[j] % 2)) continue;
if (std::max(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]) <= i || j <= std::min(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2])) std::swap(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]), std::swap(b[i], b[j]);
}
}
} else {
for (int i = 1; i <= n; i += 2) b[i] = split[0][i / 2];
for (int i = 2; i <= n; i += 2) b[i] = split[1][(i - 1) / 2];
while (C--) {
for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) {
if (b[i] < b[j] || (b[i] % 2) != (b[j] % 2)) continue;
if (std::max(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]) <= i || j <= std::min(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2])) std::swap(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]), std::swap(b[i], b[j]);
}
}
}
for (int i = 1; i <= n; i++) printf("%lld ", b[i]);
}
return 0;
}
\(\rm{T2}\)
后面的题真就有多少拿多少了, 不要留遗憾
思路
题意
每次操作一种颜色使其翻转, 求极长亮灯区间个数\(\mathcal{O} (nq)\) 是送的
再拼一点可以开始打了
对于 \(k \leq 500\) 的情况, 不难发现相邻的同颜色灯泡没有用, 但是好像还是不好
啊啊啊, 根号分治是什么啊
不难发现我们可以处理出两种颜色中间有几条连边, 然后动态维护边的有效性即可快速做出子任务 \(1 \sim 3\)
根号分治没看出怎么搞, 先丢了
代码
#include <bits/stdc++.h>
const int MAXN = 2e5 + 20;
const int MAXK = 520;
int n, q, k;
int col[MAXN];
class subtask12
{
private:
int op[MAXN];
public:
void solve() {
memset(op, 0, sizeof op);
while (q--) {
int x; scanf("%d", &x);
for (int i = 1; i <= n; i++) if (col[i] == x) op[i] = !op[i];
int ans = 0; for (int i = 1; i <= n; i++) if (op[i] == 1 && op[i - 1] == 0) ans++;
printf("%d\n", ans);
}
}
} sub12;
class subtask3
{
private:
int edge[MAXK][MAXK], num[MAXK];
int ans = 0;
int op[MAXK];
public:
void solve() {
memset(edge, 0, sizeof edge); memset(op, 0, sizeof op); memset(num, 0, sizeof num);
for (int i = 1; i <= n; i++) if (col[i] != col[i - 1]) num[col[i]]++, edge[col[i]][col[i - 1]]++, edge[col[i - 1]][col[i]]++;
while (q--) {
int x; scanf("%d", &x); op[x] = !op[x];
if (op[x]) {
ans += num[x];
for (int i = 1; i <= k; i++) {
if (!op[i] || i == x) continue;
ans -= edge[x][i];
}
} else {
ans -= num[x];
for (int i = 1; i <= k; i++) {
if (!op[i] || i == x) continue;
ans += edge[x][i];
}
}
printf("%d\n", ans);
}
}
} sub3;
int main()
{
scanf("%d %d %d", &n, &q, &k);
for (int i = 1; i <= n; i++) scanf("%d", &col[i]);
if (n <= 5000 & q <= 5000) sub12.solve();
else if (k <= 500) sub3.solve();
else sub3.solve();
return 0;
}
\(\rm{T3}\)
骗骗骗
思路
\(\mathcal{O} (2^n)\) 似乎是送的
那先拿到手上
代码
#include <bits/stdc++.h>
#define int long long
const int MOD = 998244353;
const int MAXN = 22;
inline int qpow(int a, int b, int p) {
int res = 1, base = a;
while (b) {
if (b & 1) res = res * base % p;
base = base * base % p;
b >>= 1;
}
return res;
}
namespace calc {
int inv(int a) { return qpow(a, MOD - 2, MOD); }
int add(int a, int b) { return a + b > MOD ? a + b - MOD : a + b; }
int dec(int a, int b) { return a - b < MOD ? a - b + MOD : a - b; }
int mul(int a, int b) { return (a * b) % MOD; }
int dvi(int a, int b) { return mul(a, inv(b)); }
void addon(int &a, int b) { a = add(a, b); }
void mulon(int &a, int b) { a = mul(a, b); }
} using namespace calc;
int n;
int lwin, bwin;
int bs[MAXN];
int ans[MAXN];
int pre[MAXN], suf[MAXN]; // 前后缀中, 不在集合里的数量
int bpow[MAXN], lpow[MAXN];
signed main()
{
int a, b;
scanf("%lld %lld %lld", &n, &a, &b);
lwin = dvi(a, b), bwin = dvi(b - a, b);
for (int i = 0; i <= n; i++) bpow[i] = qpow(bwin, i, MOD), lpow[i] = qpow(lwin, i, MOD);
for (int i = 1; i <= n; i++) ans[i] = 0;
bs[1] = 1; for (int i = 2; i <= n; i++) bs[i] = add(mul(bs[i - 1], bs[i - 1]), 2);
for (int S = 0; S < (1 << n); S++) {
int length = __builtin_popcount(S);
if (length == 0 || length == n) continue;
pre[0] = 0; suf[n + 1] = 0; for (int i = n; i >= 1; i--) suf[i] = suf[i + 1] + (!((S >> (i - 1)) & 1));
int tmp = 1;
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + (!((S >> (i - 1)) & 1));
if ((!((S >> (i - 1)) & 1))) {
continue;
}
mulon(tmp, mul(bpow[pre[i]], lpow[suf[i]]));
}
addon(ans[length], tmp);
}
int res = 0;
for (int i = 1; i < n; i++) addon(res, mul(ans[i], bs[i]));
printf("%lld", res);
return 0;
}

浙公网安备 33010602011771号