文章目录
3175【HNOI2010】弹飞绵羊
L C T LCT LCT,维护链长度即可, L i n k ( i − > t o [ i ] ) Link(i->to[i]) Link(i−>to[i]),出界的点 L i n k ( i − > s ) Link(i->s) Link(i−>s),询问即求 i − > s i->s i−>s链长
3196【HNOI2010】取石头游戏
贪心
首先简化问题:求出先手与后手差值的最大值(即先手加,后手减)
然后进一步简化
- 对于 a i − 1 ≤ a i ≥ a i + 1 a_{i-1}\leq a_i\geq a_{i+1} ai−1≤ai≥ai+1,一定是当前先手取左右,当前后手取中间,所以直接缩成一个值 a i + 1 + a i − 1 − a i a_{i+1}+a_{i-1}-a_i ai+1+ai−1−ai
然后就变成了这个样子:
用红线分成两个部分
对于中间的若干个 V V V字形,可以发现两个蓝色箭头所指的两段可以合起来变成一个 V V V字形,所以中间的部分等价于一堆 V V V字形
然后对于这些 V V V字形,直接贪心取能取到的最大值,具体实现就是把每个左右端点丢到大根堆里面,取一个点出来就把它旁边的点丢进去
再考虑绿箭头部分(左右分别考虑)
-
若有偶数个点,那么先手一定亏。
先手取一个,后手马上取上面那个。这样后手每次都比先手大
-
若有奇数个点,可以在红线的分界处分一个点到蓝箭头去(比如说 1 , 2 , 1 1,2,1 1,2,1可以分成 ( 1 ) ( 2 , 1 ) (1)(2,1) (1)(2,1),也可以分成 ( 1 , 2 ) ( 1 ) (1,2)(1) (1,2)(1))
-
然后先取红箭头部分,取完后该谁先手就谁先手
3197【HNOI2010】城市建设
暴力线段树分治+LCT
由于常数问题,这个显然的做法行不通,考虑其它做法
C D Q CDQ CDQ分治+ k r u s k a l kruskal kruskal
考虑分治操作序列,假设当前为 ( l , r ) (l,r) (l,r)
-
把当前区间被修改的边的边权变为 − ∞ -\infin −∞
再跑 k r u s k a l kruskal kruskal
那么所有被选中的(非当前区间)的边都是必选边
直接加贡献,然后把用必选边连起来的连通块缩成一个点,且不再考虑这些必选边
-
把当前区间被修改的边的边权变为 ∞ \infin ∞
再跑 k r u s k a l kruskal kruskal
那么所有没被选中的(非当前区间)的边都是必不选边
直接扔掉不考虑
然后分治到 ( l , l ) (l,l) (l,l)时,跑最小生成树,并加上必选边的贡献即可
代码实现可以对 C D Q CDQ CDQ的每一层都开一个边集
3218【HNOI2010】合唱队
考虑当前队列 ( l , r ) (l,r) (l,r),加入一个人只会变成 ( l − 1 , r ) (l-1,r) (l−1,r)和 ( l , r + 1 ) (l,r+1) (l,r+1),即只会扩张左边界或者右边界
考虑区间 d p dp dp,设 f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示 ( i , j ) (i,j) (i,j)队列最后一个进来的是左( 0 0 0)还是右( 1 1 1)
- f [ i ] [ j ] [ 0 ] + = f [ i + 1 ] [ j ] [ 0 ] ( a [ i ] < a [ i + 1 ] ) f[i][j][0]+=f[i+1][j][0](a[i]<a[i+1]) f[i][j][0]+=f[i+1][j][0](a[i]<a[i+1])
- f [ i ] [ j ] [ 0 ] + = f [ i + 1 ] [ j ] [ 1 ] f[i][j][0]+=f[i+1][j][1] f[i][j][0]+=f[i+1][j][1]( a [ i ] < a [ j ] a[i]<a[j] a[i]<a[j])
- f [ i ] [ j ] [ 1 ] + = f [ i ] [ j − 1 ] [ 0 ] f[i][j][1]+=f[i][j-1][0] f[i][j][1]+=f[i][j−1][0]( a [ j ] > a [ i ] a[j]>a[i] a[j]>a[i])
- f [ i ] [ j ] [ 1 ] + = f [ i ] [ j − 1 ] [ 1 ] f[i][j][1]+=f[i][j-1][1] f[i][j][1]+=f[i][j−1][1]( a [ j ] > a [ j − 1 ] a[j]>a[j-1] a[j]>a[j−1])
初始化
- f [ i ] [ i ] [ 0 ] = 1 f[i][i][0]=1 f[i][i][0]=1
- 注意到如果 f [ i ] [ i ] [ 1 ] f[i][i][1] f[i][i][1]也等于 1 1 1,则会出现重复计算,所以只初始化一个
3219【HNOI2010】平面图判定
图大概长这个样子:
容易发现一条边只有两种选择:
- 环内
- 环外
然后对于相交,可以看成是两条边在同一侧
同时注意到一条边只可能是内外中的一个
就想到了 2 − S A T 2-SAT 2−SAT问题
对于相交的两条边 u , v u,v u,v
- u 1 u_1 u1-> v 2 v_2 v2
- u 2 u_2 u2-> v 1 v_1 v1
- v 1 v_1 v1-> u 2 u_2 u2
- v 2 v_2 v2-> u 1 u_1 u1
所以相当于连了 ( u 1 , v 2 ) ( u 2 , v 1 ) (u_1,v_2)(u_2,v_1) (u1,v2)(u2,v1)两条双向边
所以原来用 T a r j a n Tarjan Tarjan跑连通,现在可以直接并查集维护(代码短了很多)
最后判一下 u 1 u_1 u1和 u 2 u_2 u2是否在一个集合即可
3220【HNOI2010】物品调度
Case 1 确定位置
对于每一个 i i i,假设 y i y_i yi确定,那么对应的位置分别是 c i + y i , c i + y i + d ⋯ c i + y i + k ⋅ d c_i+y_i,c_i+y_i+d\cdots c_i+y_i+k\cdot d ci+yi,ci+yi+d⋯ci+yi+k⋅d
可以看成是一个间隔为 d d d的环
考虑暴力,从小到大枚举 y y y,再从小到大枚举 x x x,相当于依次枚举每个环,然后再枚举环上的每个位置
对于暴力的优化,可以使用两个并查集
一个用来维护每个环上的位置,且 f a [ x ] fa[x] fa[x]表示这个环上 x x x后面第一个空,每次用完一个位置,就令 f a [ x ] = ( x + d ) % n fa[x]=(x+d)\%n fa[x]=(x+d)%n即可
类似的,第二个用来维护每个环, f a [ y ] fa[y] fa[y]表示 y y y这个环后面第一个有空的环
同时记录 v i s vis vis数组方便查看这个位置是否被用过,注意到 v i s [ X . F i n d ( y ) ] = = t r u e vis[X.Find(y)]==true vis[X.Find(y)]==true即表示 y y y这个环上没有空了
Case 2 求答案
遍历每个环
- 包含空位( s s s)的,贡献为 l e n − 1 len-1 len−1
- 否则贡献为 l e n + 1 len+1 len+1
- 同时长度为 1 1 1的,贡献为 0 0 0(不用动)