zoukankan      html  css  js  c++  java
  • HDU6856 Breaking Down News

    题目大意

    题目链接

    有一个长度为(n)的数组(a_1,a_2,dots ,a_n)(a_iin{-1,0,1})。请你将这个数组,划分为连续的(mgeq 1)段(每个位置都恰好位于某一段内),使得每一段的长度,都大于等于(L),小于等于(R)

    我们定义,一段连续子序列(a_l,a_{l+1},dots ,a_r)的价值为:

    • 如果序列里,所有元素之和(>0),则价值为(1)
    • 如果序列里,所有元素之和(=0),则价值为(0)
    • 如果序列里,所有元素之和(<0),则价值为(-1)

    也可以用公式表示为:(v(l,r)=[(sum_{i=l}^{r}a_i)>0]-[(sum_{i=l}^{r}a_i)<0])

    请你通过划分,最大化所有(m)段的价值之和((m)是你自己选的,并不是题目规定的)。

    数据范围:(1leq Lleq Rleq nleq 10^6)(T)组测试数据,(1leq Tleq 1000)(sum nleq 9cdot 10^6)

    本题题解

    考虑DP。设(dp[i])表示把(a_1dots a_i)划分为若干段,并且(a_i)是最后一段的结尾时,的最大价值。

    朴素的转移很简单:

    [dp[i]=max_{j=i-R}^{i-L}{dp[j]+v(j+1,i)} ]

    其中(v)表示一段连续子序列的价值,题面里已给出定义。按这个式子直接DP,时间复杂度(O(n^3))

    我们可以对(a)序列做前缀和。设(s_i=sum_{j=1}^{i}a_i)。于是转移式改写为:

    [dp[i]=max_{j=i-R}^{i-L}{dp[j] + [s_i-s_j>0]-[s_i-s_j<0]} ]

    时间复杂度(O(n^2))

    继续优化。首先,因为每个(i)只能从一段特定的区间转移过来,容易想到单调队列优化DP:队列从前到后,“价值”单调递减,位置单调递增,也就是永远不保留“又老又不中用”的元素。

    然而会遇到一个问题:你如何比较两个元素的“价值”大小呢?我们不能只比较(dp[j])的大小,因为还有后面的(s_j)的贡献。但又不能简单地把(dp[j]-s_j)作为价值,因为(s_j)是要和后面的每个(i)共同算出一个价值(vin{-1,0,1})

    既然把它们放在一起,怎么比较都不公平,那不如将它们分开来:对每种(s_j)的值(从最小的(-n)到最大的(n),共有(2n+1)种可能的值),各开一个单调队列。此时同一个单调队列里,就只需要比(dp[j])的大小了。

    对于每个值(x) ((-nleq xleq n)),我们维护出它的队首(最优的),记其DP值为(f[x])。特别地,如果单调队列为空,则(f[x]=inf)

    每次转移前,先将(i-L)入队,将(i-R-1)出队。只需要在对应的(x=s_{i-L})(x=s_{i-R-1})这两个队列里做修改,修改后更新对应的(f[x])的值。这都是单调队列的基本操作。

    单调队列是维护好了,怎么转移呢?我们总不能枚举所有(x)吧?考虑,使得(v(j+1,i)=1)(j),一定是(s_i-s_j>0),也就是(s_j<s_i),所以这样的(s_j)值是一段前缀(即(xin[-n,s_i-1]));同理,使得(v(j+1,i)=-1)(j),一定是一段后缀((xin[s_i+1,n]));而使得(v(j+1,i)=0)(s_j),就是(x=s_i)。所以,我们只需要在(f)数组上做3次区间最大值查询,就能完成转移了!

    总结一下,我们在( ext{push}, ext{pop})一个元素,也就是对单调队列做改动时,要支持对(f)数组单点修改。因为每个(j)只会入队、出队一次,所以总修改次数是(O(n))的。在转移时,需要做3次区间最大值查询,总查询次数也是(O(n))的。用线段树来维护这个(f)数组即可。时间复杂度(O(nlog n))

    单调队列,可以用( exttt{vector})维护(( exttt{deque})时间空间常数都太大了)。( exttt{pop_front})操作,可以用变量记录每个( exttt{vector})的实际队首元素在哪里,要( exttt{pop_front})时令这个队首位置( exttt{++})即可。因为每个元素只会入队一次,所以空间复杂度是(O(n))的。

    总时间复杂度(O(nlog n)),空间复杂度(O(n))

    参考代码(本代码仅供参考,实际提交时建议使用读入优化,详见本博客公告栏):

    //problem:1002
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
    template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
    
    const int MAXN=1e6,INF=1e9;
    int n,L,R,a[MAXN+5],s[MAXN+5],dp[MAXN+5];
    int lpt[MAXN*2+5],bas;
    vector<int>vec[MAXN*2+5];
    struct SegmentTree{
    	int mx[MAXN*8+100];
    	void build(int p,int l,int r){
    		mx[p]=-INF;
    		if(l==r) return;
    		int mid=(l+r)>>1;
    		build(p<<1,l,mid);
    		build(p<<1|1,mid+1,r);
    	}
    	void modify(int p,int l,int r,int pos,int v){
    		if(l==r){
    			mx[p]=v;
    			return;
    		}
    		int mid=(l+r)>>1;
    		if(pos<=mid)
    			modify(p<<1,l,mid,pos,v);
    		else
    			modify(p<<1|1,mid+1,r,pos,v);
    		mx[p]=max(mx[p<<1],mx[p<<1|1]);
    	}
    	int query(int p,int l,int r,int ql,int qr){
    		if(ql<=l && qr>=r)
    			return mx[p];
    		int mid=(l+r)>>1;
    		int res=-INF;
    		if(ql<=mid)
    			res=query(p<<1,l,mid,ql,qr);
    		if(qr>mid)
    			ckmax(res,query(p<<1|1,mid+1,r,ql,qr));
    		return res;
    	}
    	SegmentTree(){}
    }T;
    void ins(int p){
    	int vv=s[p]+bas;
    	while(SZ(vec[vv])>lpt[vv] && dp[vec[vv].back()]<=dp[p])
    		vec[vv].pop_back();
    	vec[vv].pb(p);
    	T.modify(1,1,n+bas,vv,(SZ(vec[vv])>lpt[vv] ? dp[vec[vv][lpt[vv]]] : -INF));
    }
    void del(int p){
    	int vv=s[p]+bas;
    	if(SZ(vec[vv])>lpt[vv] && vec[vv][lpt[vv]]==p)
    		++lpt[vv];
    	T.modify(1,1,n+bas,vv,(SZ(vec[vv])>lpt[vv] ? dp[vec[vv][lpt[vv]]] : -INF));
    }
    void solve_case(){
    	cin>>n>>L>>R;
    	for(int i=1;i<=n;++i){
    		cin>>a[i];
    		s[i]=s[i-1]+a[i];
    	}
    	bas=n+1;
    	for(int i=-n;i<=n;++i){
    		vector<int>().swap(vec[i+bas]);
    		lpt[i+bas]=0;
    	}
    	T.build(1,1,n+bas);
    	for(int i=L;i<=n;++i){
    		int mid=s[i]+bas;
    		if(i-R-1==0 || i-R-1>=L){
    			del(i-R-1);
    		}
    		if(i-L==0 || i-L>=L){
    			ins(i-L);
    		}
    		dp[i]=-INF;
    		if(mid>1){
    			ckmax(dp[i],T.query(1,1,n+bas,1,mid-1)+1);
    		}
    		ckmax(dp[i],T.query(1,1,n+bas,mid,mid));
    		if(mid<n+bas){
    			ckmax(dp[i],T.query(1,1,n+bas,mid+1,n+bas)-1);
    		}
    	}
    	cout<<dp[n]<<endl;
    }
    int main() {
    	int T;cin>>T;while(T--){
    		solve_case();
    	}
    	return 0;
    }
    
  • 相关阅读:
    Oracle Merge into
    查询 null 记录
    删除 eclipse 插件
    vs 密钥
    视频网站建设
    eclipse 版本 查看
    让数据库变快的十个建议
    c# WebBrowser 操作
    svn 代码合并
    Android开发者应该深入学习的10个开源应用项目
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13502446.html
Copyright © 2011-2022 走看看