BZOJ3747:[POI2015]Kinoman(线段树)
Description
共有m部电影,编号为1~m,第i部电影的好看值为w[i]。
在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部。
你可以选择l,r(1<=l<=r<=n),并观看第l,l+1,…,r天内所有的电影。如果同一部电影你观看多于一次,你会感到无聊,于是无法获得这部电影的好看值。所以你希望最大化观看且仅观看过一次的电影的好看值的总和。
Input
第一行两个整数n,m(1<=m<=n<=1000000)。
第二行包含n个整数f[1],f[2],…,f[n](1<=f[i]<=m)。
第三行包含m个整数w[1],w[2],…,w[m](1<=w[j]<=1000000)。
Output
输出观看且仅观看过一次的电影的好看值的总和的最大值。
Sample Input
9 4
2 3 1 1 4 1 2 4 1
5 3 6 6
2 3 1 1 4 1 2 4 1
5 3 6 6
Sample Output
15
样例解释:
观看第2,3,4,5,6,7天内放映的电影,其中看且仅看过一次的电影的编号为2,3,4。
Solution
记一个数组$pre[i]$,表示$i$这个位置上的数上一次出现的位置在哪。
从左往右扫一边,区间$pre[i]+1$到$i$加上当前数的权值,$pre[pre[i]]+1$到$pre[i]$这段位置减去这个数的权值。然后查询区间$[1,i]$,取查询的$max$即为答案。
Code
1 #include<iostream> 2 #include<cstdio> 3 #define N (1000009) 4 #define LL long long 5 using namespace std; 6 7 struct Sgt{LL max,add;}Segt[N<<2]; 8 int n,m,f[N],w[N],pre[N],a[N]; 9 LL ans; 10 11 void Pushdown(int now) 12 { 13 Segt[now<<1].add+=Segt[now].add; 14 Segt[now<<1].max+=Segt[now].add; 15 Segt[now<<1|1].add+=Segt[now].add; 16 Segt[now<<1|1].max+=Segt[now].add; 17 Segt[now].add=0; 18 } 19 20 void Update(int now,int l,int r,int l1,int r1,int k) 21 { 22 if (l>r1 || r<l1) return; 23 if (l1<=l && r<=r1) 24 { 25 Segt[now].add+=k; 26 Segt[now].max+=k; 27 return; 28 } 29 Pushdown(now); 30 int mid=(l+r)>>1; 31 Update(now<<1,l,mid,l1,r1,k); 32 Update(now<<1|1,mid+1,r,l1,r1,k); 33 Segt[now].max=max(Segt[now<<1].max,Segt[now<<1|1].max); 34 } 35 36 LL Query(int now,int l,int r,int l1,int r1) 37 { 38 if (l>r1 || r<l1) return -1; 39 if (l1<=l && r<=r1) return Segt[now].max; 40 Pushdown(now); 41 int mid=(l+r)>>1; 42 return max(Query(now<<1,l,mid,l1,r1),Query(now<<1|1,mid+1,r,l1,r1)); 43 } 44 45 int main() 46 { 47 scanf("%d%d",&n,&m); 48 for (int i=1; i<=n; ++i) 49 scanf("%d",&f[i]), pre[i]=a[f[i]], a[f[i]]=i; 50 for (int i=1; i<=m; ++i) 51 scanf("%d",&w[i]); 52 for (int i=1; i<=n; ++i) 53 { 54 Update(1,1,n,pre[i]+1,i,w[f[i]]); 55 Update(1,1,n,pre[pre[i]]+1,pre[i],-w[f[i]]); 56 ans=max(ans,Query(1,1,n,1,i)); 57 } 58 printf("%lld\n",ans); 59 }