链接:Educational Codeforces Round 81 (Rated for Div. 2) E. Permutation Separation
题意
给出一个 1 1 1 ~ n n n( 2 ≤ n ≤ 2 ⋅ 1 0 5 2\le n\le 2\cdot 10^5 2≤n≤2⋅105)的排列 p 1 , p 2 , ⋯ , p n p_1,p_2,\cdots,p_n p1,p2,⋯,pn,选择 k k k并将序列分为左右两个集合 p 1 , p 2 , ⋯ , p k p_1,p_2,\cdots,p_k p1,p2,⋯,pk和 p k + 1 , p k + 2 , ⋯ , p n p_{k+1},p_{k+2},\cdots,p_n pk+1,pk+2,⋯,pn( 1 ≤ k ≤ n − 1 1\le k\le n-1 1≤k≤n−1),移动元素 p i p_i pi至另一个集合的代价为 a i a_i ai, k k k任选,求 使得最终左集合中所有元素小于右集合中所有元素的最小总代价。(任意集合为空时也符合要求)
分析
首先考虑使得 最终某一集合为空 时的最小总代价,显然是当 k = 1 k=1 k=1或 k = n − 1 k=n-1 k=n−1时,故取 m i n { a [ 1 ] , a [ n ] } min\{a[1],a[n]\} min{a[1],a[n]};
接下来考虑 最终左集合中含有元素为 1 , 2 , ⋯ , i 1,2,\cdots,i 1,2,⋯,i( 1 ≤ i ≤ n − 1 1\le i\le n-1 1≤i≤n−1),则我们可以枚举 i i i,然后每次取找到最优的划分 k k k(即找到最小的代价),朴素解法下这样的时间复杂度会达到 O ( n 2 ) O(n^2) O(n2),故需要对寻找最优划分 k k k的过程进行优化;
观察当 i = 1 i=1 i=1时,设 i i i在原排列中的位置为 p o s [ i ] pos[i] pos[i]:
- 当 k < p o s [ 1 ] k\lt pos[1] k<pos[1]时( 1 1 1被划分至右集合),代价为 ∑ j = 1 k a [ j ] + a [ p o s [ 1 ] ] \sum\limits_{j=1}^k a[j]+a[pos[1]] j=1∑ka[j]+a[pos[1]](左集合元素均移至右集合, 1 1 1移至左集合);
- 而当 k ≥ p o s [ 1 ] k\ge pos[1] k≥pos[1]时( 1 1 1被划分至左集合),代价为 ∑ j = 1 k a [ j ] − a [ p o s [ 1 ] ] \sum\limits_{j=1}^{k}a[j]-a[pos[1]] j=1∑ka[j]−a[pos[1]](左集合元素除 1 1 1以外均移至右集合);
这样可以看出,代价是和前缀和有关的,可以设置一个总代价数组 t [ 1 ] , t [ 2 ] , ⋯ , t [ n − 1 ] t[1],t[2],\cdots,t[n-1] t[1],t[2],⋯,t[n−1],初始状态下 t [ k ] = ∑ j = 1 k a [ j ] t[k]=\sum\limits_{j=1}^{k}a[j] t[k]=j=1∑ka[j],即前缀和,当枚举至 i i i时,令 t [ 1 ⋯ p o s [ i ] − 1 ] + a [ p o s [ i ] ] t[1\cdots pos[i]-1]+a[pos[i]] t[1⋯pos[i]−1]+a[pos[i]],令 t [ p o s [ i ] ⋯ n − 1 ] − a [ p o s [ i ] ] t[pos[i]\cdots n-1]-a[pos[i]] t[pos[i]⋯n−1]−a[pos[i]],利用 min 1 ≤ k ≤ n − 1 { t [ k ] } \min\limits_{1\le k\le n-1}\{t[k]\} 1≤k≤n−1min{t[k]}更新答案即可。
线段树维护区间最值,最终时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)。
分析
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int LINF=0x3f3f3f3f3f3f3f3f;
const int MOD=1e9+7;
const int maxn=2e5+10;
int n,p[maxn],a[maxn],pos[maxn];
LL sum[maxn],t[maxn<<2],laz[maxn<<2];
void push_down(int rt)
{
if(laz[rt])
{
laz[rt<<1]+=laz[rt];
laz[rt<<1|1]+=laz[rt];
t[rt<<1]+=laz[rt];
t[rt<<1|1]+=laz[rt];
laz[rt]=0;
}
}
void build(int rt,int l,int r)
{
if(l==r)
{
t[rt]=sum[l];
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
t[rt]=min(t[rt<<1],t[rt<<1|1]);
}
void updata(int rt,int l,int r,int ql,int qr,LL val)
{
if(ql<=l&&r<=qr)
{
laz[rt]+=val;
t[rt]+=val;
return;
}
push_down(rt);
int mid=(l+r)>>1;
if(ql<=mid)
updata(rt<<1,l,mid,ql,qr,val);
if(qr>mid)
updata(rt<<1|1,mid+1,r,ql,qr,val);
t[rt]=min(t[rt<<1],t[rt<<1|1]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&p[i]);
pos[p[i]]=i;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
build(1,1,n-1);
LL ans=min(a[1],a[n]);
for(int i=1;i<=n-1;i++)
{
if(pos[i]>1)
updata(1,1,n-1,1,pos[i]-1,a[pos[i]]);
if(pos[i]<n)
updata(1,1,n-1,pos[i],n-1,-a[pos[i]]);
ans=min(ans,t[1]);
}
printf("%lld\n",ans);
return 0;
}