nowcoder 牛客挑战赛61 D
D https://ac.nowcoder.com/acm/contest/11201/D
总思路没问题, dijkstra不能带log, 2s时间卡时限了
没有log的也800ms, 稍微一个3倍都过不了
所以需要的是点的值要么是初始的要么是推导(当前最大值-定值k)生成的, 双队列+指针实现
没过这题也能+3
代码
1 |
|
D https://ac.nowcoder.com/acm/contest/11201/D
总思路没问题, dijkstra不能带log, 2s时间卡时限了
没有log的也800ms, 稍微一个3倍都过不了
所以需要的是点的值要么是初始的要么是推导(当前最大值-定值k)生成的, 双队列+指针实现
没过这题也能+3
1 | #include <bits/stdc++.h> |
https://codeforces.com/contest/1693/problem/D
给你一个排列
问多少个子区间 可以表示成 增序列和减序列合成的, 称作Decinc
n 2e5
2s
512MB
如果是判断一个是否合法
考虑
inc[i] 表示i在增序列, 减序列的最大值
dec[i] 表示i在减序列, 增序列的最小值
然后dp一下O(n) 就做了
然后这里考虑有没有办法转移
因为如果[i..j] 是decinc的,那么它的所有子区间也是
考虑有没有办法dp然后做转移, 发现并没有办法转移
一样的, 但是说是可以证明均摊更新是常数倍?
对于给定i, 找最大的j, 使得 j < i, a[j] > a[j+1]
注意到a[j],a[j+1]
不会都在增序列里,必定一个在减序列里
情况1 a[j+1]
在增序列的话 => a[j]
在减序列
情况2 a[j+1]
在减序列
且因为它是最大的j, 所以a[j] > a[j+1]
, 且a[j+1] < a[j+2] < a[j+3] < ... < a[i]
inc[i] = a[j](情况1) or a[j+1](情况2) or 0
而对于inc[i]
初始化是是inf
, 而对于a[j+1]..a[i]
这一段都是inf
所以每个位置的值只会有4种情况
dec对称关系同理
换句话说
l从大到小,
每轮从小到大, 如果更新才会去算下一个位置, 否则提前退出
这里还有一点是就是 运算时,当给定l的时候, dp[i]仅依赖于dp[i-1]和a[i], 所以说如果dp[i]没有更新,则i以后的也不会更新, 所以更新的一定是连续的区间
所以sum 遍历 = sum 更新次数 = sum变化次数 = O(n)
一个由升序和降序列合成的序列,当且仅当它不含 3412 也不含 2143
显然包含则不满足
怎么证明不满足一定包含这样的呢
回到dp的过程, 如果刚好在i不满足,
那么, 如果 a[i-1] < a[i], (a[i-1] > a[i] 对称同理
显然a[i-1] 在增序列不合法, (如果a[i-1] 在增序列有合法方案,那么把a[i]放到增序列即可
a[i-1]在减序列, 且 增序列最小值 > a[i]
所以 存在a[j] > a[i] > a[i-1], j < i-1
所以原序列是由
增序列 ..... a[j]
和 减序列.... a[i-1]
合成的
因为a[j] 是满足的最小的a[j]
也就是, a[j] 不能放进减序列里(如果可以则能得到更小的增序列值
那么 不妨设下标 w(可能不存在) < j < k ,且 a[k] 在减序列中, a[w] 在减数列中
那么a[j] < a[k] (j k i-1 i => 3 4 1 2)
或 a[j] > a[w]
(a[j] 左侧至少一个
考虑把a[j]左侧分成三部分讨论, 大于a[j]的, a[i]到a[j]之间的, 小于a[i]的
如果a[i]到a[j]之间存在(3 4 1 2)`, 否则完全不存在, 且 小于a[i] 至少一个
如果大于a[j]的存在,则一定全属于减序列
如果小于a[i]的有不只1个, 那么一旦有其中两个递减 => (? ? j i) => (2 1 4 3)
即它的对称状态
小于a[i]的一定是升序列
总上可以重组 升序列a[j] 左侧,小于a[i], 包含a[w] .... a[j]
, 降序列a[j]左侧大于a[j]...a[j]右侧原减序列
注意到这样重组以后, a[j]
可以被放入减序列, 而增序列最小值将不再是a[j]
性质充要得证
如何实现
注意到3412和2143是对称的,所以a[i] = n+1-a[i] ,再跑一次3412就行
那么如何计算3412
考虑计算的结果是对于当前位置i作为3,min_r[i]
表示最近的2
让3412
出现
给定3以后,4要是最近的,如果有2,那么1是离2最近的
所以先预处理每个位置后面和它最近的比它大的,以及每个位置前面最近的比它小的的位置
但是记录并不是[3]->4
, [2] -> 1
考虑反着来4-> array{3}
, 1->array{2}
为什么要这么样做呢,因为除了大小关系还有顺序,1
需要在4
的右侧
那么我们倒着遍历4的位置
我们可以用fenwick记录i右侧, (1,2) 存在的[值]->2的坐标
这样我们对于每个3, 去fenwick上查, 值 < 3的值中存在的最大坐标, 就算出答案了
https://codeforces.com/contest/1693/submission/160996027
1 | #include <bits/stdc++.h> |
3412,2143的
https://codeforces.com/contest/1693/submission/161132167
1 | #include <bits/stdc++.h> |
https://codeforces.com/contest/1693/problem/E
n+2 长度数组a
首尾元素值为0
最小操作次数让 所有值 = 0
每次操作可以任选以下一种
n 2e5
2s
256mb
值挺友好,给你的是[0,n]的范围,(就算很大也可以手动离散
没思路了
官方的代码实在太长了
ecnerwala 有个超短的代码
https://codeforces.com/contest/1693/submission/160890042
有点延后判断, 贪心选最小值的意味
1 | 0 1 4 2 4 0 2 0 |
总贡献是2+3+2 = 7
样例2
1 | 0 1 3 5 4 2 0 |
1+1+2+2+3 = 9
再补充一个例子 1 2 3
1 | 0 1 2 3 0 |
1+1+1 = 3
总的来说, 每轮最大值,确定覆盖区间
区间左侧:
1 | ? 变 < |
区间内部
1 | < 变 ? |
区间右侧
1 | ? 变 > |
最后最大值的所有点都是? , 统计?个数即可
实现
并不需要真的像上面思路那样维护4种 . , ? , > , <的状态
发现其实只需要统计?的个数
那么?个数有多少呢
区间内,所有大于它的都变成了问号, 所以区间内就是大于它的个数
区间左侧, 可能有 >,?,<
但 注意到一旦出现 > ,说明上一轮 > 的左侧有?, 如果出现 < 说明上一轮右侧有 ?
引理, 每轮结束后 除开.的情况,剩下的一定是 < ? > 形状的
归纳法可证明
因此, 你需要统计的是
1 | ? ? // 上一轮结果 |
1 | ? ? // 上一轮结果 |
有
1 | newl = min(l, lastr) |
区间统计点 = 前缀差 = 树状数组维护
ecnerwaia https://codeforces.com/contest/1693/submission/160890042
基于修改+注释+自己格式+bit改为fenwick: https://codeforces.com/contest/1693/submission/161139663
1 | #include <bits/stdc++.h> |
https://codeforces.com/contest/1693/problem/F
0/1 字符串 S
每次选择一段 sort, 代价 |cnt(1) - cnt(0)| + 1
求最小总代价,让整个S有序
n 2e5
假设最后一次操作了 [l..r]
那么说明 操作之前, [0..l-1] 和目标一样[r+1..n-1] 和目标一样
并且[l..r]中的1和0的个数尽可能的靠近
结论: 只对0和1个数相等的进行排序
证明:
若最优方案中, 对[l,r]排序,且区间中,0比1多d个(d > 0), 代价d+1
如果l上是0, 只需要对[l+1,r]排序,代价为d, 且效果相同, 所以l上一定是1
确定区间左端点,右端点增加时,0和1的差的变化为+1/-1
因此必然存在k < r, 区间 [l,k] 的0和1的个数相等
排序[l,k],代价1, 再排序 [l+1,r] 代价 = d, 总代价 = d+1
所以任何一个0比1多排序, 可以拆成 (0和1相等的排序,代价1) + (0和1的差更小,更短,比原来代价更小的排序)
对于1比0多的情况, 对称关系同理可证
得证
问题变成如何选尽量少的0和1相等区间排序
把0换为-1
又变成经典的,前缀和2维图形化了, 每次选择的是等高的点, 让等高点之间变成 V字形
假设1比-1多,那么也就是结束点比起点高, 如果最后一段是从一个和起点相等的值 一直+1达到 结束点的,那么 把起点和这个值的区间处理即可
所以就是让最后一个连续+1 到的结束点 的那一串尽量长
我们记录达到每个值的首先出现的点, 只考虑(>=0的部分) 显然随着值越大,下标大,( 因为是从0涨过来的
而我们对末端的操作不会改变这个首次出现的位置
贪就完了
-1 比 1多 对称处理即可, 这里只要方案数不要操作细节,(所以还可以把 1变-1,0变1,并旋转字符串)
样例输入1的最后一个数据
这个和上面假设相反, 那就是 把头部可达的值的最小值的最后出现位置之间做区间处理
当然也可以双指针, (总代价移动是线性的
https://codeforces.com/contest/1693/submission/162002156
1 | #include <bits/stdc++.h> |
C
Dijkstra 性质还是不够熟啊
D
直接的dp能想到,也在想转移,倒是转移也可以倒着做, 而且需要推导这个变化的条件,从而得到必定是区间变化,有遍历次数=变化次数=可能次数
另一个方案, 我有大的方向, 说看能不能找不成立的,但是没有得到3412/2143, 一个是这个充要真不知道比赛能不能快速证明,
再就是3412, 就算我知道了, 也不知道怎么去算, 这个按中间位置做遍历, 预处理 两头算是又学了一手
E
总觉得好像见过类似的标记处理, 这里是标记+延后+贪心
哦 像python里的 a,b= b,a+b 可以写成 std::tie(a,b) = make_pair(b,a+b)
原来树状数组还有bit和fenwick写法区别
bit版本的是 a|=a+1
fenwick的是 a+=(a&-a)
逻辑上 bit版本,统计的是末尾连续1的所有子集或上高位1的信息
而fenwick是当前结束向前(a&-a)长度的信息
F
敢于去猜让解答变容易的特殊情况,并证明它
经典的0/1区间个数相等处理, 变成-1,1 和二维图
我的第二次 2-SAT 练习
https://codeforces.com/contest/1697/problem/F
给你m个限制, 分别可能是
请构造一个满足限制的长n的数组a, 且每个元素在$[1,k]$之间
n 2e4
m 2e4
k 10
2s
512MB
一眼2-sat,写过但不熟, 来看看题解如何建立图的
tourist ,jiangly也是拖的板子, XD, 看来我要好好准备板子了?
一个值拆成2k个点
分别是$\le 1,\le 2,\cdots,\le k,>1,>2,\cdots,>k$
其中$\le i, > i$ 是一个互补对
$(i,v,0) = a_i \le v$
$(i,v,1) = a_i > v$
因为2-sat 就是每个点选或不选 0/1, 而上面的两个必定一个满足一个不满足
$(i,v,0) \to (i,v+1,0)$ 不等式关系
$(i,v,1) \to (i,v-1,1)$ 不等式关系 (可以由上面自动建立对称关系
$(i,v,1) \to (i+1,v,1)$ 非严格单增
$(i,x,0) \to (i,x-1,0)$, $a_i \ne x$
$(i,x-1,1) \to (i,x,1)$, $a_i \ne x$ (可以由上面自动建立对称关系
$(i,v,1) \to (j,x-a_i-1,0)$, $a_i + a_j \le x$, 轮换$i,j$
$(i,v,0) \to (j,x-a_i-1,1)$, $a_i + a_j \ge x$, 轮换$i,j$
对于限制必定不合法的$(i,v,x)$ , 建立 $(i,v,x) \to (i,v,x\oplus 1)$
之前有个问题, 我一直没想太通, 现在有点思路了
假设我们完成建边和tarjan的部分
如下, 这样那怎么顺序选都没问题
1 | a0 <-> b0 |
而对于这种, 你是不能去选b1/c1 的,而这也是Tarjan 不会处理的,因为tarjan只是合并联通块, 这种还算有答案
1 | a0 <-> b0 |
这种是没有答案, 而且tarjan 的时候是判断不了的
1 | a0<->b0 |
那么两个办法我想的
方法一
如果我们要加 $a[x] \to b[y]$
考虑 如果 $b[y\oplus 1]$ ,那么a不能选x, 所以同时会产生$b[y\oplus 1] \to a[x\oplus1]$
这个好处是,本身可以利用tarjan
方法二
在tarjan处理完scc后, 对scc的每个点的反点
做并查集, 缺点是还要跑并查集
等价性 一个奇怪的视角可以证明就是 这些操作是对称性的, 比如方法一里面每次都是对称加的边, 而方法二,不妨设scc 中的反点个数比它大,那么scc必定会合其它scc连接,最终所有并查集完成后, scc和scc反点的scc个数相等
这两个任选一种以后, 最后对scc/并查集 做原图的反向边 做倒序拓扑选择,必定有解?
再看上面的 3个例子
第一个不变
1 | a0 <-> b0 |
第二个变成如下,你需要反向拓扑选择
1 | a0 <-> b0 <-> c0 |
第三个则全部连到一个并查集里了, 直接确定不合法了
第三个是限制的不可选状态
比如 $(i,x)$ 不可选, 之前的办法是做一个 失败的节点,让它和这个节点双向连通
而现在发现其实$(i,x) \to (i,x\oplus 1)$, 因为这样如果选$(i,x)$自动造成矛盾
注意区别是不可选
还是选了一定满足
我先自己写了个twosat的板子,下次也可以用
https://codeforces.com/contest/1697/submission/160657743
1 | #include <bits/stdc++.h> |
这里的问题就是说 选则一个范围的值,怎么变成2-sat需要的 真/假
这里的办法是拆成大于小于
当然从逻辑上 用 $= v$ 和$\ne v$ 也可以, 但是这样的问题是, 在做上面的和的不等式的关系时, 边的量就很大了, 不是k条边了
之前2-sat 还有点问题,缺少了一些反向的连接,和缩点后的反向拓扑序选择
准备准备2sat的板子了
G(倍增)
https://atcoder.jp/contests/abc254/tasks_print
n个楼, 每个楼高都是1..1e9
有m个电梯, 每个电梯在一个楼里,负责[li,ri] 之间
可以允许这个楼里, [li,ri] 范围中的移动
跨楼同楼层移动代价1
同楼电梯内移动,代价 高度差
q个询问, ai楼bi层 到 ci楼di层最小代价, 或它们不可达
n 2e5
m 2e5
q 2e5
6s
1024mb
显然 同楼层答案是1或0
不同楼层,只会单调移动,不会先上再下这种, 答案 = 距离+跨楼数, 需要最小化跨楼数
而每次移动贪心距离, 这样模拟可做, 但是复杂度无法接受
显然同楼的重叠电梯可以合并
显然上下还满足对称性, 那么只考虑从下向上
合并同楼重叠电梯(这样做了以后剩下的线段就不用考虑属于哪个楼了? 因为是一旦重叠肯定不重楼
如果 ai楼内bi移动到最高位置, ci 楼内 di 移动到最低位置, 合法提前输出
dp[i][j]
当前建筑第i层,用j个电梯后最高能到达的楼层
考虑 离散+倍增
我写的按右断点跳右端点的map不知道为什么WA了除了测试点, 调了七八个小时,atcoder还没数据, 放弃了
下面是一个别人的代码,我改了部分格式,靠清理了一些线段,保持左右端点都严格单调递增, 然后用线段跳线段来做的, 我觉得完全一样啊, 吐了, 搞不动了,心态炸了
1 | #include <bits/stdc++.h> |
我的代码 不知道WA在哪里了
1 | #include <bits/stdc++.h> |
G
倍增, 编码速度也是问题, 写几个小时还在wa,哎
主要问题不在算法,在自己写bug
https://www.codechef.com/submit-v2/PRFSUFDSTNCT
一个数组, 的前缀i个数,不重复的数值个数为pi
后缀i到结束,不重复的数值个数为si
给你p和s,问是否存在数组满足, 返回是否即可
n 1e5
1s
首先最直接的,
p[0] = s[n-1] = 1
p[n-1] = s[0]
然后 p的增量为0/1,s的增量为0/-1
再因为每个值至少贡献1次
所以如果p[i] + s[i+1] == p[n-1], 那么说明 i 和i+1 可以切开,并且这位置p增量为1,s增量为-1
对于切开后的每一段 找到变化的位置(增量为1/-1)的位置
分别跑后缀的前缀 应该小于等于前缀(与前缀首个的差)
和 前缀的后缀 应该小于等于后缀(与后缀最后一个的差)
反而没有切割的操作,上面几个倒是有
官方 判断了个a[i]+b[i] <= a[n-1]
跟我切割操作有点像,但是 不是错位的判断
原理和我那个也是类似,所有数贡献一次,左右统计的第i个两侧都有贡献,所以至少是a[n-1]+1
–
分析的是同一位的(p[i]-p[i-1],s[i]-s[i+1]) 的四种情况
1,1 原数组中唯一的值, 不需要额外判断, 甚至可以考虑原数组删除它
0,1 和 1,0 是对称的, 如果全部都是这两个
那么1,0 的出现意味着 右侧会有一个0,1 也就是从后缀上这个数首次出现的
可以看成1,0左括号,0,1右括号做括号匹配, 但不完全是相邻位置的, 如 (()), 可以1和3,2和4配
0,0 说明没有变化,应该被包含在某个值中, 如果记作.
那么(.)(.)
是好的, 而().()
,0,0无值可选
如此检查
(.)(.)
如
1 | p 111222 |
正好一个答案是111222
().()
如
1 | 11122 |
再换句 (, 上面+1, 右括号下面后一个-1, 所以考虑上下的总和变化, 出现问题就是 a[i]+b[i] <= a[n-1]
看了一下应该是有真的代码被我判否了, 因为我把答案逻辑塞到我代码后面return false也不对
最后发现是因为我代码 if 的l和r复制漏改了
按照我的逻辑AC的代码
https://www.codechef.com/viewsolution/66653363
1 | #include <bits/stdc++.h> |
BUG 是我AC失败一个重大阻碍
题解的转化我也是没想到的学习了