zoukankan      html  css  js  c++  java
  • 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

    样例输出

    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
    ",ans);
    
    	return 0;
    }
    
  • 相关阅读:
    Linux socket编程 服务器 客户端
    嵌入式编程 相关 细节 收集
    Linux内核运行机制学习笔记
    Button驱动学习
    LED驱动学习
    C#修改IIS匿名账号的密码问题
    数据库自增字段的获取
    VC头文件的顺序 以及一些常见错误的常见处理方式
    WIN8 更新CD KEY
    linux下 lvm 磁盘扩容
  • 原文地址:https://www.cnblogs.com/gshdyjz/p/7682874.html
Copyright © 2011-2022 走看看