\(2019/8/27\)大考
\(\color{#ff0808}{\text{初二诀别赛(SAD)}}\)
题目名称 | 链接 |
---|---|
寿司 | \(BSOJ5111\) |
秀秀的森林 | \(BSOJ5125\) |
分组 | \(BSOJ5126\) |
入阵曲 | \(BSOJ5129\) |
将军令 | \(BSOJ5130\) |
文本编辑器 | \(BSOJ5089\) |
【第一题】
\(\color{#0080FF}{\underline{\large{题面}}}\)
【简述】
有一个由\(R\)和\(B\)组成的字符串环,每次可以交换相邻两个字符,问最少需要交换多少次使得\(R\)在一起,\(B\)在一起
【题解】
法一:
变环为链后我们就是要让一种同类元素聚在中间
我们可以发现,我们一定可以找到一个"断点"或者"中间点",使得这个点同类的点都向它靠,不同类的都向两边散开
我们假设这个种类的点是\(B\),则向两边走的是\(R\)
关键问题是,我们并不知道最优解的那个循环同构串是那个,因此要枚举
以\(i\)开始的循环同构串在两倍串中是\([i,i+len-1]\)
第一个问题:如何计算一个点\(x(\in[l,r])\),将\([l,x]\)的所有的\(R\)移到\([l,r]\)最左边,将\([x+1,r]\)的所有\(R\)移到\([l,r]\)最右边
这里很简单还是写一下公式但自己推意思
设\(bl_i\)表示\(i\)(为\(R\))位置以左有多少个\(B\),也就是它向左移会与那么多个\(B\)交换
设\(br_i\)表示\(i\)(为\(R\))位置以右有多少个\(B\),同上
设\(rl_i\)表示\(i\)(为\(B\))位置以左有多少个\(R\),同上
设\(rr_i\)表示\(i\)(为\(B\))位置以右有多少个\(R\),同上
则把\(i\)位置以前的\(R\)(包含\(i\))全部向左移的代价是\(sl_i=\displaystyle{\sum_{j=1}^i}[str_j=\text{'R'}]\cdot bl_j\)
把\(i\)位置以后的\(R\)(不包含\(i\))全部向右移的代价是\(sr_i=\displaystyle{\sum_{j=i+1}^n}[str_j=\text{'R'}]\cdot br_j\)
这两句话的意思是向两边挤的最小交换次数就是由两边向内,每个\(R\)都与其外侧的'B'交换,用这种顺序是可以实现的
则我们得出\(val_{l,x,r}=\\sl_x-sl_{l-1}+\\bl_{l-1}\cdot(ll_x-ll_{l-1})+\\sr_{x+1}-sr_{r+1}+\\br_{r+1}\cdot(lr_{x+1}-lr_{r+1})\)
四堆分别表示\([l,x]\)全部移到\([1,n]\)最左边的代价
\([l,x]\)已经在\([l,r]\)最左边再移到\([1,n]\)最左边的代价
\([x+1,r]\)全部移到\([1,n]\)最右边的代价
\([l,x]\)已经在\([l,r]\)最右边再移到\([1,n]\)最右边的代价
相当于抵消了多余贡献
这样我们就可以求出每个循环同构串在特定断点的答案了
但这样是\(O(n^2)\)的
发现在同一循环同构串内答案是单峰的且随循环同构串后移答案下标单调
考试时未证明
\(\color{#0080ff}{\underline{Code}}\)
法二:
参见\(ppt\)
中位数思想,也可以证明上面的单调性
\(\color{#0080ff}{\underline{Code}}\)
【第二题】
【简述】
有一颗初始的树,求按照一定顺序删边每个时刻每棵树直径的乘积
【题解】
化删边为加边,问题变成合并两个联通快直径,有一个结论,这个新直径两端一定均为原两直径两端
因此在原树上跑\(ST\),就可以\(O(1)\)求两点距离
实现中用并查集记录连通块直径以及两端,用乘法逆元抵消合并前两直径
\(\color{#0080ff}{\underline{Code}}\)
【第三题】
求序列的划分方案使得同一段
【第四题】
\(\color{#0080FF}{\underline{\large{题面}}}\)
【简述】
求有多少个子矩阵满足和为\(k\)的倍数
【题解】
不妨思考一个简单易懂的问题
求一个一维的序列有多少个子串和为\(k\)的倍数
我们将其转为前缀和\(\{sum\}\)(含\(sum_0=0\))后就是问有多少对数的差为\(k\)的倍数(有序)
也就是有多少对数的差为\(0\)在\(\mod k\)剩余系下
这个问题我们可以轻易做到\(O(n^4)\)即把一个矩阵的和真的算出
有一道题用了一个转化可以参考最大子矩阵
就是我们把同样\((y1,y2)\)的矩阵系看成一些数,即我们确定两条线确定矩阵的上下边界的那些矩阵
问题就转化为了简单的一维问题
\(\color{#0080ff}{\underline{Code}}\)
【第五题】
\(\color{#0080FF}{\underline{\large{题面}}}\)
【简述】
每个关键点可以覆盖与它距离\(\le~k\)的点,求覆盖整棵树最少需要选多少个关键点
【题解】
\(k=1\)是经典的看点\(dp\)
\(k=?\)一个玄妙的贪心
我们从叶子节点至下向上,记录两个值\(maxl_i\)表示在\(i\)的子树中没有被覆盖的点最大距离、
\(dis_i\)表示被\(i\)的儿子覆盖的最大剩余距离,也就是\(i\)的某个孙子管了\(i\)后还剩的距离
如果\(maxl_i\le dis_i\)就是被覆盖了的
而相反就将\(i\)置关键点
特殊情况就是如果\(1\)没有被覆盖其实被少覆盖所有剩余包含\(1\)的连通块要多选一次
\(\color{#0080ff}{\underline{Code}}\)
【第六题】
懒标记思想
、 、、、、、、、、、、、、、、、、、、、、、、、、 、 、 、、 、、
\(\color{#0080FF}{\underline{\large{题面}}}\)
这道题没有翻转的话就是很简单的双向链表(还是写错了)
\(UPD\)双向链表模板
inline void Link(re int x,re int y){p[x].r=y;p[y].l=x;}
inline void Cut(re int x,re int y){p[x].r=p[y].l=0;}
考虑左右光标错位前一定到达过\(l-r\)或\(r-l\)的位置,因此在\(Link\)中加一句话维护左光标是否在右光标左
if(x==l&&y==r)flag=1;if(x==r&&y==l)flag=0;
很好实现几个除了翻转的操作
inline char Del(re int x){
re int rs=p[x].r,rrs=p[rs].r;
if(rs==t)return 0;
Cut(x,rs);Cut(rs,rrs);
Link(x,rrs);
return 1;
}
inline char goleft(re int x){
re int ls=p[x].l,rs=p[x].r,lls=p[ls].l;
if(ls==s)return 0;
Cut(lls,ls);Cut(ls,x);Cut(x,rs);
Link(lls,x);Link(x,ls);Link(ls,rs);
return 1;
}
inline char goright(re int x){
re int ls=p[x].l,rs=p[x].r,rrs=p[rs].r;
if(rs==t)return 0;
Cut(ls,x);Cut(x,rs);Cut(rs,rrs);
Link(ls,rs);Link(rs,x);Link(x,rrs);
return 1;
}
\(\color{#ff0000}{\large{\text{血的教训}}}\)\(\color{#ff0000}{\text{:对两个光标一定要分别建点,因为如果不建,当两个光标重合时就无法判断它到底还有几个位置可以走,}}\)
对于\(Reverse\)操作:
考虑暴力:要翻转\([x,y]\)的节点时,只有\(x,y,l_x,l_y\)的前驱后继值会被真正改变,其余都是交换前驱后继即可
我们可否利用懒标记思想呢,答案是可以的,考虑
我们其实只需要修改\(lr,rl\)的前驱后继为\([0,r],[l,0]\)
在调用\(l\)的后缀的后缀或\(r\)的前缀的前缀时我们就会发现它的前缀的后缀或后缀的前缀不等于自己因此交换即可
inline void pushdown(re int x){
re int ls=p[x].l,rs=p[x].r;
if(p[ls].r!=x)swap(p[ls].l,p[ls].r);
if(p[rs].l!=x)swap(p[rs].l,p[rs].r);
}
注意一下这个\(pushdown\)一定是由外向内的
因此如果有本来在\([l,r]\)中但要出去的都要先\(pushdown\)才能得到正确前驱后继
\(\color{#0080ff}{\underline{Code}}\)
【总结】
本次考试并未涉及到任何高级数据结构虽然可以用来骗分
但考察了一些经典思想:中位数思想,延迟标记思想