zoukankan      html  css  js  c++  java
  • P6631 [ZJOI2020] 序列

    可以将问题用形象的方式来表述。给定一排点,第 (i) 个点有它需要的覆盖次数 (a_i)。有两种线段,一种能覆盖连续的一些点,称其为连续线段;另一种能覆盖相邻间隔为 (1) 的一些点,称其为为间隔线段。现在要用尽可能少的线段覆盖每个点 (i) 恰好 (a_i) 次。

    发现如果没有间隔线段就是被某组织出烂的原题。显然,每次取最长的非 (0) 段最优。

    回到本题,来证明一个结论:假设 (a_1sim a_i>0)(a_{i+1}=0)(i>1),选一条从 (1sim i) 的连续线段一定不劣。

    (1sim i) 分成奇数偶数两组,那么每组都可以用间隔线段完全覆盖且不会影响到另一组,这意味着如果不用连续线段两者可以用上面的方法单独计算

    反之如果我们选择了一条 (1sim i) 的连续线段,两组均会被完全覆盖,代价减少了 (1),看起来会更优

    但现在点 (i) 所在的组可能对后面产生贡献(覆盖到点 (i+2)),而另一组不行。所以我们可以以点 (i+2) 为开头选一条间隔线段,加上 (1sim i) 的连续线段,代价为 (2)不劣于选两条间隔线段。

    接着怎么做呢?唯一需要考虑的就是前面有线段连过来。在处理点 (i) 时,记连过来的连续线段数量为 (x),能覆盖到点 (i) 的间隔线段数量为 (y),不能覆盖到点 (i) 的间隔线段数量为 (z)

    首先处理一下特殊情况:连过来的线段太多了,即 (x+y>a_i),有 (kgets x+y-a_i) 条线段不能向后继续连。但我们会发现留下间隔线段还是连续线段是由后面的点决定的,所以不妨将 (xgets x-k)(ygets y-k)反悔标记置为 (k),表示可以免费连向下一个点的线段个数(线段的种类可以任意选)。但是这样会出现一种情况,(x<k)(y<k),此时的反悔可能是不合法的。对于这种情况,我们将少的一种线段置为 (0),另一种线段对应减少,即可保证合法。

    然后就好办了,根据之前的结论,处理 (a_{i-1})(a_i)。能用连续线段就用,如果 (i-1) 还有剩下就用间隔线段。

    当然还有一些需要注意的细节。

    • 如果更新了标记,最后需要将 (a_i) 置为 (k),答案减去 (k),同时清空标记。
    • 尽量从 (i=2) 开始做,防止出现一些边界问题。
    • 最后多做一次,计算 (n) 位置的答案。

    单组数据时间复杂度 (O(n))

    code:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define Min(x,y)((x)<(y)?x:y)
    int read()
    {
    	int A;
    	bool K;
    	char C;
    	C=A=K=0;
    	while(C<'0'||C>'9')K|=C=='-',C=getchar();
    	while(C>'/'&&C<':')A=(A<<3)+(A<<1)+(C^48),C=getchar();
    	return(K?-A:A);
    }
    int main()
    {
    	int t,n;
    	ll last,now,k,x,y,z,tag,ans,tmp;
    	t=read();
    	while(t--)
    	{
    		n=read(),last=read();
    		ans=tag=x=y=z=0;
    		while(n--)
    		{
    			now=(n?read():0);
    			if(now<x+y)
    			//处理多余的线段 
    			{
    				k=x+y-now;
    				//必须要结尾的线段 
    				if(x<k)y-=k-x,k=x;
    				if(y<k)x-=k-y,k=y;
    				x-=k;
    				y-=k;
    				now-=k;
    				tag=k;
    				//向后所能免费延伸的线段 
    			}
    			now-=x+y;
    			//新的必须经过当前点的线段 
    			tmp=Min(last,now);
    			ans+=tmp;
    			last-=tmp;
    			now-=tmp;
    			x+=tmp;
    			//用连续线段覆盖 
    			ans+=last;
    			z+=last;
    			//用间隔线段覆盖 
    			last=now|tag;
    			ans-=tag;
    			tag=0;
    			//处理标记 
    			swap(y,z);
    			//奇偶互换 
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    
  • 相关阅读:
    秒杀多线程第十篇 生产者消费者问题 (续)
    平面最近点距离问题(分治法)
    阿里神马搜索算法实习生 二面
    37. Sudoku Solver
    52. N-Queens II(数个数)
    51. N-Queens
    89. Gray Code(公式题)
    22. Generate Parentheses(回溯)
    回溯总结
    40. Combination Sum II
  • 原文地址:https://www.cnblogs.com/May-2nd/p/13739871.html
Copyright © 2011-2022 走看看