首页 > 其他分享 >CSP-S 题解

CSP-S 题解

时间:2023-12-31 22:13:07浏览次数:34  
标签:pre 10 int 题解 复杂度 maxN CSP dp

非考场上想出来的会标星号。

T1 密码锁

鲜花:我看到这道题的时候满脑子想的都是春测的 lock。

考虑到只有五个拨圈,每个拨圈只有 \(10\) 个状态,\(n\le 8\),那么直接暴力枚举每个状态即可。

考场代码:

// 15: 00
// 15: 24.

#include<bits/stdc++.h>
using namespace std;

const int maxN = 10;
int n, a[6], b[maxN][6];

bool reach(int k) {
#define c b[k]
	// turn exactly one circle. Only one circle are not the same.
	int cnt = 0;
	for(int i = 1; i <= 5; i++) cnt += (a[i] != c[i]);
	if(cnt == 1) return true;
	if(cnt > 2) return false;
	// turn two circle. Only two circle are not the same;
	int idx1 = -1, idx2 = -1;
	for(int i = 1; i <= 5; i++) {
		if(a[i] != c[i]) {
			(~idx1 ? idx2 : idx1) = i;
		}
	}
	if(idx2 != idx1 + 1) return false;
	return (c[idx2] - a[idx2] + 10) % 10 == (c[idx1] - a[idx1] + 10) % 10;
#undef c
}

int main() {
	freopen("lock.in", "r", stdin);
	freopen("lock.out", "w", stdout);
	cin >> n;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= 5; j++) cin >> b[i][j];
	}
    int ans = 0;
	for(int i = 0; i < 10; i++) {
		a[1] = i;
		for(int j = 0; j < 10; j++) {
			a[2] = j;
			for(int k = 0; k < 10; k++) {
				a[3] = k;
				for(int x = 0; x < 10; x++) {
					a[4] = x;
					for(int y = 0; y < 10; y++) {
						a[5] = y;
						bool flag = true;
						for(int w = 1; w <= n; w++)
							flag &= reach(w);
						ans += flag;
					}
				}
			}
		}
	}
	cout << ans;
}

T2 消消乐

考虑所有合法串的结构,一个合法串要么是最小的,要么是由很多个最小的合法串前后拼接而成。我们令 \(pre_i\) 为以 \(i\) 结尾的最短合法串的头下标(形式化的,\(pre_I=\max(j, s[j:i]\text{合法})\)),那么定义 \(dp_i\) 为以 \(i\) 结尾的合法串数量,必然有 \(dp_i=dp_{pre_i-1}+1\),所求即为 \(\sum_{i=1}^ndp_i\)。

所以问题转为求 \(pre_i\)。初看似乎从前到后用栈模拟,弹出去的就是 \(pre_i\),但其实用 aaa 可以轻松 hack 掉。事实上,我们应当考虑的是,从 \(pre_i\) 到 \(i\) 之间(不包括两端)的字符串是由若干合法串连接而成的,我们要做的无非就是依次跳这些串,直到串的前一个字符是当前要求 \(pre\) 的字符,或者没有 \(pre\)。

初看这个跳的过程复杂度是 \(O(n^2)\) 的,因此可以优化跳的过程。记 \(jmp_{i,j}\) 为从 \(i\) 开始跳到字符 \(j\) 的最近下标,转移是容易的。复杂度 \(\Theta(n\Sigma)\)。

但其实直接暴力跳的复杂度也是对的。我们容易发现,最差情况下每个字符不会跳超过均摊 \(\Sigma\) 次,因此时间复杂度也为 \(\Theta(n\Sigma)\)。
考场代码:

// 15: 24
// let dp[i] is the count of good substrings that end with i.
// then the answer is \sum dp[i]
// We noticed that, for every good substrings that end with i, 
// if we let pre[i] is the last(nearest && < i) index that
// pre[i] ~ i can be a good substring, dp[i] = dp[pre[i] - 1] + 1.
// Because good substrings are either mininum or maximum, and the latter
// perfectly covered the previous one(s).
// Then the problem is how we can find pre[i].
// In classical thought, some index has pre[i], and some index only have nxt[i].
// Is that right? Or say are those indexes always have dp[pre[i] - 1] = 0?
// I'd say it is right. We noticed that, the order that we delete chars is not important.
// Then for every good substrings, we can always find a way to split it into some pieces,
// that every (minimum) pieces is good.

// So we pre-calc the array pre[i], use stack tech.
// then dp.

// Weird things happened that I passed no large samples.
// Is my conclusion right????
// I thought it is right obviously!!!

// **** it. hack: ppoopp. (7)
// The fault is in fact the poped can find its pre[i].
// Then we consider to "jump". We know that what we want to find is
// have the form of AaaabbbcccdddA, which means the middle substrings are all 
// compose of pre[i] ~ i. So, we let pre[i] = i at first, 
// and i -> pre[i] -> pre[pre[i] - 1] (if pre[i] - 1 != i)
// -> pre[... - 1] (if pre[i] - 1 != i)
// until pre[i] = -1. we can compress the path to fasten this process.

// I did not compress the path, but I think it's enough to pass this prob.
// I trust the data strength.
// 16: 25

// Hit by T3 and T4.
// seems that this algo is not beyond O(n\log n).

#include<bits/stdc++.h>
using namespace std;

const int maxN = 2e6 + 10;
int n, pre[maxN], dp[maxN];
int jmp[maxN][26];
char s[maxN];

int main() {
	freopen("game.in", "r", stdin);
	freopen("game.out", "w", stdout);
	cin >> n >> (s + 1);
	memset(jmp, -1, sizeof jmp);
	memset(pre, -1, sizeof pre);
	stack<pair<char, int>> stk;
	for(int i = 2; i <= n; i++) {
		pre[i] = i - 1;
		while(pre[i] > -1 && s[pre[i]] != s[i])
			pre[i] = pre[pre[i]] - 1;// , cout << pre[i] << " ";
		pre[i] = max(pre[i], -1);
	}
	for(int i = 1; i <= n; i++) {
		if(~pre[i]) dp[i] = dp[pre[i] - 1] + 1;
	}
	long long ans = 0;
	for(int i = 1; i <= n; i++) {
		ans += dp[i];
	}
	cout << ans;
}

T3 结构体

鲜花:(拿到压缩包)什么题会叫 struct 呢?
看到大样例:我草。
素材来自同机房大佬。

按照题意模拟即可。

内存对齐,吐了。本人因为过菜,场上没调出来,自闭。

T4 种树

显然答案可以二分,下面考虑如何快速 check 答案的合法性。

首先我们需要贪心的选取要求快速种植的树,我们先去种那样的树不会影响结果。这是因为,对于每棵树而言,先种植什么地方无关紧要,只要能在规定时间种植自己就好。换言之,在两棵树要求的种植时间之间,随意地打乱种植顺序(需要保证合法)不会对答案造成影响;同时,我们种植的区域都是必须要先种的地方,如果一个地方不种就会导致目标无法完成。因此我们证明了贪心的正确性。

那么一个答案不合法,就当且仅当我们没有足够的时间种植。或者说我们依次做以下操作:

  • 计算从该点到根上的未被覆盖的个数。如果大于与前一棵树种植的时间差,说明任务无法完成。
  • 将该树到根上的所有树全部种植。

这是一个显然的树剖,需要写树剖和区间覆盖区间和线段树,时间复杂度 \(O(n\log^3n)\),其中一个 \(\log\) 为树剖贡献,适当卡常应该是足以通过此题的。

考场上因为时间所剩不多,料定自己写不完而放弃此题。

(*)考虑到所有操作全部向根,显然有更优解法。注意到若一个点被覆盖,那么这个点到根路径上所有点都覆盖了。那么我们暴力的一个一个父亲找上去,一直到第一个被覆盖的点为止,计数即为所求。因为每个节点被访问 \(O(1)\) 次,因此 check 复杂度为 \(O(n)\),总复杂度 \(n\log n\)。

CSP-S 丢大分了。我已在 LA 群里定了 NOIp 的目标,因为害怕不能过审就没放在博客里。祝大家 NOIp rp++!

标签:pre,10,int,题解,复杂度,maxN,CSP,dp
From: https://www.cnblogs.com/Piggy424008/p/17938113/solution-csps2023

相关文章

  • CF1748F 题解
    这3000?以下,\(\operatorname{op}(i)\)代表对\(i\)进行一次操作。我们考虑暴力。因为每一位都是分开处理的,我们考虑仅仅把一段区间的端点交换。即我们希望通过\(\operatorname{solve(l,r)}\)把\(a_ia_j\)交换而其他下标不动。一个显然的想法是,我们一定需要大量的前后缀异......
  • CF1844G 题解
    鉴定为学MO学的。MO中著名的《数学奥林匹克小丛书高中卷》的第十五本曾经讲过,如果原方程较难解,可以考虑给左右两边同时对\(M\)取模,然后研究取模以后的问题,其中\(M\)为一个根据问题选取的适当的整数。我们看见这个问题觉得很烦,因为大家都能发现这个条件给的相当于\(d_i+d......
  • P4894 题解
    实际上,这是两个向量的叉积已经是其他题解说烂了的。这里只是给出一个容易记忆\(dim\le3\)的行列式的值的办法。我们以\(3\)维行列式为例子,假设为\[\begin{vmatrix}a&b&c\\i&j&k\\o&p&q\end{vmatrix}\]我们有一个神奇的方法来记忆这个行列式的求值。首......
  • AT_arc127_a 题解
    在HL群里吃瓜,顺手写一篇题解。第一眼必定是数位dp,可是这会使原题难度反而升高了。相对而言,我们要是枚举前缀\(1\)的长度,然后寻找对答案有贡献的区间,此问题是很容易的。同时我们不难发现,前缀\(1\)长度为\(l\)的所有有贡献的数字即为\(\foralli\in[l,16],(\sum_{i=0}^l1......
  • CF1239E 题解
    因为懒得用bitsetMLE了。所以各位想A这题的别偷懒用布尔数组!本题解意在解释如何做类似的dp题,而不在于解释本道题做法的具体推导,只是给出一个思路。我们观察发现,题目想让我们最小化一个最大值。我们并不能枚举每种方案去找最大值再取\(\min\),这样复杂度爆炸而且没有前途......
  • AGC034F 题解
    FWT入门题,很适合我这样的蒟蒻。首先我们可以轻松的根据转移条件写出来一个优美的函数\(T(i)=1+\sum_{j\oplusk=i}a_kT(j)\),边界为\(T(0)=0\)。这个方程属于转移带环的DP,处理方法一般是高斯消元,在这道题里会T飞。但是我们又注意到后边是一个美丽的异或卷积,因此可以考虑用......
  • P6256 题解
    我认为,这道题是我学OI历史以来做过的最难写,最难受,最变态,最不可做,最怀疑人生的题。然后还莫名其妙遇见了!给出一种时间复杂度略劣于ix35的做法。因为本人码力不是很好,因此认为这道题讲讲代码写法也很必要。题意就是给一些线段上戳洞,使得对于给定的一个区间\([l,r]\),从无穷远......
  • P9438 题解
    对于一次询问,相当于在考虑整数\(\frac{n}{x}\)变为\(1\)的方案数。进一步的,这相当于给定一个数列\(c_0\cdotsc_k\),每一次可以减小任意位的任意值,但不能空选,问方案数,这里“空选”指的是不选任何一个数。先考虑允许空选的时候应该怎么做,令\(f(x)\)代表正好走了\(x\)步变......
  • P4528 题解
    这篇题解并不做任何形式上的理论推导,而是在于引导像我一样的蒟蒻,如何在遇到这样的题时,不会陷入数据结构暴力分别求三种形态的深渊里无法自拔。看到这道题我们的第一想法应该是把三种形态的数量都求出来,如果可以的话,这题马上就秒掉了。那么我们尝试着去求——比较简单的可能是高......
  • 一些数 题解
    首先我们考察LIS长度为\(n-1\)的数列的性质。可以发现,这必定是\(1,2,3,\cdots,n\)中拎出一个不听话的元素甩到其他地方去,剩下的元素依次补齐所构成的。这意味着,最多只有一个元素满足\(a_i-i\ge2\),更具体的,不考虑只对邻项交换的排列(即形如\(1,2,3,\cdots,i,i-1,\cdots,n\)),......