已自我实现,但还是归入无码专区序列。哈哈哈哈哈
对于my idea部分,我的每一个想法都实现了,可供参考。
problem
给定一个 1∼n1\sim n1∼n 的排列和 kkk,求所有 r−l+1≥kr-l+1\ge kr−l+1≥k 的区间 [l,r][l,r][l,r] 中的第 kkk 大数,输出他们的和。
60%,n≤5e460\%,n\le 5e460%,n≤5e4
另 20%,k=120\%,k=120%,k=1
100%,n≤5e5,k≤50100\%,n\le 5e5,k\le 50100%,n≤5e5,k≤50
128MB,1s128MB,1s128MB,1s
my idea
注意到 kkk 是非常小的,粗略来看正解应该是 O(nk)O(nk)O(nk) 的。
考虑单独统计每个位置 iii 的贡献。
则需要枚举其左边有 xxx 个 >a[i]>a[i]>a[i] 的数字,右边有 k−x−1k-x-1k−x−1 个。因为 aia_iai 自己本身还要在前 kkk 大中占据一席位置。
然后需要知道两个端点的位置,记为 l,rl,rl,r。即 [l,i)[l,i)[l,i) 中 >a[i]>a[i]>a[i] 的数有 xxx 个,(i,r](i,r](i,r] 中 >a[i]>a[i]>a[i] 的数有 k−x−1k-x-1k−x−1 个。
假设 lll 左边,rrr 右边第一个 >a[i]>a[i]>a[i] 的数位置为 L,RL,RL,R。
则 ∀p∈(L,l],q∈[r,R)\forall\ p\in(L,l],q\in[r,R)∀ p∈(L,l],q∈[r,R) 的区间 [p,q][p,q][p,q] 的答案都会是 a[i]a[i]a[i]。
问题就在于怎么快速求得位置左右大于 a[i]a[i]a[i] 个数为 x(1≤x<k)x(1\le x<k)x(1≤x<k) 的所有位置。
没错——还是主席树!!!
弄两个主席树,一个表示前缀主席树,一个表示后缀主席树。
对 a[i]a[i]a[i] 建立权值线段树作下标,树上节点权值为下标 iii,维护最大值/最小值。
具体而言:以前缀树为例。
查询:查询前 i−1i-1i−1 个树的版本中 [ai+1,n][a_i+1,n][ai+1,n] 的区间中的最大值。表示是最靠近 iii 的 >a[i]>a[i]>a[i] 的位置。
假设这个位置是 ppp,接下来就是在 p−1p-1p−1 的版本中查询 [ai+1,n][a_i+1,n][ai+1,n] 的最大值,表示是第二靠近 iii 的 >a[i]>a[i]>a[i] 的位置,以此类推,最多查询到第 k−1k-1k−1 靠近就行了。
修改:直接在 i−1i-1i−1 的版本上新增将 a[i]a[i]a[i] 对应叶子权值修改为 iii,继续维护最大值。
后缀树与前缀树同理,从后往前做,维护最小值即可。
因为枚举顺序不一样所以是分开做,那么就可以共享同一棵主席树,节省空间。
将每个位置的左右都预处理出来,时间复杂度为 O(nklogn)O(nk\log n)O(nklogn) 的。
对于 60%60\%60% 的数据点 n≤5e4n\le 5e4n≤5e4,算出来大约是 4e74e74e7 空间也是绰绰有余的。
只不过如果 n≤5e5n\le 5e5n≤5e5,超时不说,连主席树都开不出来这么大。
#include <bits/stdc++.h>
using namespace std;
#define maxk 52
#define maxn 50002
#define inf 0x3f3f3f3f
int n, k, cnt;
int lst[maxn][maxk], nxt[maxn][maxk];
int a[maxn], root[maxn];
struct node { int ans, lson, rson; } t[maxn * 25];#define mid ( ( l + r ) >> 1 )namespace pre_sgt {void build( int &now, int l, int r ) { t[now = ++ cnt].ans = 0;if( l == r ) return;build( t[now].lson, l, mid );build( t[now].rson, mid + 1, r );}void modify( int pre, int &now, int l, int r, int pos, int k ) {t[now = ++ cnt] = t[pre];if( l == r ) { t[now].ans = k; return; }if( pos <= mid ) modify( t[pre].lson, t[now].lson, l, mid, pos, k );else modify( t[pre].rson, t[now].rson, mid + 1, r, pos, k );t[now].ans = max( t[t[now].lson].ans, t[t[now].rson].ans );}int query( int now, int l, int r, int L ) {if( r < L ) return 0;if( L <= l ) return t[now].ans;return max( query( t[now].lson, l, mid, L ), query( t[now].rson, mid + 1, r, L ) );}
}namespace suf_sgt {void build( int &now, int l, int r ) { t[now = ++ cnt].ans = inf;if( l == r ) return;build( t[now].lson, l, mid );build( t[now].rson, mid + 1, r );}void modify( int pre, int &now, int l, int r, int pos, int k ) {t[now = ++ cnt] = t[pre];if( l == r ) { t[now].ans = k; return; }if( pos <= mid ) modify( t[pre].lson, t[now].lson, l, mid, pos, k );else modify( t[pre].rson, t[now].rson, mid + 1, r, pos, k );t[now].ans = min( t[t[now].lson].ans, t[t[now].rson].ans );}int query( int now, int l, int r, int L ) {if( r < L ) return inf;if( L <= l ) return t[now].ans;return min( query( t[now].lson, l, mid, L ), query( t[now].rson, mid + 1, r, L ) );}
}int main() {freopen( "kth.in", "r", stdin );freopen( "kth.out", "w", stdout );scanf( "%d %d", &n, &k );for( int i = 1;i <= n;i ++ ) scanf( "%d", &a[i] );pre_sgt :: build( root[0], 1, n );for( int i = 1;i <= n;i ++ ) {lst[i][0] = i;int pos = i - 1;for( int j = 1;j <= k;j ++ ) {lst[i][j] = pre_sgt :: query( root[pos], 1, n, a[i] + 1 );if( ! lst[i][j] ) break;else pos = lst[i][j] - 1;}pre_sgt :: modify( root[i - 1], root[i], 1, n, a[i], i );}cnt = 0;suf_sgt :: build( root[n + 1], 1, n );memset( nxt, 0x3f, sizeof( nxt ) );for( int i = n;i;i -- ) {nxt[i][0] = i;int pos = i + 1;for( int j = 1;j <= k;j ++ ) {nxt[i][j] = suf_sgt :: query( root[pos], 1, n, a[i] + 1 );if( nxt[i][j] == inf ) break;else pos = nxt[i][j] + 1;}suf_sgt :: modify( root[i + 1], root[i], 1, n, a[i], i );}long long ans = 0;for( int i = 1;i <= n;i ++ ) {for( int j = 0;j < k;j ++ ) {int l = lst[i][j], r = nxt[i][k - j - 1];if( ! l or r == inf ) continue;int posl = max( 1, lst[i][j + 1] + 1 );int posr = min( n, nxt[i][k - j] - 1 );ans += 1ll * ( l - posl + 1 ) * ( posr - r + 1 ) * 1ll * a[i];}}printf( "%lld\n", ans );return 0;
}
注意到还有 20%20\%20% 的额外特殊数据:k=1k=1k=1。
这就相当于求每个数为一个区间的最大值的贡献。
显然 [l,r][l,r][l,r] 中最大的数假设在 midmidmid 处,则会将区间划分成独立的两个子区间 [l,mid−1],[mid+1,r][l,mid-1],[mid+1,r][l,mid−1],[mid+1,r]。
所以可以直接递归处理,对于区间内最大值的贡献自然是 ∀l≤i≤mid≤j≤r\forall\ l\le i\le mid\le j\le r∀ l≤i≤mid≤j≤r 的 [i,j][i,j][i,j] 区间都合法。
一个区间内的最大值可以用 st表
预处理出来,查询做到 O(1)O(1)O(1)。
每个点就只会被当作最大值算一次,计算也是 O(1)O(1)O(1) 的。
时间复杂度在于 st表
的预处理,O(nlogn)O(n\log n)O(nlogn)。
namespace K1 {long long ret = 0;int query( int l, int r ) {int i = log( r - l + 1 ) / log( 2 );if( a[st[l][i]] < a[st[r - ( 1 << i ) + 1][i]] ) return st[r - ( 1 << i ) + 1][i];else return st[l][i]; }void dfs( int l, int r ) {if( l > r ) return;if( l == r ) { ret += a[l]; return; }int mid = query( l, r );ret += 1ll * ( mid - l + 1 ) * ( r - mid + 1 ) * a[mid];dfs( l, mid - 1 );dfs( mid + 1, r );}void solve() {for( int i = 1;i <= n;i ++ ) st[i][0] = i;for( int j = 1;( 1 << j ) <= n;j ++ )for( int i = 1;i + ( 1 << j ) - 1 <= n;i ++ )if( a[st[i][j - 1]] > a[st[i + ( 1 << j - 1 )][j - 1]] )st[i][j] = st[i][j - 1];elsest[i][j] = st[i + ( 1 << j - 1 )][j - 1];dfs( 1, n );printf( "%lld\n", ret );}
}
将这两份代码拼凑在一起,就可以拿到 80′80'80′ 的高分了。
solution
事实上,我的想法已经非常接近正解了。
只是预处理不是采用主席树自带一个 log\loglog ,而是使用双向链表。
把所有元素从大到小操作,用双向链表连接起来,每次新增一个数就会断掉两个位置的连接,然后加进去再连上。
链表 Li,RiL_i,R_iLi,Ri 记录的是当前对于 iii 而言朝左朝右第一个比 a[i]a[i]a[i] 大的数为止,每次新增数都会维护这个链表。
暴力朝左朝右跳 kkk 次链表,将过程点记录下来,然后就可以枚举 xxx 计算了。
时间复杂度为 O(nk)O(nk)O(nk)。
code
#include <bits/stdc++.h>
using namespace std;
#define maxn 500005
#define maxk 55
int n, k;
long long ans;
int lst[maxn], nxt[maxn], L[maxk], R[maxk], a[maxn], id[maxn];
set < int > s;int main() {freopen( "kth.in", "r", stdin );freopen( "kth.out", "w", stdout );scanf( "%d %d", &n, &k );for( int i = 1;i <= n;i ++ ) scanf( "%d", &a[i] ), id[i] = i;sort( id + 1, id + n + 1, []( int x, int y ) { return a[x] > a[y]; } );for( int i = 1;i <= n;i ++ ) {int l = 0, r = 0, cnt_l = 0, cnt_r = 0, Last, Next;auto it = s.lower_bound( id[i] );if( it != s.end() ) r = *it, l = lst[r];else if( ! s.empty() ) l = *-- it, r = nxt[l];s.insert( id[i] );L[0] = R[0] = id[i], Last = l, Next = r;for( int j = 1;j <= k and l;j ++ ) L[++ cnt_l] = l, l = lst[l];for( int j = 1;j <= k and r;j ++ ) R[++ cnt_r] = r, r = nxt[r];if( cnt_l < k ) L[++ cnt_l] = 0;if( cnt_r < k ) R[++ cnt_r] = n + 1;for( l = k - 1, r = 0;~ l;l --, r ++ )if( l < cnt_l and r < cnt_r )ans += 1ll * ( L[l] - L[l + 1] ) * ( R[r + 1] - R[r] ) * a[id[i]];lst[id[i]] = Last, nxt[id[i]] = Next;if( Last ) nxt[Last] = id[i];if( Next ) lst[Next] = id[i];}printf( "%lld\n", ans );return 0;
}