hard version 的 On 做法我老早就看题解弄懂了,但 easy version 的 n2 暴力直到现在才想明白。。。
题意:
给定一个01串,尽量把1改成0,要求任意子区间的 LIS 长度保持不变。
这里的 LIS 为最长不降子列
串长2000
思路:
若把某个1改成0之后,以它为左端点的所有子区间的 LIS 长度保持不变,那就可以改。解释:
对某个固定的 \(L\),把 \(s_L\) 从1改成0后 \(\forall R\in[L,n],\text{LIS}(L,R)\) 的长度不变 \(\iff \text{LIS}(L,R)\) 都是以1而非0开头 \(\iff\) 在 \([L,R]\) 中存在一个全1子列(11111),其长度严格大于任一 0011子列(修改 \(s_L\) 后,0011的长度最多和原来的11111相等)
考虑 \([p,R],p<L\) 的 \(\text{LIS}\) ,它的前半部分一定是区间 \([p,L-1]\) 的某个子列,后半部分要么是 \([L,R]\) 中的最长0011子列,要么是 \([L,R]\) 中的最长全1子列。由上段讨论,后半部分的长度不变,所以 \([p,R],p<L\) 的 \(\text{LIS}\) 长度也不变。
const signed N = 2003; int n, f[N], g[N]; char s[N]; void LIS(int l, int *a) { //以l开头的所有子区间的LIS长度 static int dp[2]; dp[0] = dp[1] = 0; //以0/1结尾的LIS长度 for(int i = l; i <= n; i++) { if(s[i] == '0') dp[0]++; else dp[1] = max(dp[0], dp[1]) + 1; a[i] = max(dp[0], dp[1]); } } signed main() { iofast; cin >> s + 1; n = strlen(s + 1); for(int i = 1; i <= n; i++) if(s[i] == '1') { LIS(i, f); s[i] = '0'; LIS(i, g); for(int j = i; j <= n; j++) //比较 if(f[j] != g[j]) s[i] = '1'; } cout << s + 1; }