BZOJ 3747: [POI2015]Kinoman 线段树
题目描述
共有m部电影,编号为1~m,第i部电影的好看值为w[i]。
在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部。
你可以选择l,r(1<=l<=r<=n),并观看第l,l+1,…,r天内所有的电影。如果同一部电影你观看多于一次,你会感到无聊,于是无法获得这部电影的好看值。所以你希望最大化观看且仅观看过一次的电影的好看值的总和。
输入
第一行两个整数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)。
输出
输出观看且仅观看过一次的电影的好看值的总和的最大值。
样例输入
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
样例输出
15
样例解释:
观看第2,3,4,5,6,7天内放映的电影,其中看且仅看过一次的电影的编号为2,3,4。
在沈队的博客上看了这道题感觉不错,在来做的这道题。
这道题,我认为主要有两个难点:
1:
对于每一天,下一次该天播放的电影下次播放时间的处理。
2:
将电影对后面的影响转换到线段树上。
首先,我们定义线段树为区间最大值。
我们将每次电影的价值的表达形式变为:
这次该种电影到下次该种电影的区间都加上w[i]。
这样转换之后,我们在枚举每次播放电影的时候只需要取到1到n的区间中最大值,就是答案。
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define ll long long #define il inline #define db double #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) using namespace std; il int gi() { int x=0,y=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') y=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*y; } il ll gl() { ll x=0,y=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') y=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*y; } struct node { int l,r; ll s; }c[4000045]; ll lazy[4000045]; il void pushdown(int rt) { if(lazy[rt]) { lazy[rt<<1]+=lazy[rt]; lazy[(rt<<1)+1]+=lazy[rt]; c[rt<<1].s+=lazy[rt]; c[(rt<<1)+1].s+=lazy[rt]; lazy[rt]=0; } } void add(int rt,int l,int r,int L,int R,ll num) { if(L<=l&&R>=r) { c[rt].s+=num; lazy[rt]+=num; return; } pushdown(rt); int mid=(l+r)>>1; if(L<=mid) add(rt<<1,l,mid,L,R,num); if(R>mid) add((rt<<1)+1,mid+1,r,L,R,num); c[rt].s=max(c[rt<<1].s,c[(rt<<1)+1].s); } int next[1000045];//i th day next time to show int day[1000045];//num i film last time appear int f[1000045]; ll w[1000045]; int main() { int n=gi(),m=gi(); for(int i=1;i<=n;i++) f[i]=gi(); for(int i=1;i<=m;i++) w[i]=gl(); for(int i=n;i>=1;i--)//必须逆向才能求出来 { next[i]=day[f[i]]; day[f[i]]=i; } for(int i=1;i<=m;i++)//把每种电影第一段区间加上 { if(!day[i]) continue; if(next[day[i]]) add(1,1,n,day[i],next[day[i]]-1,w[i]); else//只出现了一次 add(1,1,n,day[i],n,w[i]); } ll ans=0; for(int i=1;i<=n;i++) { ans=max(ans,c[1].s); if(next[i]) { add(1,1,n,i,next[i]-1,-w[f[i]]);//把当前到下次出现的区间减去 if(next[next[i]])//为枚举到next[i]做准备 add(1,1,n,next[i],next[next[i]]-1,w[f[i]]); else add(1,1,n,next[i],n,w[f[i]]); } else add(1,1,n,i,n,-w[f[i]]); } printf("%lld\n",ans); return 0; }
PEACE