逆天
复盘
7:40 开题
扫了一眼四个题,T1 神秘构造,感觉和以前做过的某道题有点像啊,应该能做;T2 题意很简洁,感觉可做;T3,一眼感觉是什么优化 dp;T4,看上去像是拆期望推式子的题,可做!
看起来今天的题印象不错
推 T1,猜了一大堆结论发现都假了,40min 过去,心态有点崩
T2,简单分讨一下发现情况都是可做的,而且需要求的东西 kmp + manacher 似乎都能处理,我真会了?然而心想不会复刻曾经 T1 不会,T2 写 2h 最后假了保龄的悲剧吧
反复确认了一下,觉得没什么问题,开写
写完一遍过大样例?然后发现大样例是真的水,只 manacher 就能过掉 2,3,4,剩下的小数据瞎一贪都过了
决定对拍,还真拍出来错了
然后发现做法有漏洞,没考虑再选一个回文串的情况,不过感觉这个似乎只需要求 以每个位置为左/右端点的最长回文串就行了,思考了一下可以在 manacher 的过程中做到线性
写到最后几步求答案的地方了,然后猛然意识到只考虑最长是不对的,有可能选个比较短的会更优!然后直接人麻了,感觉做法假完了
完了,真复刻了
开始打补丁,觉得这个东西可以 字符串 hash + 二分,那样就单 log 了,可是觉得要是直接字符串 hash 那我推这么多东西还有啥用啊?心态再崩
决定先跳,重新回来想 T1,失败
打完 T3 暴力后剩 30min 决定 T2 瞎搞搞,但也不报希望了,写了个明显假的东西交了
发现能过附加的小样例,但觉得也没什么用。因为觉得算法铁假了,也没再拍
就结束了,感觉真保龄了
结果是:
20 + 100 + 10 + 0 = 130 ???
最逆天的一场
T2 过了我是完全没想到,这做法看上去是随便 hack 的,赛后一直以为是这样。直到准备订题,尝试手造卡掉这个做法,发现造不出来?然后去对拍,硬是拍了 60w 组拍不出来,我去,我真了?
要是知道 T2 能过,后半场的心态肯定就完全不一样了
T1 想偏了啊,潜意识或者说第一印象认为这个构造肯定是猜个结论直接输出那种,完全没往建图后 toposort 的方向想,赛后会看感觉这也很自然啊!可是赛时是第一想法错了,先猜肯定是从小到大填数,然后完全陷进去了
感觉这场问题很大啊,心态怎么这么差,模拟赛 T1 不会直接就变成 sb 了,T3、T4 能写的暴力都不想写了,T2 的处理也不够冷静,其实我的做法跟正解差不了多少了
这可不行啊
题解
T1
这道题是真应该深刻反思了
我们知道,LIS LDS 本质都可以看作 DAG 上的最长链,所以往建图的角度思考是十分自然的
一个传统的想法:
考虑 LIS 的 dp 过程: f i = max ( f j + 1 ) , j < i , a j < a i f_i=\max(f_j+1),j<i,a_j<a_i fi=max(fj+1),j<i,aj<ai
找限制,对于一种数字 x x x:
- 所有等于 x x x 的位置填的数必定单调下降
- 一个 x x x 需要从前面至少一个 x − 1 x-1 x−1 的位置转移
- x x x 不能从前面任何一个 ≥ x \geq x ≥x 的位置转移
1 , 3 1,3 1,3 限制是很好建图的,关键在于 2 2 2 限制
结合 1 1 1,简单分析就能得出一个结论:只需从前面第一个 x − 1 x-1 x−1 转移即可!
然后我们就得到一个 n 2 n^2 n2 建边的思路
考虑优化,显然可以线段树优化建图,但很唐
Sorato 佬的做法是:对于一个 x x x,向前面 最小的 且 ≥ x \geq x ≥x 的位置 p p p 连边
乍一看不对, [ p + 1 , x − 1 ] [p+1,x-1] [p+1,x−1] 这段区间中有 > x >x >x 的数怎么办?
分讨一下:这样的数最终一定也需要 p p p 进行转移!连边后是能够传递过去的!
太牛了,只需建 O ( n ) O(n) O(n) 条边
对于正解:
考虑另一种 LIS,LDS 的求法,维护一个数组 f i f_i fi 表示长度为 i i i 的 lis 的结尾元素的最小值,易知 f i f_i fi 单调不降
设 l i l_i li 表示以 i i i 结尾的 LIS 的最长长度
扫到第 i i i 个位置上时,设值为 a i a_i ai,在 f i f_i fi 中二分找到位置 k k k 使得 f k ≤ a i < f k + 1 f_k\leq a_i<f_{k+1} fk≤ai<fk+1 ,然后 l i l_i li 就等于 k + 1 k+1 k+1,再更新 f k + 1 = a i f_{k+1}=a_i fk+1=ai
正确性是十分显然的
对于本题,我们发现这种做法中的大小关系是十分明显的,直接模拟这个过程就能做到 O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std ;
typedef long long LL ;
const int N = 2e5+10 ;
// LIS 的技巧: 维护 fi 表示长度为 i 的lis结尾元素的最小值
// 易知 fi 是不降的
// 更新时只需要每次二分找到合法的位置替换
int n , a[N] , b[N] ;
int f[N] ;
vector<int> E[N] ;
int du[N] ;
inline void add( int x , int y )
{
E[x].push_back(y) ; du[y] ++ ;
}
int q[N] , hh , tt , ans[N] ;
int main()
{
scanf("%d" , &n ) ;
for(int i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i] ) ;
for(int i = 1 ; i <= n ; i ++ ) scanf("%d" , &b[i] ) ;
for(int i = 1 ; i <= n ; i ++ ) {
if( f[a[i]] ) add(i,f[a[i]]) ;
if( a[i]>1 && f[a[i]-1] ) add(f[a[i]-1],i) ;
f[a[i]] = i ;
}
memset( f , 0 , sizeof f ) ;
for(int i = n ; i >= 1 ; i -- ) {
if( f[b[i]] ) add(i,f[b[i]]) ;
if( b[i]>1 && f[b[i]-1] ) add(f[b[i]-1],i) ;
f[b[i]] = i ;
}
// 十分充要
hh = 1 ; tt = 0 ;
for(int i = 1 ; i <= n ; i ++ ) {
if( !du[i] ) q[++tt] = i ;
}
int now = 0 ;
while( hh <= tt ) {
int x = q[hh] ; hh ++ ;
ans[x] = ++now ;
for(int t : E[x] ) {
if( --du[t] == 0 ) q[++tt] = t ;
}
}
for(int i = 1 ; i <= n ; i ++ ) {
printf("%d " , ans[i] ) ;
}
return 0 ;
}