D 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然后做转移, 发现并没有办法转移
题解 AmShZ 一样的, 但是说是可以证明均摊更新是常数倍?
对于给定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)
Koosha_Mv 一个由升序和降序列合成的序列,当且仅当它不含 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define rep(i,a,n) for (ll i=a;i<(ll)n;i++) #define per(i,a,n) for (ll i=n;i-->(ll)a;) #define pb push_back ll read () {ll r;scanf ("%lld" ,&r);return r;} const int N = 2e5 + 10 ;int n;int a[N]; int inc0[N]; int dec0[N]; int f[N]; ll ans; void udec0 (int i) { if (n >= i){ int inc1 = max ( (dec0[i - 1 ] < a[i]?a[i - 1 ]:0 ), (a[i - 1 ] < a[i] ? inc0[i - 1 ]: 0 )); int dec1 = min ( (a[i] < inc0[i - 1 ]?a[i - 1 ]:n+1 ), (a[i] < a[i - 1 ] ? dec0[i - 1 ]: n+1 )); if (!(inc1 == inc0[i] && dec1 == dec0[i])){ inc0[i] = inc1; dec0[i] = dec1; f[i] = 0 ; if (dec1 <= n || inc1) udec0 (i+1 ); } } f[i - 1 ] = f[i] + 1 ; } int main () { n = read (); rep (i,1 ,n+1 ) a[i] = read (); per (i,1 ,n+1 ) { inc0[i] = n + 1 ; dec0[i] = 0 ; udec0 (i + 1 ); ans += f[i]; } printf ("%lld\n" , ans); return 0 ; }
3412,2143的
https://codeforces.com/contest/1693/submission/161132167
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define rep(i,a,n) for (ll i=a;i<(ll)n;i++) #define per(i,a,n) for (ll i=n;i-->(ll)a;) #define pb push_back ll read () {ll r;scanf ("%lld" ,&r);return r;} const int N = 2e5 + 10 ;int INF = 0x3f3f3f3f ;struct Fenwick { const int n; vector<int > a; Fenwick (int n) : n (n), a (n, INF) {} void setMin (int x, int v) { for (int i = x + 1 ; i <= n; i += i & -i) a[i - 1 ] = min (a[i - 1 ], v); } int get (int x) { int ans = INF; for (int i = x; i > 0 ; i -= i & -i) ans = min (ans, a[i - 1 ]); return ans; } }; int n;int a[N]; int min_r[N];ll ans; stack <int > sk; vector <int > vec[2 ][N]; void find_3412 () { Fenwick f (n+10 ) ; rep (i,0 ,2 ) fill (vec[i]+1 ,vec[i]+n+1 ,vector <int >()); sk = {}; rep (i,1 ,n+1 ) { while (!sk.empty () && a[i] < a[sk.top ()]) sk.pop (); if (!sk.empty ()) vec[0 ][sk.top ()].push_back (i); sk.push (i); } sk = {}; per (i,1 ,n+1 ) { while (!sk.empty () && a[sk.top ()] < a[i]) sk.pop (); if (!sk.empty ()) vec[1 ][sk.top ()].push_back (i); sk.push (i); } per (i,1 ,n+1 ){ for (auto ind : vec[0 ][i]) f.setMin (a[ind], ind); for (auto ind : vec[1 ][i]) min_r[ind] = min (min_r[ind], f.get (a[ind])); } } int main () { n = read (); INF = n+1 ; rep (i,1 ,n+1 ) a[i] = read (); fill (min_r, min_r + n + 2 , INF); find_3412 (); rep (i,1 ,n+1 ) a[i] = n + 1 - a[i]; find_3412 (); per (i,1 ,n+1 ) { min_r[i] = min (min_r[i], min_r[i + 1 ]); ans += min_r[i] - i; } printf ("%lld\n" , ans); return 0 ; }
E 题目 https://codeforces.com/contest/1693/problem/E
n+2 长度数组a
首尾元素值为0
最小操作次数让 所有值 = 0
每次操作可以任选以下一种
最左的最大值=它的左侧的最大值
最右的最大值=它的右侧的最大值
范围 n 2e5
2s
256mb
题解 我的思路 值挺友好,给你的是[0,n]的范围,(就算很大也可以手动离散
没思路了
ecnerwala 官方的代码实在太长了
ecnerwala 有个超短的代码
https://codeforces.com/contest/1693/submission/160890042
有点延后判断, 贪心选最小值的意味
1 2 3 4 5 6 7 8 9 10 11 12 0 1 4 2 4 0 2 0 . . . . . . . . // 初始 . . ? . ? . . . // 最大的标记为?, 贡献 +2, 意义是第一轮处理2个 . . . x x x x . // 准备处理2的也就是x覆盖区间的, 把区间左侧问号标成 <(表示这个?位置的值比当前小,和左侧的最值相等, 右侧标成 > (同理 . . < . ? . . . // 标记 . . < ? ? . ? . // 对2处理, 贡献 +3 . . ? > > . > . // 同理对于1, 但注意到 1右侧的 < 会变成? 因为 . ? ? > > . > . // 贡献+2 // 0 不需要处理
总贡献是2+3+2 = 7
样例2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0 1 3 5 4 2 0 . . . . . . . . . . ? . . . // +1 . . . < . . . // . . . < ? . . // +1 . . . ? > . . // . . ? ? > . . // +2 . . < < ? . . // . . < < ? ? . // +2 . . ? ? > > . // . ? ? ? . . . // +3
1+1+2+2+3 = 9
再补充一个例子 1 2 3
1 2 3 4 5 6 7 8 9 10 0 1 2 3 0 . . . . . . . . ? . // +1 . . . > . // . . ? > . // +1 . . > > . // . ? > > . // +1
1+1+1 = 3
总的来说, 每轮最大值,确定覆盖区间
区间左侧:
区间内部
区间右侧
最后最大值的所有点都是? , 统计?个数即可
实现
并不需要真的像上面思路那样维护4种 . , ? , > , <的状态
发现其实只需要统计?的个数
那么?个数有多少呢
区间内,所有大于它的都变成了问号, 所以区间内就是大于它的个数
区间左侧, 可能有 >,?,<
但 注意到一旦出现 > ,说明上一轮 > 的左侧有?, 如果出现 < 说明上一轮右侧有 ?
引理, 每轮结束后 除开.的情况,剩下的一定是 < ? > 形状的
归纳法可证明
因此, 你需要统计的是
相交关系
1 2 3 4 5 ? ? // 上一轮结果 < [l...r] > // 上一轮结果 [l...r] // 本轮 ? ? // 本轮 < [l...r]> // 结果
非相交关系
1 2 3 4 5 ? ? // 上一轮结果 < [l...r] > // 上一轮结果 [l...r] // 本轮 ? ? // 本轮 < [l ...r]> // 结果
有
1 2 newl = min(l, lastr) newr = max(r, lastl)
区间统计点 = 前缀差 = 树状数组维护
代码 ecnerwaia https://codeforces.com/contest/1693/submission/160890042
基于修改+注释+自己格式+bit改为fenwick: https://codeforces.com/contest/1693/submission/161139663
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <bits/stdc++.h> using namespace std; typedef long long ll;#define rep(i,a,n) for (ll i=a;i<(ll)n;i++) #define per(i,a,n) for (ll i=n;i-->(ll)a;) ll read () {ll r;scanf ("%lld" ,&r);return r;} int main () { int n = read (); vector<vector<int > > idxs (n+1 ); rep (i,1 ,n+1 ) idxs[read ()].push_back (i); vector<int > fenwick (n) ; ll ans = 0 ; int lo = 1 , hi = n + 1 ; per (v, 1 , n+1 ) { if (idxs[v].empty ()) continue ; std::tie (lo, hi) = make_pair ( min (idxs[v].front (), hi), max (idxs[v].back ()+1 , lo) ); for (int a : idxs[v]) { for (; a <= n; a += (a&-a)) fenwick[a-1 ]++; } for (int a = hi-1 ; a; a -= (a&-a)) ans += fenwick[a-1 ]; for (int a = lo-1 ; a; a -= (a&-a)) ans -= fenwick[a-1 ]; } printf ("%lld\n" , ans); return 0 ; }
F 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <bits/stdc++.h> using namespace std;#define rep(i,a,n) for (int i=a;i<(int)n;i++) #define per(i,a,n) for (int i=n;i-->(int)a;) int read () {int r;scanf ("%d" ,&r);return r;} char s[200010 ];int pre[200010 ]; int pos[200010 ]; int w () { int n = read (); fill (pos,pos+n+1 ,-1 ); scanf ("%s" ,s); int cnt[] = {0 ,0 }; rep (i,0 ,n) cnt[s[i] == '1' ] ++; if (cnt[0 ] > cnt[1 ]) { swap (cnt[0 ],cnt[1 ]); rep (i,0 ,n) s[i] = (1 - (s[i] - '0' )) + '0' ; rep (i,0 ,n/2 ) swap (s[i],s[n-1 -i]); } int d = cnt[1 ] - cnt[0 ]; rep (i,0 ,n) pre[i+1 ] = pre[i] + (s[i] == '0' ? -1 : 1 ); rep (i,0 ,n+1 ){ if (pre[i] < 0 ) continue ; if (pos[pre[i]] != -1 ) continue ; pos[pre[i]] = i; } int minv = d; per (i,-cnt[0 ],d+1 ){ if (pre[n - (d-i)] != i) break ; minv = i; } int ans = 0 ; while (minv + cnt[0 ] > 0 ){ if (minv < 0 ) return ans + 1 ; ans ++ ; minv -= ((n-(d-minv))-pos[minv])/2 ; } return ans; } int main () { int t = read (); while (t--) printf ("%d\n" ,w ()); return 0 ; }
总结 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 和二维图
参考 官方
ecnerwala E