https://atcoder.jp/contests/abc262/tasks
F - Erase and Rotate 给一个长n排列A
问不超过k次操作后能得到的最小字典序列
每次操作 可以删除一个数, 或循环右移1个单位
范围 n 2e5
k [0,n-1]
2s
1024mb
我的思路 考虑两种一个是不移动, 从左删除, 那么就是前 k个中取最小, 然后 后面塞入一个, minPQ维护
右侧移动, 也可以确定 首个数字是啥
问题是 当右侧移动时, 可能和删除是同一个, 这个感觉没啥简单的想法去记录
比如样例数据三
如果看成先移动,再删除, 其实会删除移动过的数字
不知道能不能移动+标记+贪心局部?
然后因为k可能比较大,可以从左侧删除和右侧移动 来得到, 所以考虑两个方向来算, 算完了比较?
代码 https://atcoder.jp/contests/abc262/submissions/35554024
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 #include <bits/stdc++.h> using namespace std;#define rep(i,a,n) for (int i=a;i<(int)n;i++) template <typename T> using minPQ = priority_queue<T,vector<T>, greater<T>>; int read () {int r;scanf ("%d" ,&r);return r;}vector<int > calc (vector<pair<int ,bool >>&A,int del) { vector<int > ret={}; minPQ<pair<int ,int >> vi; int li=0 ; int ri=-1 ; auto moveri=[&](int d){ while (d>=0 ) { if (++ri>=(int )A.size ())break ; if (!A[ri].second)d--; vi.push ({A[ri].first,ri}); } }; moveri (del); while (ri<(int )A.size ()){ auto [v,i]=vi.top (); vi.pop (); if (i<li)continue ; ret.push_back (v); li=i+1 ; if (!A[i].second)moveri (0 ); } return ret; } void p (const vector<int >&A) {rep (i,0 ,A.size ())printf ("%d%c" ,A[i]," \n" [i+1 ==(int )A.size ()]);}int le (const vector<int >&A,const vector<int >&B) { int n = min (A.size (),B.size ()); rep (i,0 ,n)if (A[i]!=B[i]) return A[i]<B[i]; return A.size ()<B.size (); } int main () { int n = read (); vector<int >a (n); int k = read (); rep (i,0 ,n) a[i]=read (); if (k==0 ){ p (a); return 0 ; } int li=0 ; rep (i,0 ,k+1 )if (a[i]<a[li])li=i; vector<pair<int ,bool >>al={}; rep (i,li,n)al.push_back ({a[i],false }); auto ansl=calc (al,k-li); int ri=n-1 ; rep (i,0 ,k) if (a[n-1 -i]<a[ri])ri=n-1 -i; vector<pair<int ,bool >>ar={}; rep (i,0 ,n)ar.push_back ({a[(ri+i)%n],ri+i<n}); auto ansr=calc (ar,k-(n-ri)); p (le (ansl,ansr)?ansl:ansr); return 0 ; }
G - LIS with Stack 空序列X
空栈S
长n序列A
i=1->N, 每次把S.push(Ai) 或 A中移除
当S非空时, 可以把S顶部移动到X尾部
分数= 若X非严格单调递增, 则为X的元素个数
求最大分数
范围 n 50
ai [1,50]
2s
1024mb
我的思路 感觉说得很玄乎
实际上就是以 i 开始向前连续未取的取反向一段(可以一个也不取
以i+1开始向前连续未取的反向一段
这样拼起来 构成单调递增序列的长度
若 A[i]作为选的第一个, 那么后面[i+1..n-1]所有小于A[i] 的都是一个隔断
而考虑A[i-1] 的值
A[i-1] < A[i]
, 那么必定A[i-1] 要么不选, 要么在A[i] 之前选,
想做一个 dp[i][l] =
从第i个向前反选, 总长为l
的最小最大值
问题是 比如 6 4 2 1 3 5
这样的, 后面的还可能选到更前面的, 光是 长度和最大值是不够的
n 有50 也不可能bitmask
然后考虑说倒着做,
如果 i开始向前选, 那么 单调递增必选, 因为如果不选, 也不可能之前选, 所以要么就不连续
而跨过的比它小的 则标记为前向的必选
这样有一个奇怪的
f(arr,i) = 数组倒着选, 最大的不超过a[i] 的方案数, 考虑最后增加一个无限大
f(arr,i) = max(arr[-1] 非必选 && f(arr[:-1],i), 从arr[-1]倒着选一段 f(arr - 移除, arr[-1]对应的a的下标j) )
这个看起来复杂度巨大, 但似乎当必选的时候 分叉有限 , 但是想加记忆化 也不知道怎么加
不会分析
然后对于相同的时候, 如果都要选,那必然是后面选前面
我好像读错题了………………………………………………………..
是 放入S或者从A中移除, 不是一定放入S
题解 假设如果在操作以前 先把 不会放在X的 提前移除
那么最终S为空
同时, 每次放入S的应该小于S中的所有, 而在X中最后一个元素被放入S时 它是i空的
然后dp
dp[li][ri][lv][rv] =
, 通过用a[li..ri]
且值范围在 lv<=value<=rv 的最大长度
ans=dp[1][N][1][50]
也就变成3种操作
还是放入S
还是把S的出栈 加入X
删除A中的这个元素(不再是放入S, 对那些最终不会进入X的来说)
考虑转移
如果产生的最长序列X没有rv , 那么dp[li][ri][lv][rv] = dp[li][ri][lv][rv-1]
如果产生的最长序列X有rv, 设a[j] = rv, 且对应是X中的最后一个
那么在a[j] 放入S时, S一定为空(上面有说, 因为如果还有其它的话 也会被放入X中(从S最终为空的角度看))
那么考虑a[li...j-1] a[j] a[j+1..ri]
三段对应的X的三段 (<= v) (>= v, <= rv) rv(a[j])
dp[li][ri][lv][rv] = max(1+dp[li][j-1][lv][v]+dp[j+1][ri][v][rv]), v\in [lv,rv]
代码 https://atcoder.jp/contests/abc262/submissions/35555875
O(n^6)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <bits/stdc++.h> #define rep(i,a,n) for (int i=a;i<(int)n;i++) int read () {int r;scanf ("%d" ,&r);return r;}const int N=50 ;int a[N+10 ]; int dp[N+10 ][N+10 ][N+10 ][N+10 ];void setMax (int &a,int b) {a=std::max (a,b);}int main () { int n=read (); rep (i,1 ,n+1 )a[i]=read (); rep (i,1 ,n+1 )rep (l,1 ,a[i]+1 )rep (r,a[i],N+1 )dp[i][i][l][r]=1 ; rep (len,2 ,n+1 )rep (i,1 ,n-(len-1 )+1 )rep (l,1 ,N+1 )rep (r,l,N+1 ){ int j=i+(len-1 ); int &o=dp[i][j][l][r]; if (l!=r)setMax (o,dp[i][j][l][r-1 ]); rep (k,i,j+1 )if (a[k]==r)rep (v,l,r+1 )setMax (o,1 +dp[i][k-1 ][l][v]+dp[k+1 ][j][v][r]); } printf ("%d\n" ,dp[1 ][n][1 ][N]); return 0 ; }
考虑a[i]是否选, 和a[i] 从S出栈的时刻O(n^5)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <bits/stdc++.h> #define rep(i,a,n) for (int i=a;i<(int)n;i++) int read () {int r;scanf ("%d" ,&r);return r;}const int N=50 ;int a[N+10 ]; int dp[N+10 ][N+10 ][N+10 ][N+10 ];void setMax (int &a,int b) {a=std::max (a,b);}int main () { int n=read (); rep (i,0 ,n)a[i]=read (); rep (i,0 ,n)rep (l,1 ,a[i]+1 )rep (r,a[i],N+1 )dp[i][i][l][r]=1 ; rep (len,2 ,n+1 )rep (i,0 ,n-(len-1 ))rep (l,1 ,N+1 )rep (r,l,N+1 ){ int j=i+(len-1 ); setMax (dp[i][j][l][r],dp[i+1 ][j][l][r]); if (a[i]>=l && a[i]<=r)rep (k,i,j+1 )setMax (dp[i][j][l][r],dp[i+1 ][k][l][a[i]]+1 +dp[k+1 ][j][a[i]][r]); } printf ("%d\n" ,dp[0 ][n-1 ][1 ][N]); return 0 ; }
Ex - Max Limited Sequence 问有多少个长n序列A满足
Ai \in [0,M]
和Q个限制 max(A[li..ri]) = Xi
求 个数 mod 998244353
范围 M [1,998244353)
n 2e5
q 2e5
xi [1,M]
我的思路 如何描述 max(a[l..r]) = X
考虑首个=X的位置是i
a[i] = X
a[l..i-1] \in [0..X-1]
a[i+1..r] \in [0..X]
那么无非是 不同的i的选取
问题是 这q很大, 每个这样拆的话, 似乎没法容斥?
题解 若Xi 全都一样 也就是 只是区间限制了
所有被区间覆盖的不超过Xi, 且每个区间至少一个Xi
dp[i][j] =
a的前i个确定时, 且对于所有[L..R] <= i 满足, 最后一个等于X的位置在j的 方案数
虽然这状态数就 n^2, 但通过转移关系发现可以优化
第i个放X: dp[i][i] = sum dp[i-1][...]
第i个不放X: dp[i][j] = dp[i-1][j]*X (R=i的最大的L 满足L<=j) or 0
所以可以滚动掉第一个维度
每次 dp[i] = sum (dp[0..i-1])
然后 把 dp[0..max(L)-1] = 0
注意到 = 0 对于前缀和的贡献是0
所以考虑滑窗+记录和
范围 [j…i-1] 是非0, 和为s
1 2 3 4 5 6 7 8 s表示上一层 [0..i-1]的和 t=表示原始位置上i乘上 t 以后才是这个贡献 dp[i]=s/t/X; // 记录原始值, 因为t会变成t*X newj = max(j,max(L(R==i))) for k = [j..newj-1]: s-=dp[j]*t // *t 才是贡献 t*=X; s*=X; s+=dp[i];
这样的话 时间 空间都是1维的
回到原问题 准备 长n的 数组B 元素全是M
期望把它变成 全部是Ai的上限, 也就是把Q的限制合并成一个总上限, (因为还有等于的限制无法直接用上限表达
这个 也容易算出(其实把max(a[li..ri])=xi 改成 max(a[li..ri]) <= xi 就变成一个简单的黄色(2000分左右)题?
然后 问题变成了 你不再关心上限了, 因为上面合并出的已经包含了上限, 现在关心的只有存在性
也就是问题变成了
A[i] <= B[i] 的情况下, 满足 [Li..Ri] 中存在一个Xi
然后可选的位置也是 其中上限为Xi的位置中
所以不同的Xi 分开考虑
相同的Xi 考虑的位置就是那些限制为Xi的位置
对于每个X来说, 和上面一样的
dp[i][j] =
a的前i个确定时, 且对于所有[L..R] <= i 满足, 最后一个等于X的位置在j的 方案数
(其中注意没有Xi=M的情况
但是转移有区别
dp[i][j]
不再是跟i-1
相关了 而是和上一个限制为X的位置相关, 其它不变
然后另外要注意的是, 上面是考虑R==i时,max(L) 而现在可能 R=i 的那个并不是满足 X的
只考虑Xi=X的所有[L,R]
所以转移也有变化
在i放X dp[i][i] =
要所有 R in [(last i)+1..i-1], 的区间的 最大L, dp[last i][>= max L的 i_L .. last i]
这里可能出现 不存在的情况
看如何逐步变化
1 2 3 4 5 6 dp[i][i] = sum dp[i-1][L限制区间] = s[i-1] for j in 限制区间: dp[i][j] = dp[i-1][j]*x s[i] = sum dp[i][新限制区间] ans = s[last]
s改成变化表达
1 2 3 4 5 6 7 8 9 dp[i][i] = sum dp[i-1][L限制区间] = s[i-1] for j in 限制区间: dp[i][j] = dp[i-1][j]*x s[i] = s[i-1] s[i]-= sum dp[i-1][移除的区间] s[i]*= x s[i]+= dp[i][i] ans = s[last]
乘法和加法顺序交换
1 2 3 4 5 6 7 8 9 dp[i][i] = sum dp[i-1][L限制区间] = s[i-1] for j in 限制区间: dp[i][j] = dp[i-1][j]*x s[i] = s[i-1] s[i]+= dp[i][i]/x; s[i]-= sum dp[i-1][移除的区间] s[i]*= x ans = s[last]
1 2 3 4 5 6 7 8 dp[i][i] = sum dp[i-1][L限制区间] = s[i-1] for j in 限制区间: dp[i][j] = dp[i-1][j]*x s[i] = s[i-1] s[i]+= dp[i][i]/x - sum dp[i-1][移除的区间] s[i]*= x ans = s[last]
令f[i][j]=dp[i][j]/x^i
, 则dp[i][j] = f[i][j]*x^i
令 s[i] = sum dp[i][i+1的L限制区间] = sum f[i][...]*x^{i} = (sum f[i][...])*x^{i}= t[i]*x^{i}
于是t[i] = sum f[i][i+1的L限制区间]
1 2 3 4 5 6 7 8 f[i][i]*x^i = t[i-1]*x^{i-1} for j in 限制区间: f[i][j]*x^i = f[i-1][j]*x^{i-1}*x t[i]*x^i = t[i-1]*x^{i-1} t[i]*x^i += f[i][i]*x^i/x - sum f[i-1][移除区间] *x^{i-1} t[i]*x^i *= x ans = t[last]*x^last
两边移除 通项
1 2 3 4 5 6 7 8 f[i][i] = t[i-1]/x for j in 限制区间: f[i][j] = f[i-1][j] t[i] = t[i-1]/x t[i] += f[i][i]/x - sum f[i-1][移除区间]/x t[i] *= x ans = t[last]*x^last
处理 t=t[i-1]/x, 把数组变成复用的变量
1 2 3 4 5 6 7 8 9 f[i][i] = t[i-1]/x = t for j in 限制区间: f[i][j] = f[i-1][j] t[i] = t[i-1]/x = t t[i] += f[i][i]/x - sum f[i-1][移除区间]/x t[i] *= x t = t[i]/x ans = t[last]/x*x^{last+1}= t*x^{last+1}
1 2 3 4 5 6 7 8 f[i][i] = t for j in 限制区间: f[i][j] = f[i-1][j] t[i] = t t[i] += f[i][i]/x - sum f[i-1][移除区间]/x t = t[i] ans = t*x^{last+1}
1 2 3 4 5 6 7 8 f[i][i] = t for j in 限制区间: f[i][j] = f[i-1][j] t = t t += f[i][i]/x - sum f[i-1][移除区间]/x t = t ans = t*x^{last+1}
1 2 3 4 5 6 f[i][i] = t for j in 限制区间: f[i][j] = f[i-1][j] t += f[i][i]/x - sum f[i-1][移除区间]/x ans = t*x^{last+1}
去掉维度
g[i] = f[?][i]/x
1 2 3 4 5 6 g[i] * x = t for j in 限制区间: g[j]*x = g[j]*x t += g[i] - sum g[移除区间] // 因为上面不变 所以才可以这样 ans = t*x^{last+1}
化简
1 2 3 4 g[i] = t/x t += g[i] - sum g[移除的区间] ans = t*x^{last+1}
0-index
再考虑无限制的前缀, 其实 就是 dp[i][i] = sum dp[i-1][L限制区间]
变成了 dp[i][i] = sum dp[i-1][L限制区间]+x^i
这个x^i就是 前[0..i-1]中x一次都不出现的所有情况, 也就是每个在[0..x-1] 之间随便选
所以影响的也一直是第一行, 看第一行的变化
1 2 3 4 5 6 7 8 9 10 11 dp[i][i] = sum dp[i-1][L限制区间]+x^i = s[i-1]+x^i f[i][i]*x^i = t[i-1]*x^{i-1} + x^i f[i][i] = t[i-1]/x + 1 f[i][i] = t + 1 g[i] * x = t+1 g[i] = t/x + 1/x
合并一下
1 2 3 4 g[i] = t/x + (无限制前缀?1/x:0) t += g[i] - sum g[移除的区间] ans = t*x^{last+1}
移除区间 也就和上面一样的滑窗维护就行了
失效情况
当 出现 [l..r] 都不为x的时候
这时, 会发现 计算的区间为空, 让sum = 0
而这时 已经不会是无限制前缀了, 所以往后的g[i],t 全为0, 能正常处理
这样ans= 所有的X的答案之乘积
代码 离散化 185ms
https://atcoder.jp/contests/abc262/submissions/35558599
unordered_map 191ms
https://atcoder.jp/contests/abc262/submissions/35558963
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 #include <bits/stdc++.h> #include <atcoder/modint> using namespace std;using mint = atcoder::modint998244353;#define rep(i,a,n) for (int i=a;i<(int)n;i++) #define SEG_ROOT 1,0,n-1 #define SEG_L (o<<1) #define SEG_R (o<<1|1) #define mid (l+r)/2 #define SEG_L_CHILD SEG_L,l,mid #define SEG_R_CHILD SEG_R,mid+1,r int read () {int r;scanf ("%d" ,&r);return r;}const int N=200000 ;const int INF=0x3f3f3f3f ; int seg[4 *N+10 ]; int b[N+10 ];int build (int o,int l,int r,int v) { if (l==r)return seg[o]=v; build (SEG_L_CHILD,v); build (SEG_R_CHILD,v); return seg[o]=INF; } void setv (int o,int v) {seg[o]=min (seg[o],v);}void down (int o) { if (seg[o]!=INF){ setv (SEG_L,seg[o]); setv (SEG_R,seg[o]); seg[o]=INF; } } void update (int o,int l,int r,int ql,int qr,int v) { if (ql<=l&&r<=qr) return setv (o,v); down (o); if (ql<=mid) update (SEG_L_CHILD,ql,qr,v); if (qr> mid) update (SEG_R_CHILD,ql,qr,v); } void toB (int o,int l,int r) { if (l==r){ b[l]=seg[o]; return ; } down (o); toB (SEG_L_CHILD); toB (SEG_R_CHILD); } template <class T >int lowb (const vector<T>& v, const T& x) {return lower_bound (begin (v), end (v), x)-begin (v);}int main () { int n=read (); int m=read (); int Q=read (); unordered_map<int ,vector<pair<int ,int >> > query; build (SEG_ROOT,m); while (Q--){ int l=read ()-1 ; int r=read ()-1 ; int x=read (); update (SEG_ROOT,l,r,x); query[x].push_back ({l,r}); } toB (SEG_ROOT); unordered_map<int ,vector<int > > index; index[m]={}; rep (i,0 ,n) index[b[i]].push_back (i); mint ans = 1 ; for (auto &[x,list]:index){ int sz=size (list); if (query[x].empty ()) { if (sz) ans*=mint (x+1 ).pow (sz); continue ; } int first = sz; vector<int > left (sz+1 ) ; for (auto [l,r]: query[x]) { int s = lowb (list, l); int t = lowb (list, r+1 ); first = min (first, t); left[t] = max (left[t], s); } mint invx = mint (x).inv (); vector<mint> dp (sz) ; mint sum = 0 ; int j=0 ; rep (i,0 ,sz){ dp[i]=sum*invx + (i<first?invx:0 ); sum +=dp[i]; for (;j<left[i+1 ];j++) sum -= dp[j]; } ans *= sum*mint (x).pow (sz); } printf ("%d\n" ,ans.val ()); return 0 ; }
总结 F
感觉这种 不算太难想出一个时间复杂度内的方法, 但是想不出一个容易编码的题,好难受啊
G
不要读错题,不要读错题,不要读错题,不要读错题,不要读错题
这种穿插着选取的 感觉遇到了三四次了, 没有悟到什么
一个是不操作的转化为处理时删除
然后万物皆可去想一下作为dp的状态维度, 比如这里完全应该想到的下标, 和 值范围
然后这里从上往下想 竟然比从 贡献角度 想DP 感觉更显然
然后通过端点转移(这里的最大值, 按理说,类似的通过首尾下标也有可能类似的
Ex
特殊->一般
这个特殊虽然还比较好想出方案
但如果告诉我这个特殊,也没想到具体的和一般之间的关系
对于限制 合并一部分,DP另一部分: 这里是合并上限要求, DP存在性要求
从而存在性只于Xi有关
然后 如何优雅的让失效状态失效,感觉也是技术活
另一个是 感觉如果在比赛中,即使 上面我全部得到了, 也很容易对上面的推导过程中出现 笔误问题
或者说, 应该向着 Xi相等的方向去对原问题拆分
参考 官方题解