专项训练

暑假训练快结束了,突然又想整个这个,零散的刷题时间也要积累起来,万一以后忘了呢。。。

数据结构专题

1. [SNOI2017]一个简单的询问

link:https://www.luogu.com.cn/problem/P5268

从来没有见过这么水的蓝题,一眼莫队板题,分开维护两个区间的和,加入一个数就减去原来的贡献,加上增加的贡献。删除亦然。

Tips:

  1. 块长要设为1000,会影响复杂度。
  2. 排序按 \(l1,r1,l2,r2\) 关键字排。

2. [USACO19DEC] Bessie's Snow Cow P

link:https://www.luogu.com.cn/problem/P5852

set+线段树。会发现如果点 u 已经染过A种颜色,那它的子孙就没必要再染了。所以给每个颜色开个 set 来存储染上这种颜色的点,暴力删除 set 里它的子孙,并加入这个节点。大概就是这样一个思路:

假设现在正在进行一个修改操作,将 x 染成 col 色:

  1. 找到 set 中 x 的前一个点,判断其是否是 x 的祖先,如果是的话直接 continue 跳过本次操作。
  2. 通过 set 找到 x 子树中已经被染上 col 色的节点。
  3. 将这些节点的子树权值总体减 1,同时把这些节点移出 set。
  4. 将 x 放入对应的 set,同时将 x 子树中的节点权值总体加 1。

Tips:

  1. 线段树大坑,一定要好好检查。
  2. set 是个很好用的东西,平常写代码时可以尝试一下。

3. [十二省联考 2019] 春节十二响

link:https://www.luogu.com.cn/problem/P5290

也不算太难。考虑贪心思想,大的和大的分成一段,小的和小的分。而考虑到不能有祖先--子孙关系,所以就是和父亲节点的其他儿子合并。用大根堆存储两个子树的答案,合并时取出堆顶,比较两个堆顶并得出最大值,把这些值再放入较大一点的堆里面,至于为什么要放到较大堆里面,可以用启发式合并来证明。

让高度小的树成为高度较大的树的子树,这个优化可以称为启发式合并算法。

摘自 OI-WiKi 。

Tips:注意 long long。

4. [SDOI2011] 消防

link:https://www.luogu.com.cn/problem/P2491

树的直径有这一结论:对于 任意树上的节点 , 距离其最远的点 一定为树的直径的端点。

所以整两个dfs把树的直径求出来。具体的,先从根节点跑一遍,求出直径的一个端点p,再从 p 跑一遍,即可求出另一个端点。

然后采用尺取法(移动区间),可以计算出在所有满足条件的最优情况下,最大值的最小值。(其实就是维护两个指针,根据s调整指针,再用记录这两个指针距离两个端点的距离 的最大值 的最小值)

但最远距离还可能是直径外的,所以再在直径的每个节点dfs一遍即可。

放一下代码吧:

dfs:

点击查看代码
void dfs(int u,int fa)
{
	f[u]=fa;
	if(len[u]>len[p]) p=u;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa||vis[v]) continue;
		cout<<v<<endl;
		len[v]=len[u]+edge[i].w;
		dfs(v, u);
	}
}

尺取法:

点击查看代码
for(int i=p, j=p;i;i=f[i])
{
	while(len[j]-len[i]>s) j=f[j];
	ans=min(ans, max(len[i], len[p]-len[j]));
}

Tips:

  1. 最后搜索的时候要标记一下直径上的点。避免重复。
  2. 注意到 f 数组,存的不是父亲节点,而是直径上的前驱节点。

5. [HNOI2016] 序列

link:https://www.luogu.com.cn/problem/P3246

看着明显是一个莫队,但也可以强制在线。难点就是转移,从 \([l,r]\)\([l,r+1]\) 转移,每次转移的变化由两部分组成:\([l,p]\)\([p+1, r]\) ( p 是这个区间最小值),前者贡献很好算,后者可以用dp求解,搞一搞dp式子可以发现它能预处理出来。

具体的:
\(f[l][r]\) 表示以 \(r\) 为右端点,左端点在 \([l,r]\) 的区间的答案(要求的就 \(f[p+1[r+1]\) ),记录一下 \(pre_i\) 表示从 \(i\) 向前第一个比 \(i\) 小的数的位置(这个可以单调栈O(n)求出),那么左端点在 \([pre_r,r]\) 的区间最小值都是 \(a[r]\) ,那么就有 \(f[l][r]=f[l][pre_r]+a_r×(r-pre_r)\)

可以发现 \(dp\) 增量只和 \(r\) 自身有关,所以可以去掉 \(l\) 那一维,因为最终一定会存在一个点
\(x\) ,满足 \(pre_x=p\),那么 \(f_{r+1}=a_{r+1}×(r+1-pre_{r+1})+…+a_x×(x-p)+f_p\)

我们可以发现 \(f_{r+1}-f_p\) 就是原来要求的 \(f[p+1][r+1]\)

RMQ:https://blog.csdn.net/qq_41311604/article/details/79900893

数论专题

明显刷题吃力了很多。。。数论还是我的弱项。

1. [ABC281G] Farthest City

link:https://www.luogu.com.cn/problem/AT_abc281_g

一层一层递推过来,dp[i][j]表示总共i个点,最后一层有j个点的方案数。

有dp转移式:\(dp[i][j]+=dp[i-j][k]*C_{n-i+j-1}^{j}*(2^k-1)^j*2^{C_j^2}\)

k就是枚举前面每一层的最后一层的点数,\((2^k-1)^j\) 表示每个点向前面的k个点连线:\(C_k^1,C_k^1...C_k^k\) ,共有 \((2^k-1)^j\) 种方案。\(2^{C_j^2}\) 表示j内部的连线,共有 \(C_j^2\) 种连线,有 \(C_{C_j^2}^0,C_{C_j^2}^1...C_{C_j^2}^{C_j^2}\) 种方案数。

自己只推出来一半,剩下的式子还是推不出来。考虑的不够全面,还有所欠缺。

Tips:

  1. 二项式定理很重要,不要忘了。
  2. TLE 要预处理。
  3. 取模别炸。

2. [ABC305G] Banned Substrings

link:https://www.luogu.com.cn/problem/AT_abc305_g

由于AC自动机学得比较晚,所以印象还是较为深刻的。但矩阵快速幂是真的忘得一干二净了

考虑将所有不能匹配的串构建到AC自动机上,\(dp[i][son[u][1/0]]\) 表示到T的第i位,当前在u节点,下一个节点是a/b的方案数。注意到这是不能匹配的,做以维护一个数组记录每个点是否有结束标记,构建矩阵,用矩阵快速幂来优化,递推dp转移。

Tips:注意RE。

此题并未完全掌握,务必在学习AC自动机时再看一遍!!!

3. [CQOI2016] 密钥破解

link:https://www.luogu.com.cn/problem/P4358

虽然学新知识是好事,但这个 \(Pollard\,rho\) 给我看的够呛。

\(Pollard\,rho\) 算法

可以利用这种算法将N分解,就很轻松地得到p和q,根据扩展欧几里德定理即可求出d和n。

发现自己的扩欧学得还是不扎实,唉,赶紧填坑吧。

4. [NOI2018] 屠龙勇士

link:https://www.luogu.com.cn/problem/P4774

一眼excrt。

根据数据范围给出的表格,我们可以迅速发现 p 数组(有一个点都为质数)即为模数,就能立马推出式子:\(a[i]≡x*atk[i] (mod\,p[i])\),发现和普通的excrt的区别就是多带了个*x,因为模数不一定为质数,所以我们无法用逆元求解,所以就考虑从excrt的本质操作入手。

excrt的过程其实就是两两联立相邻的两行方程,用扩欧求解,方程为:\(lcm*x+p_i*y=(a_i-ans)\) ,总共有 n-1 次合并,将 atk[i] 带入里面推式子,发现式子变为 \(atk_i*lcm*x+p_i*y=a_i-atk_i*ans\) ,也就多带了个 atk[i] ,所以直接改变excrt函数即可。

杀死每条龙的剑提前预处理即可,用multiset

5.「雅礼集训 2018 Day10」足球大战

link:https://gxyzoj.com/d/hzoj/p/3990

其实是一道很简单的概率题,枚举主队和客队的赢的场数,直接递推即可,式子即为:

但是由于数据范围(1e7),要先预处理,边计算边枚举i,注意卡常。

Tips:

  1. 开两个1e7的数组会炸空间,但由于组合数都只需要n的阶乘,所以用一个数来存储 \(n!\)
  2. 求逆元的写法可能会被卡,考虑换写法(详见数论)。

DP杂项

1. [PA2021] Od deski do deski

link:https://www.gxyzoj.com/d/hzoj/p/4096?tid=6709f0bc723564e75e98eb21

小清新dp,但状态设定很不好想。感觉它是从dp的推导设定的,会发现删除操作没有交集,设 \(f[i][j][1/0]\) 代表长度为 i 的序列,在后面放 j 种数可以变成合法的,当前是否合法。答案显然为 \(∑f[n][i][1]\)

转移就是分类讨论,讨论这一位是否合法,填表法和刷表法都可以,大概就是这样(其实是太懒不想写:

2. [TJOI2019] 甲苯先生的字符串

link:https://www.gxyzoj.com/d/hzoj/p/4119?tid=6709f0bc723564e75e98eb21

看到一个1e15,直接想到矩阵快速幂。

基础 dp 很好想, \(dp[i][j]\) 表示到第 i 位,且当前位为 j 的方案数,显然 \(dp[i][j]=\sum_{j=1}^{26}dp[i-1][j]*check[i][j]\) ,其中 \(check[i][j]\) 为1/0,ij是 s1 的子串即为0,否则为1。

考虑矩阵加速,可以不看第1维,把它想象成每一次的转移。初始矩阵是这个样子(共26列):\begin{bmatrix}
1 & 1 & 1 & 1 & ... &1 & 1 &1
\end{bmatrix}

转移矩阵(设为X)其实就是上面的 check 数组,第 i 行第 j 列就是0/1,ij是 s1 的子串即为0,否则为1。

所以答案即为上面第一个矩阵* \(X^{n-1}\) ,最后将这个结果矩阵的每一个值加起来即可。

Tips:对角线为1,其余为0的矩阵 \(\times\) 任意矩阵 A 结果都为 A。

3. [ABC213G] Connectivity 2

link:https://www.gxyzoj.com/d/hzoj/p/778?tid=6709f0bc723564e75e98eb21

这个题是在改完前一次考试题后写的,所以写起来很流畅,很快就A了,题解思路很经典,一定要好好整理。

数据一看就是状压,每一位表示这个点是否删去,设两个数组f和g,f[s]表示s里的连通子集的个数,g[s]表示s里的子集个数。显然用g数组更新f数组。

DP专题

1. [NOIP2021] 数列

link:https://www.luogu.com.cn/problem/P7961
发现n的范围极小,只有30,就可以考虑 \(O(n^4)\) 的复杂度。

因为在计算S的二进制表示时会有进位的现象,考虑从低位到高位转移,设 \(dp[i][j][k][p]\) 表示到S从低到高的第i为,已经确定了j个序列里a中的元素,前i位中有k个1,要向下一位进位p。

采取刷表法。

废,写不完了,等未来某一天再补吧。

2. [ABC234G] Divide a Sequence

link:https://www.luogu.com.cn/problem/AT_abc234_g

3. [春季测试 2023] 圣诞树

link:https://www.luogu.com.cn/problem/P9119

图论专题

1. 小L的有向图

link:https://gxyzoj.com/d/hzoj/p/2144?tid=66aa3ef2d0a287a57dccd3c8

2. 「NOIP2017」逛公园

link:https://www.luogu.com.cn/problem/P3953

3. [bzoj2438] [中山市选2011]杀人游戏

link:https://www.luogu.com.cn/problem/P4819

图论杂项

1.「NOIP2013」华容道

link:https://www.gxyzoj.com/d/hzoj/p/P438?tid=670db460723564e75e9b760d、

自己先写的爆搜,因为太暴力了,而且是dfs,只拿到10分。优化一下应该能拿到更高。暴力思路没啥问题,发现TLE的主要问题在于枚举了很多和目标格子没有任何关系的状态,考虑抽离出这些状态,建边跑最短路即可。

具体的,大概分为以下几个步骤:

  • 预处理
    • 空白格子乱转(建边)
    • 空白格子和指定格子互换(建边)
  • 让空白格子跑到指定格子周围
  • 跑最短路

代码比较长,调了一早上才过,很多细节要注意。

2. Star Way To Heaven

link:https://www.gxyzoj.com/d/hzoj/p/P824?tid=670db460723564e75e9b760d

首先明确一下:走的路径是可以弯曲的,不一定非要走直线!!!

第一眼看完真的是一点思路都没有,考虑将第一个star和上届连接,最后一个star和下界连接,中间的star互相连接,大概就是把这个矩形分成两半,因为要求最小距离的最大值,肯定是最短路径中间最大的那一条分一半(最优策略肯定是走终点)的值。也就是求最小生成树的最大边权。代码实现很清奇,不能说写了prim,而是用了这种思想。

贪心专项

1. 「NOIP2012」国王游戏

link:https://www.luogu.com.cn/problem/P1080

好久都没有做绿题了。显然这个题最好做到a、b值都升序(可以通过这些思路骗到50pts)现在单独考虑两个人的情况:

假设这两个人手上的数为(num表示前面所有人a的乘积):

\(a\) \(b\)

\(c\) \(d\)

对于第二个人,他得到的金币为 \(num*a/d\)

但如果按下面的顺序排:

\(c\) \(d\)

\(a\) \(b\)

对于第二个人,他得到的金币为 \(num*c/b\)

此时,就会发现,如果要交换两个人,当且仅当 \(num*a/d<num*c/b\),把 num 消掉后,经过移项得 \(a*b>c*d\),所以最佳的选择就是所有数满足 \(a*b<c*d\)

此题要高精

2. P2123 皇后游戏

link:https://www.luogu.com.cn/problem/P2123

上面那个题得延续吧。思路还是一样的,就是推两个人的关系。


但是因为这个简单的式子没有传递性,所以这样写肯定是不对的。仔细探究这个式子的本质,就是希望前面的数x值越小,y值越大,而且主要是关于这四个数两两的大小关系。分类讨论一下:

  1. \(a[i]<b[i]\), \(a[j]<b[j]\), 按照a值升序排序。

  2. \(a[i]<b[i]\), \(a[j]>b[j]\), 按照 < > 排序。

  3. \(a[i]>b[i]\), \(a[j]<b[j]\), 同2。

  4. \(a[i]>b[i]\), \(a[j]>b[j]\), 按照b值降序排序。

根据这些排序即可。

3. [luogu1248]加工生产调度

和上面那道题没有太大区别,同样的方法,只看两个物品,假设加工他们的时间分别为:

\(a\) \(b\)

\(c\) \(d\)

此时的时间为:\(a+max(b, c)+d\)

但如果按下面的顺序排:

\(c\) \(d\)

\(a\) \(b\)

此时的时间为:\(c+max(d, a)+b\)

就会发现,如果要交换两个物品,当且仅当 \(a+max(b, c)+d>c+max(d, a)+b\),经过移项得 \(max(b, c)-c-b>max(d, a)-a-d\),同样得到 \(min(d, a)<max(b, c)\)

然后就没有然后了。

4.[JXOI2017] 加法

link:https://www.luogu.com.cn/problem/P4064

显然二分。最小值为min,最大值就是min+m*a,问题在于check函数怎么写。

显然是用最优的方法去改变原数组,判断每一位是否大于mid。会发现遍历到 i 位时(i<mid),当前区间能覆盖 l 且 r 的范围更大,那肯定是选当前区间而不选以前选的。所以贪心策略即为优先选择右端点最远的点进行加法。可以用一个大根堆来维护。需要一个支持区间加、单点查的数据结构,肯定是树状数组。注意维护时的合法性(比如选的区间数等)。

check 函数
bool check(int x)
{
	memset(sum, 0, sizeof(sum));
	while(q.size()) q.pop();
	for(int i=1;i<=n;i++)
	{
		add(i, a[i]-a[i-1]);
	}
	int p=1, cnt=0;
	for(int i=1;i<=n;i++)
	{
		while(p<=m&&b[p].l<=i) q.push(b[p++]);
		while(query(i)<x&&!q.empty())
		{
			choce t=q.top();
			q.pop();
			while(!q.empty()&&t.r<i)
			{
				t=q.top();
				q.pop();
			}
			if(t.r<i||++cnt>k) return 0;
			add(t.l, num);
			add(t.r+1, -num);	
		}
		if(query(i)<x) return 0;
	}
	return 1;
}

5. [NOIP2018 提高组] 赛道修建

link:https://www.luogu.com.cn/problem/P5021

又是二分,又是不会写判断

判断长度最小 \(x\) 是否可行 → \(≥x\) 的段数是否 \(≥m\)

现在只考虑一个点 u 和他所有儿子 v 的情况。用一个 multiset(可自动排序)来记录从每个儿子 v 上来的赛道长度+ u—v 边权。如果儿子两两之间可以配对(每次最小的和与它加起来刚好大于mid的)或单独可以自成一段,那么就删除他们(使得段数更多),个数+1,然后在所有没有删除的值中选一个最大的上传给父亲u,大致就是个dfs。

6. [JOI 2022 Final] 让我们赢得选举

link:https://www.gxyzoj.com/d/hzoj/p/4100?tid=67063ae5f3395ed072f506cd

首先有几个结论:

  1. 多个人在同一州演讲比在不同州讲时间短。
  2. 肯定是先去协作者+赢得选票(简称合作州)的州再去赢得选票(简称支持州)的州。
  3. 去合作州肯定是按b的升序去找的。

剩余的没有获得选票的简称反对州。

发现这个题直接贪心是不可以的,可能一些选中的b对应的a极其地小,此时就不是最优的。所以引入dp。

dp[i][j][k]表示前i个州恰好j个支持州k个合作州的最短时间,因为外面还要枚举协作者的个数,所以复杂度为O(n^4)。考虑化为二维dp,dp[i][j]表示前i个州有j个合作州的最短时间,因为两个合作州之间肯定是支持州(当然算的时候是放到最后),就把支持州的时间带到dp转移里,相当于每一步决策:这一步我是选合作还是支持。感觉特别神奇巧妙,有点说不清楚,感性理解吧。


生活似海/起伏不定/无边总是看不到头/我欲乘风/浪迹远方/因为我并不平凡普通

posted @ 2024-08-24 17:59  zhouyiran2011  阅读(28)  评论(0编辑  收藏  举报