// // // // // // // // // // // // // //

$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. \(1\) 号柱进行统计 记为 \(cnt\)
  2. \(n\) 号柱上 \(cnt\) 个球移到 \(n + 1\) 号柱上
  3. \(1\) 号柱上为指定颜色的球移到 \(n\) 号柱上 其他颜色的球移到 \(n + 1\) 号柱上
  4. \(n + 1\) 号柱上从 \(1\) 号柱移过来的球移回 \(1\) 号柱
  5. \(2\) 号柱上不为指定颜色的球移到 \(1\) 号柱上 指定颜色的球移到 \(n + 1\) 号柱上 特别的 如果 \(1\) 号柱已经满了 则移到 \(n + 1\) 号柱上

目前柱子的状态为

\(1\) 号柱中不含指定颜色

\(2\) 号柱为空柱

\(n\) 号柱与 \(n + 1\) 号柱均为非法柱

对所有柱子进行上提操作

每次操作涉及三根柱子 定为 \(k\) 号柱 \(n\) 号柱 和 \(n + 1\) 号柱 定义 \(n\) 号柱中不含指定颜色 \(n + 1\) 为空柱

  1. \(k\) 号柱进行统计 记为 \(cnt\)
  2. \(n\) 号柱上 \(cnt\) 个球移到 \(n + 1\) 号柱上
  3. \(k\) 号柱上指定颜色的球移到 \(n\) 号柱上 其他颜色球移到 \(n + 1\) 号柱上

进行完一次操作的状态为

\(k\) 号柱为空柱

\(n\) 号柱为伪合法柱

\(n + 1\) 号柱中不含指定颜色

\(n + 1\) 置为空柱 \(n\) 号柱置为不含指定颜色的柱子 \(k\) 号柱置为伪合法柱

\(k\) 推到下一位 继续进行上提操作 直至所有柱子完成上提操作

将所有柱子顶部指定颜色的球移到 \(n + 1\) 柱上 得到一根合法柱 用 \(n\) 号柱上的球将其他所有柱子补齐 \(m\) 个 使 \(n\) 号柱成为空柱 \(n\) 推到上一位 继续上述循环 直至所有柱子成为合法柱

然后发现过不了样例

\(n = 2\) 的时候 一共只有 \(3\) 根柱子 对柱子进行预处理的时候 需要 \(4\) 根 本身无法完成 需要进行特殊处理 其实这个时候只有两种颜色 比较简单了

  1. \(1\) 号柱进行统计
  2. \(2\) 号柱上 \(cnt\) 个球移到 \(3\) 号柱上
  3. \(1\) 号柱上指定颜色的球移到 \(2\) 号柱上 其他颜色的球移到 \(3\) 号柱上
  4. \(2\) 号柱从 \(1\) 号柱上移过来的球移回 \(1\) 号柱 将 \(3\) 号柱上从 \(1\) 号柱上移过来的球移回 \(1\) 号柱
  5. \(3\) 号柱上所有的球移到 \(2\) 号柱上
  6. \(1\) 号柱上 \(cnt\) 个球移到 \(3\) 号柱上
  7. \(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\) 微信步数

感觉这个题已经超出我的理解范畴了

题中并没有出现我学过的东西

posted @ 2021-05-23 14:35  Blank_space  阅读(70)  评论(5编辑  收藏  举报
// // // // // // //