[NOI2011] NOI 嘉年华
考虑设 (cnt_{l, r}) 表示被 ([l, r]) 包含的区间种类数。那么我们设 (f[i][j]) 表示考虑到第 (i) 段时间,第一个区间段有 (j) 个,第二个区间段最多可以有多少个。转移直接枚举新的一段到什么位置即可。复杂度 (O(N^3))
考虑第二问,枚举必须选择的数,然后转移的时候钦定转移区间至少有一段包含枚举的 ([l, r]),复杂度 (O(N^4))。可以猜出其满足决策单调性,复杂度 (O(N^3))。
[NOI2011] 兔兔与蛋蛋游戏
有一个结论:由于所有网格图的回路长度都是偶数,要移动必须经过一个空格,那么回路上黑色格子和白色格子奇偶性不可能一样,所以空格子一定不会再次来到去过的地方。
那么直接爆搜 (SG),复杂度最多 (O(2^{nm})),可以拿到 (70pts)。
再继续观察,我们走的路径一定是一个形如链状的结构,我们将相邻的黑白点连边,不难发现这是一个二分图,问题就变成了:给定一张二分图,不能走回头路,两人轮流操作,不能走的输,问先手是否必胜。
考虑先手遵循一个这样的策略:对于任意一个最大匹配,如果先手在未匹配的点上,那么后手一定会到达一个匹配的点上。此时先手前往后手选择点的匹配点上,以此类推。
假设后手可以走到一个非匹配点上,那么将起点到这个点的路径上的匹配边和非匹配边交换,那么最大匹配将加一,所以矛盾。
于是,先手只要最开始可以移动到一个点,使得其不在最大匹配中即可,也就是说,先手只要在一个不在所有最大匹配中匹配的点即可。判断的话直接忽略掉该点判断最大匹配即可。
[NOI2011] 兔农
这是类似于斐波那契的一种序列,因为 (N) 很大,所以不难猜出其有循环节。
如果 (f[i]\%k=1),那么我们把截止到 (i) 的数分成一组,那么每一组内部是一个斐波那契,且长度和结尾由第一个数确定。
考虑每一组的长度如何确定,假设开头为 (x),那么第 (i) 项的 (\%k) 的值为 (x imes f_i\%k),因此 (f_i) 为 (x) 的逆元,因此一个 (f_i) 和一个 (x) 一一对应。
预处理一下然后不断递归即可知道循环节。知道循环节之后,那么我们可以推出转移矩阵,直接在膜 (p) 意义下转移这个矩阵即可。
还有一个问题,假设 (p, x) 不互质,那么没有逆元,此时循环节就在这一层内,同样处理即可。
[NOI2011] 阿狸的打字机
首先我们可以建出所有字符串的 (AC) 自动机,对于每次询问,首先可以将 (y) 在 (AC) 自动机上暴力匹配,将相同的 (y) 一起处理,在每个位置打上标记,每次询问相当于是查询 (fail) 树上 (x) 到根节点标记点的个数,不难想到可以用树状数组维护,只需要支持子树加和单点查询即可。
对于相邻两个 (y),两者最多差一个字符,根据删除和插入讨论是跳父亲还是儿子即可。
[NOI2012] 美食节
知道这是网络流就比较简单了,从后往前表示贡献即可。但是直接做连边量是 (O(nmp)) 级别的,我们还需要优化。
接下来有一个很牛逼的操作,考虑到建图中,我们对于每个厨师做每一道菜都需要新建一个点(设为点对 ((i, j))),但是实际上,有很多点是没有作用的。那么我们可以这么做:先只加入 ((i, 1)) 的点,进行增广路之后,如果 ((i, 1)) 被增广了,此时我们再加入 ((i, 2)),以此类推。此时点数就变成了 (O(P)),而边数也优化成了 (O(pm)),可以通过此题。
[NOI2012] 魔幻棋盘
先考虑一维情况怎么做,由于 (gcd(a , b) = gcd(a, a - b)) ,发现差分之后答案不变,因此我们将原序列差分,那么问题就变成了单点修改和区间 (gcd) 了,用线段树显然可以实现。
对于二维的情况,直接二维差分之后用二维线段树即可。
[NOI2013] 向量内积
先考虑 (k=2) 的情况,设 (F(i, j)) 表示 ((sum_{x=1}^m a_{i, x}a_{j, x})\%2),接下来是一步神奇的操作。我们考虑 (sum_{j<i}F(i, j)),不难发现其为 (sum_{x=1}^ma_{i, x} imes (sum_{k=1}^ia_{k, x}))。当 (F(i, j) e (i-1)\%2) 时,至少有一个 (j),满足 (F(i, j)=0),枚举 (j) 即可做到 (O(ND))。
不难发现我们每次记录前缀和,找到合法的 (i) 之后 (check),总复杂度仍然为 (O(ND))。考虑该算法的正确性,对于一组合法的 ((i, j)),假设 (i>j),该方案不会被检测到当且仅当 (i) 前面排有偶数个合法的 (j),那么我们随机排列,每个 (i) 可以被检测到的概率是 (dfrac{1}{2}),大概 (check) (6sim 8) 次正确率就很高了,复杂度 (O(Tnd))。
再来考虑 (k=3) 的情况,沿用上面的思路,但此时 (\%3) 的值可以是 (0/1/2),直接对他求和没有什么意义。但是我们发现,如果我们求的是平方和,那么就转化为了上面的问题。所以我们只需要对每个 (i) 计算出 (sum_{k=1}^i(sum_{x=1}^ma_{i, x} imes a_{k, x})^2=sum_{k}sum_{x}sum_{y}a_{i, x}a_{k,x}a_{i, y}a_{k,y}=sum_{x}sum_{y}a_{i,x} imes a_{i, y} imes (sum_{k}a_{k,x} imes a_{k,y})),同样维护一下前缀和即可,复杂度 (O(TND^2))。
[NOI2013] 树的计数
将 (dfs) 序变成 (1sim n),同时将 (bfs) 序也进行对应变化。(bfs) 序分层数即为层数变化,不难发现每一层都是单增的。
考虑 (bfs) 序中,如果 (b[i] < b[i + 1]),那么(i) 与 (i + 1) 之间,他们的深度差不超过 (1) ,所以他们之间至多分一次层,又因为每一层单增的,所以如果 (b[i] + 1 e b[i + 1]) 那么 (b[i], b[i + 1]) 之间必须分一层,否则的话可分可不分。
我们将所有相邻且不单增的位置强行分层,可以证明只要满足上述两条件即为合法,由于保证合法,那么一段区间里面有且仅有一个不单增的位置,那么直接给这段区间打上 (0) 的标记,让答案加一即可,剩余没打上标记的位置可分可不分,贡献为 (0.5)。差分一下即可。
[NOI2015] 寿司晚宴
发现如果一个质因数大于 (sqrt{N}),那么他最多只能出现一次。而小于 (sqrt{N}) 的质数只有 (8) 个,因此我们可以采用状压 (dp)。设 (dp[i][j]) 表示第一个集合状态为 (i),第二个集合状态为 (j) 的方案数。
我们将所有数按照最大质因数排序,对于相同最大质因数的数,我们一起进行转移,对于同一组的数,只可能分配给一个人,枚举分给那个人即可。
[NOI2016] 优秀的拆分
首先题意可以显然的转化为求每一个点,往前/往后 (AA) 的数量。枚举 (A) 的长度 (len),一个神奇的操作是将字符串分组,每隔 (len) 个就分成一段,这样段数总数是 (O(NlogN)) 的。
对于相邻两段,求出其最长公共前后缀,这里可以采用后缀树或者直接二分 (Hash)。
特判掉只跨过一段的,那么剩余的 (A) 一定跨过两段,根据相邻三段的最长公共前后缀讨论即可。
[NOI2016] 国王饮水记
首先比 (1) 号小的水我们一定不要,(k) 比 (n) 大肯定无用。
将水位排序,显然每一次选择我们一定会选择 (1) 号和一段连续的区间联通。那么不难得到一个 (dp),即 (dp[i][j]) 表示考虑到了 (i),已经使用了 (j) 次操作,得到的最高水位,转移枚举一个 (k) 转移过来即可,复杂度 (O(N^2KP)),注意这里每次 (dp[i][j]) 每次需要取前缀 (max)。
转移时枚举 (j),设 (g[i] = dp[i][j]),(f[i] = dp[i][j - 1]),转移式为:
这是一个斜率优化的形式,看成是点 ((k, sum[k]-f[k])) 到点 ((i, sum[i])) 的斜率,直接在凸包上三分即可。也不难发现满足决策单调性,于是可以得到一个 (O(NKP)) 的做法。
根据打表可以知道,长度大于 (1) 的区间不会超过 (log) 次,所以我们只需要转移 (log) 层即可,复杂度 (O(NlogKP))
[NOI2017] 整数
先考虑一个 (O(NlogN*30)) 的做法,考虑对于 (a) ,把他二进制拆分,先把对应位置进行暴力加减。然后我们只需要考虑进位和退位的事情。进位和退位只关心连续的一段 (0/1),再将其反转,于是我们只需要支持:单点修改,查询一个点后的第一个 (0/1) 即可。采用任何带 (log) 的数据结构均可。
考虑压位,将 (30) 个位置压成一位,对于每一位只维护其是否全 (0) 或者全 (1),找到后暴力修改即可,可以采用线段树简单实现。
[NOI2017] 泳池
恰好为 (k) 不好统计,考虑差分,统计最大面积不超过为 (k) 的概率。
首先每隔 (k) 个就至少会出现一个 (0) ,那么我们设 (dp[i]) 表示长度为 (i) ,且最大面积不超过 (k) 的概率,设 (f[i]) 表示长度为 (i) ,且不存在 (0) ,最大面积不超过 (k) 的概率。
那么不难得出 (dp[i] = sum_{j=1}^kdp[k-j] imes f[j]),这是一个常系数齐次线性递推的模型,现在考虑怎么计算 (f[j])。
从高往低一次考虑高度,设 (g[i][j]) 表示长度为 (i),且最小高度为 (j) 的方案数,不难得出转移:
注意为了防止算重需要保证枚举的是最后一个长度为 (j) 的。用后缀和优化一下即可,复杂度 (O(K^3))。但根据调和级数,有大量无用状态,真是复杂度为 (O(K^2logK))。
常系数线性齐次递推:设数列 (a) 满足 (a_i=sum_{j=1}^kf_j imes a_{i-j}),那么我们可以在 (O(K^2logN)) 或者 (O(KlogKlogN))的时间计算 (a_n)。
设多项式 (F(x)=sum_{j=1}^kf_j imes x^{k-j}),(G(x)=x^n\%F(x)),那么我们有结论:(a_n=sum_{i=1}^ka_i imes g_i)。所以我们只需要支持多项式取模即可。暴力实现多项式取模复杂度为 (O(K^2logN))
[NOI2018] 冒泡排序
考虑到如果存在三个数 (i < j < k),满足 (p_i>p_j>p_k) ,那么一定至少存在一次交换没有卡满上界,于是问题可以转化成:有多少个排列,字典序小于给定排列,且满足最长下降子序列小于 (3)。
由于要满足字典序的限制,按照通常套路我们一定是要枚举和给定串的最长公共前缀。那么我们只需要求出,前 (i) 位确定,有多少个排列最长下降子序列小于 (3)。
实际上,我们只要知道了长度为 (x),第一位为 (i) 的排列数,那么我们可以映射到对应排列中去。具体的,第一位为我们当前枚举的字母,在剩余字母中的排名,然后根据排名依次填入即可。
所以我们只需要求出 (f(m, i)),即长度为 (m),第一位为 (i) 的所有排列个数即可。最后计算答案时,可以枚举 (lcp),枚举下一位填入什么,不难发现下一位可以填入的是一段区间内所有 (lcp) 内没有出现过的数,也就对应了 (sum_{i=l}^r f(m, i))。其中 (r) 是由于字典序的限制, (l) 是由于要大于第二个上升序列的最后一个元素。这些都可以很好的求出来,于是我们只用考虑怎么推出 (f(m, i))。
枚举第一个比 (x) 大的数,假设其位置为 (i),为权值 (j),那么不难发现 (1sim i) 一定是一个单调递增,那么我们同样可以采取映射的方式,也就是把 (1sim i) 没有出现过的数离散化。那么不难发现,一对 ((i, j)) 的贡献为 (f(m - i + 1, j - i + 1))。于是我们可以写成:
考虑 (f(m, x)) 和 (f(m - 1, x - 1)) 的关系,不难发现
需要注意 (f(m, 1)=sum_{i=1}^{m-1}f(m - 1, i)=f(m, 2)),所以可以 (x=1) 时仍然成立。
于是 (f(m, x)) 就有了组合意义:你当前在 ((1, 1)),每次可以走 ((1, 1)) 或者 ((0, -1)),到 ((m, x)) 的方案数(纵坐标不能走到 (0))。
把坐标轴旋转,((x, y) o (x, x - y)),那么就相当于每次可以走 ((1, 0)) 或者 ((0, 1)),到达 ((m, m - x)) 的方案数,且不能经过直线 (y=x),这个显然是 (dbinom{2 * m - x}{m-x}-dbinom{2*m-x}{m-x-1}),于是我们可以 (O(1)) 计算。至于需要求出 (f(m, x)) 的后缀和,直接就是 (f(m + 1, x + 1))。
[NOI2018] 你的名字
先建出原串的后缀树,再对每个询问串建出后缀树,先求出本质不同子串数量,然后考虑用总的减去出现过的本质不同的字符串的数量。
考虑用在 (S) 的后缀树上进行匹配,对于每个 (l),求出一个最大的 (r),使得其在 (S) 中出现过,那么在 (T) 的后缀树上标记这些子串,(dfs) 一边即可求出有多少个子串出现过。
先考虑 (l=1,r=n) 的情况,直接枚举 (l) ,每次 (l) 增大时跳 (fail),不断跳儿子知道不能匹配为止。而对于 (l e 1, r e n) 的情况,只需要用线段树合并判断一下 (endpos) 集合即可。
[NOI2019] 斗主地
首先有一个暴力 (dp),设 (dp[i][j]) 表示第 (i) 轮第 (j) 个位置上的期望,转移比较显然,每个 (dp) 值转移的复杂度为 (O(j))。
对于 (type=1) 的点,打表后不难看出其答案为等差数列,故我们只要知道了首项,那么就可以推出整个 (dp[i])。(dp[i][1]) 是可以快速求的,因此我们可以 (O(M)) 计算答案。
对于 (type=2) 的点,根据前面的结论,我们可以猜测其为二次函数,那么我们只需要维护出 (dp[i][1sim 3]) 即可,这些都是可以快速维护的。维护之后重新计算二次函数的各项常数即可。
[NOI2019] 序列
首先假设我们确定了相同部分,那么我们一定是选择剩余的 (a, b) 集合中前 (k - l)。所以 (a, b) 的前 (k - l) 大的数一定是会被选择的。
那么我们先选择前 (k - l) 个,那么如果对于一个 (i),既选择了 (a_i),又选择了 (b_i),那么这一个 (i) 一定会被选择。不断重复这个流程,直到选择了 (k-l) 个不同的 (a_i, b_i)。
此时我们需要凑出一对 (a_i, b_i),那么有三种情况:
1.选择一对没有选择过的 (a_i, b_i)。
2.找到一个选择过的 (a_i),选择其 (b_i),再找到一个没有选择过的 (a_i)。
3.找到一个选择过的 (b_i),选择其 (a_i),再找到一个没有选择过的 (b_i)。
我们可以建出其费用流图,费用流有一个重要性质,就是从源汇点连出或者连入的边一定不会再走其反向边,也就是说,我们可以由 (k-1) 的答案直接推出 (k) 的答案。所以我们直接选出这三种情况的最大值更新即可。
[NOI2019] 机器人
首先考虑一个朴素 (dp),设 (f[i][j][k]) 表示考虑到区间 ([i, j]),区间最大值为 (k) 的方案,(s[i][j][k]) 表示 (sum_{xle k}f[i][j][x]),转移比较显然,也就是枚举区间最大值的位置,而可行的位置只有三个,所以我们可以得到一个 (O(N^2K)) 的做法。
发现可能有用的区间实际上不多,实际上打表出来之后只有 (2500) 个,因此我们直接记忆化搜索即可。可以拿到 (50pts)。
如果我们将 (f[i][j][k]) 视作一个关于 (k) 的多项式,不难发现 (s[i][j][k]) 也是一个关于 (k) 的多项式,转移的时候相当于取两者的点值然后进行相乘,那么仍然是一个多项式!
考虑到 (f[i][i]=x^0),不妨采用归纳证明多项式系数,发现其为一个 (r-l) 次多项式。那么我们只需要 (n) 个点值就可以还原出 (f(1, n))。对于 (a_i=1, b_i=10^9) 的情况,直接差值即可,复杂度 (O(N^2W)),(W=2500)。
考虑 (a_i e 1, b_i e 10^9) 的情况,此时 (f[i][i]) 不再为 (x^0),其只有在自身值域内点值为 (1),否则为 (0)。
考虑 划艇 的做法,我们将值域离散化,也就是将值域拆成若干段 ([l_i, r_i)),显然段数是 (O(N)) 级别的。而对于每一段,差值的做法又是满足的。对于每一个值域段,小范围跑暴力,大范围跑差值,
[NOI2020] 命运
首先考虑朴素 (dp),设 (f[i][j]) 表示考虑到以 (i) 为根的子树,子树内的所有还未有 (1) 的链最深的深度。
转移即考虑当前位置填或者不填,假设 (v) 是 (u) 的新儿子,不难得到转移:
不难发现每次转移之和前缀相关,采用线段树合并即可。
[NOI2020] 超现实树
做法非常的简洁…
我们定义好树:找到原树的最长链,一棵树是好树,当且仅当最长链上所有点,除去最长链中包含的那个儿子,大小不超过一。
不难发现不是好树的树是没有意义的,因为假设一个点存在两个大小超过 (1) 的儿子,那么其一定无法表示一个为 (1) ,另一个为一棵子树的所有树,这种树是有无穷多个的。
定义一棵树是几乎完备的,当且仅当其子树是几乎完备的,对于一个节点,我们考虑四种情况,如果四种情况全在则为几乎完备的。
- 只有左儿子,我们递归左儿子。
- 只有右儿子,我们递归右儿子。
- 有左右儿子,且左儿子大小为 (1),我们递归右儿子。
- 有左右儿子,且右儿子大小为 (1)。
这四种情况全部存在,且递归下去全部是几乎完备的,那么这棵树是几乎完备的。考虑采用反证法,很好说明如果有一种情况不是几乎完备的,则整个树林就不是几乎完备的。