$NOIP\ 2020$ 模拟考试 题解报告
\(NOIP\ 2020\) 模拟
得分情况
\(T1\) \(90\ Pts\)
\(T2\) \(32/48\ Pts\)
\(T3\) \(0\ Pts\)
\(T4\) \(35\ Pts\)
总分 \(157\ Pts\)
题解
\(T1\) 排水系统
拓扑排序的模板 处理一下分数 能到 \(90\ Pts\)
正解应该要写高精
#include<cstdio>
#include<queue>
#define int long long
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, in[B], out[B];
struct edge {int v, nxt;} e[C];
int head[B], ecnt;
std::queue <int> q;
struct node {
int a, b;
node() {a = 0; b = 1;}
} sum[B];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
inline void print(int x) {
if(x > 9) print(x / 10); putchar(x % 10 ^ 48);
}
/*----------------------------------快读--快写*/
void add_edge(int u, int v) {e[++ecnt] = (edge){v, head[u]}; head[u] = ecnt;}
int gcd(int x, int y) {return y ? gcd(y, x % y) : x;}
node operator / (const node x, int y) {
node z; z = x;
if(z.a % y == 0) z.a /= y;
else
{
int g = gcd(z.a, y);
z.a /= g; y /= g;
z.b *= y;
}
return z;
}
node operator + (const node x, const node y) {
node z; int g = gcd(x.b, y.b);
int _a = x.b / g, _c = y.b / g;
z.b = x.b * _c; z.a = x.a * _c + y.a * _a;
int _g = gcd(z.a, z.b);
z.a /= _g; z.b /= _g;
return z;
}
/*----------------------------------------函数*/
signed main() {
// freopen("water.in","r",stdin);
// freopen("water.out","w",stdout);
n = read(); m = read();
for(int i = 1; i <= n; i++)
{
int x = read();
for(int j = 1; j <= x; j++)
{
int y = read(); add_edge(i, y);
in[y]++; out[i]++;
}
}
for(int i = 1; i <= n; i++) if(!in[i]) q.push(i), sum[i].a = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].v; in[v]--;
sum[v] = (sum[u] / out[u]) + sum[v];
if(!in[v]) q.push(v);
}
}
for(int i = 1; i <= n; i++) if(!out[i]) print(sum[i].a), printf(" "), print(sum[i].b), puts("");;
fclose(stdin);
fclose(stdout);
return 0;
}
开 \(\_\_int128\) 强过
\(T2\) 字符串匹配
看到有特殊的性质 试着推了一下 但是没有任何收获 所以果断写前几个数据的暴力
第一组 直接枚举 \(A\) \(B\) \(C\) 暴力判断 复杂度 \(O(n^4)\) \(16\ Pts\)
考虑优化
维护一个前缀和后缀的单个字符的个数 帮助判断 复杂度 \(O(n)\)
枚举 \(A\) \(B\) 串 判断循环节
总体的复杂度 \(O(n^3)\) 应该到不了这个级别 数据水一点的话 能过三个
发现循环节的判断可以写哈希 但是感觉总体复杂度变化不大 所以弃了
后来实测
洛谷数据 \(32\ Pts\)
\(Loj\) 数据 \(48\ Pts\)
加上哈希的话应该能稳过 \(48\) 的
再考虑优化
前缀后缀的最大值只有 \(26\) 因为只有 \(26\) 个字母
对于 \((AB)^i\) 枚举 \(i\) 对于每一个位置 向所有比这一位置出现奇数次字母种类多的位置累加贡献 相当于是对 \(0\) 到 \(26\) 再纵向累加前缀和 来达到查询的 \(O(1)\)
复杂度 \(O(n\log n + 26n)\)
实测得分 \(84\ Pts\)
大概是常数巨大 被干掉了
发现统计前缀和后缀的数组内部的数字最大只有 \(26\) 但是数组大小比较大 改成 \(short\)
哈希把模数去掉 改用自然溢出
判断奇偶性改为位运算
快读与快写
实测得分 \(92\ Pts\)
吸氧可以 \(AC\)
#include<cstdio>
#include<cstring>
#define int long long
#define ull unsigned long long
#define emm(x) memset(x, 0, sizeof x)
/*--------------------------------------头文件*/
const int base = 137;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
/*------------------------------------常量定义*/
int T, num1[30], num2[30], sum[30], ans;
short sum1[C + B], sum2[C + B];
ull H[C + B], p[C + B];
char s[C + B];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
inline void print(int x) {
if(x > 9) print(x / 10); putchar(x % 10 ^ 48);
}
/*----------------------------------------快读*/
bool check(int x, int y) {return H[y] - H[x] == H[y - x] * p[x];}
void work() {
scanf("%s", s + 1); int n = strlen(s + 1); ans = 0;
emm(sum1); emm(sum2);
for(int i = 0; i < 30; i++) num1[i] = num2[i] = sum[i] = 0;
for(int i = 1; i <= n; i++)
{
num1[s[i] - 96] ^= 1;
if(num1[s[i] - 96]) sum1[i] = sum1[i - 1] + 1;
else sum1[i] = sum1[i - 1] - 1;
}
for(int i = n; i >= 1; i--)
{
num2[s[i] - 96] ^= 1;
if(num2[s[i] - 96]) sum2[i] = sum2[i + 1] + 1;
else sum2[i] = sum2[i + 1] - 1;
}
for(int i = 1; i <= n; i++) H[i] = H[i - 1] * base + s[i] - 96;
for(int i = 2; i <= n; i++)
{
for(int j = sum1[i - 1]; j <= 26; j++) sum[j]++;
for(int j = i; j < n; j += i) if(check(i, j)) ans += sum[sum2[j + 1]]; else break;
}
print(ans); puts("");
}
/*----------------------------------------函数*/
signed main() {
p[0] = 1;
for(int i = 1; i < C + B; i++) p[i] = p[i - 1] * base;
T = read(); while(T--) work();
return 0;
}
正解应该是 扩展\(KMP\)
但是并不会
\(T3\) 移球游戏
比较恶心的一道题目
考试的时候直接弃掉 其他题能写的写完才看的这个题
有一点思路 但是写挂了...
题解思路
单看一根柱子 我们的目标是使得这一根柱子上只有一种指定颜色
现 定义:
- 某一柱子上只有一种颜色的球为 合法柱 其他柱相应为非法柱
- 某一柱子上某一颜色的球只位于顶部为 伪合法柱
- 某一柱子上没有球为 空柱
定义操作
- 上提: 将一根柱子中指定颜色的球提到最上方
- 统计: 统计某一柱子上指定颜色的球的数量
可以对所有的柱子进行上提操作 使所有有球的柱子成为伪合法柱 再将所有柱子顶的这一颜色的球移到空柱上 就可以使得空柱成为合法柱 之后就可以不考虑合法柱了
将任意一根柱子上所有的球移到其他非法柱上 使这一根柱子成为空柱 重复进行上面的操作 直到所有柱子成为合法柱
操作实现
首先 对柱子进行预处理
操作涉及四根柱子 定为 \(1\) \(2\) \(n\) \(n + 1\) 号柱 定义 \(n + 1\) 为空柱
- 对 \(1\) 号柱进行统计 记为 \(cnt\)
- 将 \(n\) 号柱上 \(cnt\) 个球移到 \(n + 1\) 号柱上
- 将 \(1\) 号柱上为指定颜色的球移到 \(n\) 号柱上 其他颜色的球移到 \(n + 1\) 号柱上
- 将 \(n + 1\) 号柱上从 \(1\) 号柱移过来的球移回 \(1\) 号柱
- 将 \(2\) 号柱上不为指定颜色的球移到 \(1\) 号柱上 指定颜色的球移到 \(n + 1\) 号柱上 特别的 如果 \(1\) 号柱已经满了 则移到 \(n + 1\) 号柱上
目前柱子的状态为
\(1\) 号柱中不含指定颜色
\(2\) 号柱为空柱
\(n\) 号柱与 \(n + 1\) 号柱均为非法柱
对所有柱子进行上提操作
每次操作涉及三根柱子 定为 \(k\) 号柱 \(n\) 号柱 和 \(n + 1\) 号柱 定义 \(n\) 号柱中不含指定颜色 \(n + 1\) 为空柱
- 对 \(k\) 号柱进行统计 记为 \(cnt\)
- 将 \(n\) 号柱上 \(cnt\) 个球移到 \(n + 1\) 号柱上
- 将 \(k\) 号柱上指定颜色的球移到 \(n\) 号柱上 其他颜色球移到 \(n + 1\) 号柱上
进行完一次操作的状态为
\(k\) 号柱为空柱
\(n\) 号柱为伪合法柱
\(n + 1\) 号柱中不含指定颜色
将 \(n + 1\) 置为空柱 \(n\) 号柱置为不含指定颜色的柱子 \(k\) 号柱置为伪合法柱
\(k\) 推到下一位 继续进行上提操作 直至所有柱子完成上提操作
将所有柱子顶部指定颜色的球移到 \(n + 1\) 柱上 得到一根合法柱 用 \(n\) 号柱上的球将其他所有柱子补齐 \(m\) 个 使 \(n\) 号柱成为空柱 \(n\) 推到上一位 继续上述循环 直至所有柱子成为合法柱
然后发现过不了样例
当 \(n = 2\) 的时候 一共只有 \(3\) 根柱子 对柱子进行预处理的时候 需要 \(4\) 根 本身无法完成 需要进行特殊处理 其实这个时候只有两种颜色 比较简单了
- 对 \(1\) 号柱进行统计
- 将 \(2\) 号柱上 \(cnt\) 个球移到 \(3\) 号柱上
- 将 \(1\) 号柱上指定颜色的球移到 \(2\) 号柱上 其他颜色的球移到 \(3\) 号柱上
- 将 \(2\) 号柱从 \(1\) 号柱上移过来的球移回 \(1\) 号柱 将 \(3\) 号柱上从 \(1\) 号柱上移过来的球移回 \(1\) 号柱
- 将 \(3\) 号柱上所有的球移到 \(2\) 号柱上
- 将 \(1\) 号柱上 \(cnt\) 个球移到 \(3\) 号柱上
- 将 \(2\) 号柱上指定颜色的球移到 \(3\) 号柱上 其他颜色的球移到一号柱上
最终状态
\(1\) 号柱与 \(3\) 号柱为合法柱
\(2\) 号柱为空柱
每次移动进行记录最后进行输出即可 复杂度是可以通过的
/*
Time: 5.23
Worker: Blank_space
Source: P7115 [NOIP2020] 移球游戏
*/
/*--------------------------------------------*/
#include<cstdio>
#define top(x) st[x][tp[x]]
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int N = 60;
const int M = 500;
const int C = 1e6 + 7;
/*------------------------------------常量定义*/
int n, m, st[N][M], tp[N], tot, d[N], cnt;
struct Ans {int x, y;} t[C];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
void move(int x, int y) {t[++tot] = (Ans){x, y}; st[y][++tp[y]] = st[x][tp[x]--];}
int sum(int x, int y, int res = 0) {
for(int i = 1; i <= m; i++) res += st[x][i] == y;
return res;
}
/*----------------------------------------函数*/
int main() {
n = read(); m = read();
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) st[i][j] = read();
for(int i = 1; i <= n + 1; i++) tp[i] = m, d[i] = i; tp[n + 1] = 0;
for(int p = n; p >= 3; p--)
{
cnt = sum(d[1], p);
for(int i = 1; i <= cnt; i++) move(d[p], d[p + 1]);
for(int i = 1; i <= m; i++) if(top(d[1]) == p) move(d[1], d[p]); else move(d[1], d[p + 1]);
for(int i = 1; i <= m - cnt; i++) move(d[p + 1], d[1]);
for(int i = 1; i <= m; i++) if(top(d[2]) == p || tp[d[1]] == m) move(d[2], d[p + 1]); else move(d[2], d[1]);
Swap(d[1], d[p]); Swap(d[2], d[p + 1]);
for(int k = 1; k < p; k++)
{
cnt = sum(d[k], p);
for(int i = 1; i <= cnt; i++) move(d[p], d[p + 1]);
for(int i = 1; i <= m; i++) if(top(d[k]) == p) move(d[k], d[p]); else move(d[k], d[p + 1]);
Swap(d[k], d[p + 1]); Swap(d[k], d[p]);
}
for(int i = 1; i < p; i++) while(top(d[i]) == p) move(d[i], d[p + 1]);
for(int i = 1; i < p; i++) while(tp[d[i]] < m) move(d[p], d[i]);
}
cnt = sum(d[1], 1);
for(int i = 1; i <= cnt; i++) move(d[2], d[3]);
for(int i = 1; i <= m; i++) if(top(d[1]) == 1) move(d[1], d[2]); else move(d[1], d[3]);
for(int i = 1; i <= cnt; i++) move(d[2], d[1]);
for(int i = 1; i <= m - cnt; i++) move(d[3], d[1]);
while(tp[d[3]]) move(d[3], d[2]);
for(int i = 1; i <= m - cnt; i++) move(d[1], d[3]);
for(int i = 1; i <= m; i++) if(top(d[2]) == 1) move(d[2], d[1]); else move(d[2], d[3]);
printf("%d\n", tot);
for(int i = 1; i <= tot; i++) printf("%d %d\n", t[i].x, t[i].y);
return 0;
}
\(T4\) 微信步数
感觉这个题已经超出我的理解范畴了
题中并没有出现我学过的东西