• NOIP2020 题解


    T1. 排水系统

    题目链接:Link

    • 将每个 " 排水节点 " 看成是一个 " 点 "。
      将每个 " 单向排水管道 " 看成是一条 " 单向边 "。
      不难发现,得到的图是一张 DAG。

    • 直接模拟题意即可。
      依次松弛每个节点的蓄水量,直至到达最终排水口。

    • 需要注意的是,在松弛任意一个节点 (v) 的蓄水量时,需要保证:

      • 对于图中的每一条有向边 ((u, v))(u) 的蓄水量都被松弛过了。
    • 发现可以通过拓扑序来转移。

    • 要打高精。
      我比较懒,用的 __int128大家还是好好打高精吧(:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    #include <vector>
    
    #define u128 __int128 
    
    using namespace std;
    
    inline void print(u128 x) {
    	if (x > 9) print(x / 10);
    	putchar('0' + x % 10);
    }
    
    u128 gcd(u128 a, u128 b) {
    	if (!b) return a;
    	return gcd(b, a % b);
    }
    
    const int N = 400100;
    
    struct Node {
    	u128 x, y;
    } a[N];
    
    Node operator + (Node a, Node b) {
    	Node c;
    	c.y = a.y * b.y;
    	c.x = a.x * b.y + b.x * a.y;
    	u128 S = gcd(c.x, c.y);
    
    	if (!S) {
    		c.x = 0;
    		c.y = 1;
    	} else {
    		c.x /= S;
    		c.y /= S;
    	}
    
    	return c;
    }
    
    Node operator / (Node a, int num) {
    	Node b;
    	b.x = a.x;
    	b.y = 1ll * a.y * num;
    	u128 S = gcd(b.x, b.y);
    
    	if (!S) {
    		b.x = 0;
    		b.y = 1;
    	} else {
    		b.x /= S;
    		b.y /= S;
    	}
    
    	return b;
    }
    
    int n, m;
    
    vector<int> to[N];
    
    int deg[N];
    
    void topsort() {
    	queue<int> q;
    	for (int i = 1; i <= n; i ++)
    		if (deg[i] == 0) q.push(i);
    
    	while (q.size()) {
    		int u = q.front(); q.pop();
    		if (to[u].size() == 0) continue;
    		Node give = a[u] / to[u].size();
    		for (int i = 0; i < (int)to[u].size(); i ++) {
    			int v = to[u][i];
    			a[v] = a[v] + give;
    			if (-- deg[v] == 0) q.push(v);
    		}
    	}
    }
    
    int main() {
    	scanf("%d%d", &n, &m);
    
    	for (int i = 1, S; i <= n; i ++) {
    		scanf("%d", &S);
    
    		while (S --) {
    			int x;
    			scanf("%d", &x);
    			to[i].push_back(x);
    		}
    	}
    
    	for (int i = 1; i <= m; i ++)
    		a[i].x = 1, a[i].y = 1;
    
    	for (int i = m + 1; i <= n; i ++)
    		a[i].x = 0, a[i].y = 1;
    
    	for (int i = 1; i <= n; i ++)
    		for (int j = 0; j < (int)to[i].size(); j ++) {
    			int v = to[i][j];
    			deg[v] ++;
    		}
    
    	topsort();
    
    	for (int i = 1; i <= n; i ++)
    		if (to[i].size() == 0) {
    			print(a[i].x);
    			printf(" ");
    			print(a[i].y);
    			puts(""); 
    		}
    
    	return 0;
    }
    
    // I hope changle_cyx can pray for me.
    

    T2. 字符串匹配

    题目链接:Link

    算法一

    特殊性质:(n leq 2^{17})

    • 一个较为简单的做法,基本不用怎么思考。

    • 注意到答案是要求将字符串划分成 (S = (AB)^iC) 的形式。

    • (F(S)) 的表示字符串 (S) 中出现奇数次的字符的数量。
      需要先预处理出:

      • 每一个前缀 (S_{1 .. i}) 出现奇数次的字符的数量,即 (F(S_{1..i}))
      • 每一个后缀 (S_{i .. n}) 出现奇数次的字符的数量,即 (F(S_{i .. n}))
    • 考虑枚举 (T = (AB)),那相当于是枚举一个前缀。
      在此基础上,再从小到大枚举一个 (i),使用 hash 判断子串是否完全相等。

    • 此时整个字符串的划分结构就已经是确定的了。
      (F(C)) 已经预处理好了,那这种情况对答案的贡献,相当于要在 (T) 里数出有多少个 (A) 满足 (F(A) leq F(C))

    • 注意到每一个 (A)(T) 中也是前缀,那直接用树状数组动态维护一下即可。

    • 时间复杂度 (mathcal{O}(n ln n + n log |sum|))
      其中 (sum) 表示字符集。

    • 期望得分 (84 sim 100)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 2000000;
    const unsigned long long P = 13331;
    
    int n;
    char S[N];
    
    // suf & pre
    
    bool exist[30];
    int num;
    
    int suf[N];
    int pre[N];
    int c[N][30];
    
    // hash
    
    unsigned long long power[N];
    unsigned long long hash[N];
    
    unsigned long long H(int l, int r) {
    	return hash[r] - hash[l - 1] * power[r - l + 1];
    }
    
    void work() {
    	scanf("%s", S + 1);
    	n = strlen(S + 1);
    
    // suf
    
    	for (int i = 0; i < 26; i ++)
    		exist[i] = 0;
    
    	num = 0;
    	for (int i = n; i >= 1; i --) {
    		int ch = S[i] - 'a';
    
    		exist[ch] ^= 1;
    
    		if (exist[ch]) num ++;
    		else num --;
    
    		suf[i] = num;
    	}
    
    // pre
    
    	for (int i = 0; i < 26; i ++)
    		exist[i] = 0;
    
    	num = 0;
    	for (int i = 1; i <= n; i ++) {
    		int ch = S[i] - 'a';
    
    		exist[ch] ^= 1;
    
    		if (exist[ch]) num ++;
    		else num --;
    
    		pre[i] = num;
    	}
    
    	for (int i = 1; i <= n; i ++) {
    		for (int j = 0; j <= 26; j ++)
    			c[i][j] = c[i - 1][j];
    		for (int j = pre[i]; j <= 26; j ++)
    			c[i][j] ++;
    	}
    
    // hash
    
    	for (int i = 1; i <= n; i ++)
    		hash[i] = hash[i - 1] * P + (S[i] - 'a');
    
    // work
    
    	long long ans = 0;
    
    	for (int i = 1; i <= n; i ++) {
    		for (int j = 1; j <= n / i; j ++) {
    			int l = (j - 1) * i + 1, r = j * i;
    
    			if (H(1, i) != H(l, r)) break;
    			if (r + 1 > n) break;
    
    			ans += c[i - 1][suf[r + 1]];
    		}
    	}
    
    	printf("%lld
    ", ans);
    }
    
    int main() {
    	power[0] = 1;
    	for (int i = 1; i <= 1500000; i ++) power[i] = power[i - 1] * P;
    
    	int T; scanf("%d", &T);
    
    	while (T --)    work();
    
    	return 0;
    }
    
    // I hope changle_cyx can pray for me.
    

    算法二

    特殊性质:(n leq 2^{20})

    • Z 函数高论,可以做到 (mathcal{O}(n))
    • 题解在路上了

    T3. 移球游戏

    题目链接:Link

    算法一

    特殊性质:(n leq 50)(m leq 300)

    • 可以强行乱搞一下。
    • 记 " 将 (x) 号柱子上的球移动到 (y) 号柱子上 " 的操作为 ( ext{move}(x, y))
    • 可以一个颜色一个颜色来考虑。
      假设说现在要考虑第 (n) 个颜色:
    1. 枚举 (i = 1 o (n - 1))
      (i) 号柱子里所有颜色为 (n) 的球都移动到 (i) 号柱子的最顶端。
      (i) 号柱子共有 (c_i) 个颜色为 (n) 的球。

      • (n) 号柱子移出 (c) 个空位。
        即进行 (c_i)( ext{move}(n, n + 1))
      • 依次考虑 (i) 号柱子里的每一个球。
        若该球的颜色为 (n),则进行一次 ( ext{move}(i, n))
        若该球的颜色不为 (n),则进行一次 ( ext{move}(i, n + 1))
      • (n + 1) 号柱子上方的 (m - c_i) 个球移回 (i) 号柱子。
        即进行 (m - c_i)( ext{move}(n + 1, i))
      • (n) 号柱子上方的 (c) 个球移回 (i) 号柱子。
        即进行 (c_i)( ext{move}(n, i))
      • (n + 1) 号柱子上方的 (c) 个球移回 (n) 号柱子。
        即进行 (c_i)( ext{move}(n + 1, n))
    2. 枚举 (i = 1 o (n - 1))
      (i) 号柱子最顶端所有颜色为 (n) 的球都移动到 (n + 1) 号柱子上。

    3. 依次考虑 (n) 号柱子里的每一个球。
      若该球的颜色为 (n),则进行一次 ( ext{move}(n, n + 1))
      若该球的颜色不为 (n),则将该球补到 (1)(n - 1) 号柱子里的一个空位上。

    • 这样的话就得到了一个规模为 (n - 1) 的子问题,直接递归调用到 (1) 即可。
    • 复杂度是 (mathcal{O}(n^2m)) 的。
      来计算一下该算法的严格操作数。
    • (g(n)) 表示解决一个规模为 (n) 的问题,且不向下递归调用时需要的操作数,则:

    [egin{aligned}g(n)& = sumlimits_{i = 1}^{n - 1} (2m + 2c_i) + sumlimits_{i = 1}^{n - 1} c_i + m\& = 2m(n - 1) + 3sumlimits_{i = 1}^{n - 1} c_i + m\& = 2nm - m + 3sumlimits_{i = 1}^{n - 1} c_iend{aligned} ]

    • 在最坏情况下,(sumlimits_{i = 1}^{n - 1} c_i = m),则:

    [egin{aligned}g(n) & = 2nm - m + 3m\& = 2nm + 2m\& = 2m(n + 1)end{aligned} ]

    • 此时:

    [egin{aligned} ext{answer}& = sumlimits_{i = 1}^n g(i)\& = sumlimits_{i = 1}^n 2m(i + 1) \& = 2m sumlimits_{i = 1}^n (i + 1)\& = 2m [frac{(n + 1)(n + 2)}{2} - 1]\& = m(n^2 + 3n)end{aligned} ]

    • 发现刚好可以过掉 (70) 分。
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    inline int read() {
    	int x = 0, f = 1; char s = getchar();
    	while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); }
    	while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); }
    	return x * f; 
    }
    
    const int N = 60, M = 450;
    
    int n, m;
    
    int top[N], a[N][M];
    int cnt[N][M];
    
    int t;
    pair<int, int> ans[820001];
    
    void move(int x, int y) {
    	ans[++ t] = make_pair(x, y);
    
    	int u = a[x][top[x]];
    
    	cnt[x][u] --;
    	cnt[y][u] ++;
    
    	top[x] --;
    	a[y][++ top[y]] = u;
    }
    
    void solve(int u) {
    	if (u == 1)
    		return;
    
    	for (int i = 1; i < u; i ++) {
    		int c = cnt[i][u];
    
    		for (int j = c; j; j --)
    			move(u, u + 1);
    
    		for (int j = m; j; j --)
    			if (a[i][j] == u) move(i, u);
    			else move(i, u + 1);
    
    		for (int j = m - c; j; j --)
    			move(u + 1, i);
    
    		for (int j = c; j; j --)
    			move(u, i);
    
    		for (int j = c; j; j --)
    			move(u + 1, u);
    	}
    
    	for (int i = 1; i < u; i ++)
    		while (a[i][top[i]] == u)
    			move(i, u + 1);
    
    	int p = 1;
    	for (int j = top[u]; j; j --) {
    		if (a[u][j] == u) move(u, u + 1);
    		else {
    			while (top[p] >= m) p ++;
    			move(u, p);
    		}
    	}
    
    	solve(u - 1);
    }
    
    int main() {
    	n = read(), m = read();
    
    	for (int i = 1; i <= n; i ++) {
    		top[i] = m;
    		for (int j = 1; j <= m; j ++)
    			a[i][j] = read(), cnt[i][a[i][j]] ++;
    	}
    
    	solve(n);
    
    	printf("%d
    ", t);
    	for (int i = 1; i <= t; i ++)
    		printf("%d %d
    ", ans[i].first, ans[i].second);
    
    	return 0;
    }
    
    // I hope changle_cyx can pray for me.
    

    算法二

    特殊性质:(n leq 50)(m leq 400)

    • 分治,可以做到 (mathcal{O}(nm log n))
    • 题解在路上了

    T4. 微信步数

    题目链接:Link

    • 题解在路上了
  • 相关阅读:
    coco2dx--Permission denied
    在Winform中屏蔽UnityWebPlayer的右键以及自带Logo解决方案整理
    Setup Factory 程序打包
    T—SQL用法剪辑,方便以后查看
    如何用asp.net MVC框架、highChart库从sql server数据库获取数据动态生成柱状图
    微软ASP.NET MVC 学习地址
    一个用WPF做的简单计算器源代码
    wpMVVM模式绑定集合的应用
    windows phone上下文菜单ContextMenu的使用示例
    CListCtrl 防止闪烁,调整行显示长度
  • 原文地址:https://www.cnblogs.com/cjtcalc/p/14124164.html
Copyright © 2020-2023  润新知