BZOJ 3747: [POI2015]Kinoman( 线段树 )

线段树...

我们可以枚举左端点 , 然后用线段树找出所有右端点中的最大值 .

-----------------------------------------------------------------------------------------

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
 
#define rep( i , n ) for( int i = 0 ; i < n ; ++i )
#define clr( x , c ) memset( x , c , sizeof( x ) )
#define Rep( i , n ) for( int i = 1 ; i <= n ; ++i )
#define M( l , r ) ( ( l + r ) >> 1 )
 
using namespace std;
 
typedef long long ll;
 
const int maxn = 1000000 + 5;
 
int L , R , v , n;
 
struct Node {
Node *lc , *rc;
ll mx , v , add;
Node() {
mx = v = add = 0;
lc = rc = NULL;
}
inline void Add( ll ad ) {
add += ad;
}
inline void update() {
if( lc ) 
mx = max( lc -> mx , rc -> mx ) + add;
else 
   mx =  (v += add ) , add = 0;
}
inline void pushdown() {
lc -> Add( add );
rc -> Add( add );
add = 0;
}
} nodePool[ maxn << 1 ] , *pt = nodePool , *root;
 
void build( Node* t , int l , int r ) {
if( r > l ) {
   build( t -> lc = pt++ , l , M( l , r ) );
   build( t -> rc = pt++ , M( l , r ) + 1 , r );
}
t -> update();
}
 
// add
void modify( Node* t , int l , int r ) {
if( L <= l && r <= R )
t -> Add( v );
else {
t -> pushdown();
int m = M( l , r );
L <= m ? modify( t -> lc , l , m ) : t -> lc -> update();
m < R ? modify( t -> rc , m + 1 , r ) : t -> rc -> update();
}
t -> update();
}
 
int w[ maxn ];
 
struct day {
int f;
day* next;
day() {
next = NULL;
}
} *head[ maxn ] , *last[ maxn ] , d[ maxn ];
 
int main() {
freopen( "test.in" , "r" , stdin );
clr( last , 0 );
int m;
cin >> n >> m;
build( root = pt++ , 1 , n );
Rep( i , n ) {
head[ i ] = d + i;
scanf( "%d" , &head[ i ] -> f );
}
Rep( i , m )
   scanf( "%d" , w + i );
for( int i = n ; i ; i-- ) {
head[ i ] -> next = last[ head[ i ] -> f ];
last[ head[ i ] -> f ] = head[ i ];
}
Rep( i , m ) if( last[ i ] ) {
L = last[ i ] - d , R = last[ i ] -> next ? last[ i ] -> next - d - 1 : n;
v = w[ i ];
modify( root , 1 , n );
}
ll ans = 0;
Rep( i , n ) {
ans = max( ans , root -> mx );
if( head[ i ] -> next ) {
L = i , R = head[ i ] -> next - d - 1;
v = -w[ head[ i ] -> f ];
modify( root , 1 , n );
L = head[ i ] -> next - d , v = w[ head[ i ] -> f ];
if( head[ i ] -> next -> next )
   R = head[ i ] -> next -> next - d - 1;
else 
   R = n;
modify( root , 1 , n );
} else {
L = i , R = n , v = -w[ head[ i ] -> f ];
modify( root , 1 , n );
}
}
cout << ans << " ";
return 0;
}

  

----------------------------------------------------------------------------------------- 

3747: [POI2015]Kinoman

Time Limit: 60 Sec  Memory Limit: 128 MB
Submit: 440  Solved: 165
[Submit][Status][Discuss]

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

Sample Output

15
样例解释:
观看第2,3,4,5,6,7天内放映的电影,其中看且仅看过一次的电影的编号为2,3,4。

HINT

Source

原文地址:https://www.cnblogs.com/JSZX11556/p/4637078.html