You are given an array consisting of n non-negative integers a1, a2, ..., an.
You are going to destroy integers in the array one by one. Thus, you are given the permutation of integers from 1 to n defining the order elements of the array are destroyed.
After each element is destroyed you have to find out the segment of the array, such that it contains no destroyed elements and the sum of its elements is maximum possible. The sum of elements in the empty segment is considered to be 0.
The first line of the input contains a single integer n (1 ≤ n ≤ 100 000) — the length of the array.
The second line contains n integers a1, a2, ..., an (0 ≤ ai ≤ 109).
The third line contains a permutation of integers from 1 to n — the order used to destroy elements.
Print n lines. The i-th line should contain a single integer — the maximum possible sum of elements on the segment containing no destroyed elements, after first i operations are performed.
4 1 3 2 5 3 4 1 2
5 4 3 0
5 1 2 3 4 5 4 2 3 5 1
6 5 5 1 0
8 5 5 4 4 6 6 5 5 5 2 8 7 1 3 4 6
18 16 11 8 8 6 6 0
Consider the first sample:
- Third element is destroyed. Array is now 1 3 * 5. Segment with maximum sum 5 consists of one integer 5.
- Fourth element is destroyed. Array is now 1 3 * * . Segment with maximum sum 4 consists of two integers 1 3.
- First element is destroyed. Array is now * 3 * * . Segment with maximum sum 3 consists of one integer 3.
- Last element is destroyed. At this moment there are no valid nonempty segments left in this array, so the answer is equal to 0.
题意:
起先你有n个正整数a1,a2,...,an,现在你要将这个数组里的数一个接一个的破坏掉,直到最后每个数都被
破坏,对于每次破坏,求出当前最大连续段和,要求这些段不能包含被破坏的数
开始做一直想用前缀和数组,但发现怎么写都是n^2,肯定超时,结果用并查集。。。妙啊
题解:
正着删除不好处理,就反过来求了,相当于从一个空序列往上面添加数值,加一次,求一次线段的最大和,使用离线处理,每次与当前值和前一个答案来更新答案!
加入某个值后,使用用并查集进行处理,令a[i]是 i 这个点的权值,那么先判断i+1的位置,如果有值的话,就把i+1这一系列线段与i位置上的值合并,再判断i-1位置。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define MAXN 100100
//逆向思维,倒着一个一个插入,这样结果肯定递增,因此每次插入和前一个比,较大就行
long long a[MAXN];//存放原始数,注意用long long,因为最大1e9相加肯定超int
long long ans[MAXN];//存放答案的数组
long long f[MAXN];//用于储存每一段的和
int p[MAXN];//用于储存删除点的位置
int pre[MAXN];//使用并查集时父亲数组
bool have[MAXN];//判断左右两边有没有已经插入数字
int n;
void init(){//初始化pre数组have数组
int i;
for(i = 1; i <= n; i++){
pre[i] = i;
}
memset(have,false,sizeof(have));
}
int Find(int x){//并查集
if(x==pre[x])return x;
else return pre[x] = Find(pre[x]);
}
int main(){
int i;
scanf("%d",&n);
init();
for(i = 1; i <= n; i++)scanf("%lld",&a[i]);
for(i = 1; i <= n; i++)scanf("%d",&p[i]);
ans[n] = 0;//因为每次和前一个比,而插入第一次相当于删除的最后一次,为零
for(i = n; i > 1; i--){//倒着来
int p1 = p[i];//用p1保存下插入的位置
f[p1] = a[p1];//因为这个点是新插入的,肯定时单独的,所以他自己单独算一段所以f[p1]=a[pa];
if(have[p1-1]){//找前一个点,但事实上,根据之前并查集的合并,这一个点的值实际上
//已经把已经插入的点的和包含了,也就是说已经合并了,只是用这个点代替表示而已
int f1 = Find(p1),f2 = Find(p1-1);
pre[f1] = f2;//找到两点的父亲,让新插入的点的合并到p1-1那个点的集合中去
f[f2] += f[f1];//因此值也要加进去,也就解释了为什么只判断插入点的前后两个点就可以了
//因为虽然找到是前后两点,但是实际是找到前后两点的父亲,即一段
}
//同理
if(have[p1+1]){
int f1 = Find(p1),f2 = Find(p1+1);
pre[f1] = f2;
f[f2] += f[f1];
}
ans[i-1] = max(f[Find(p1)],ans[i]);//最后找到插入点的父亲所代表的这一段的和,比较上一次的答案
have[p1] = true; //为什么是ans[i-1],因为插入一个相当于没删之前的情况,所以i-1
}
for(i = 1; i <= n; i++)
printf("%lld\n",ans[i]);
return 0;
}