zoukankan      html  css  js  c++  java
  • [LOJ#3313]「ZJOI2020」序列

    神贪 QAQ

    题目链接

    题目传送门

    Solution

    我们定义从 (i) 出发的一类操作为 (i,i+1,i+2,dots) 加一,二类操作为 (i,i+2,dots) 加一。

    先说一个结论:

    (a_1>0,a_2>0),则存在一种最优方案,至少有一次从 (1) 出发的一类操作。

    证明:

    考虑反证,分两种情况。
    (1)从 (1) 出发了一个二类操作,(2) 出发了一个一类操作:
    这时可以把 (1) 移出这个二类的操作范围,加入后面的一类操作,这样就形成了一个从 (1) 出发的一类操作和一个从 (3) 出发的二类操作,答案一定不会更劣。
    (2)从 (1) 出发了一个二类操作,(2) 出发了一个二类操作:
    设两次操作分别影响了 (x)(y) 个位置,则取 (t=min(x,y)),把两次操作影响的前 (t) 个位置并成一个长度为 (2t) 的一类操作,剩下的 (max(x,y)-t) 个位置必然会被一次二类操作覆盖,答案也不会更劣。

    于是有一个贪心:设 (t=min(a_1,a_2)),先从 (1) 出发 (t) 次一类操作(长度至少为 (2)),再从 (1) 出发 (a_1-t) 次二类操作。这样之后 (a_1=0),可以把 (a_1) 删掉之后继续考虑下一个数。

    这样存在一个问题:上面的过程只影响了 (a_1,a_2) 两个数,对后面的数的影响无法快速处理。

    故记录三个标记 (A,B,C) 表示可以当前免费从 (i+1) 出发 (A) 个一类操作和 (B) 个二类操作,从 (i+2) 出发 (C) 个二类操作。

    然后 (i)(1)(n),如果从 (a_i) 出发了 (t) 次一类操作,则只需将 (a_i)(a_{i+1}) 都减掉 (t) 之后把 (A) 加上 (t),如果从 (a_i) 出发 (s) 次二类操作,则把 (a_i) 减掉 (s)(C) 加上 (s),之后让 (i) 加一并 swap 一下 (B)(C)

    那么现在的主要问题就是处理之前的 (A,B) 标记对 (i+1) 的影响。如果 (A+Ble a_{i+1}) 则直接把 (a_{i+1}) 减掉 (A+B) 即可。

    否则 (a_{i+1}) 减为 (0),但在这之前需要确定 (x+y=a_{i+1},xle A,yle B),从 (i+1) 出发 (x) 次一类操作和 (y) 次二类操作。(x)(y) 的具体值和后面的数有关。

    (K=A+B-a_{i+1}),把 (A)(B) 分别减掉 (K),表示可以从 (i+1) 免费出发 (K)任意的操作。

    这样有另一个问题:(A)(B) 会被减成负数。

    考虑如果 (A<K),则在 (B) 次二类操作中一定至少有 (K-A) 次没有加在 (i+1) 上面。故可以把 (B) 减掉 (K-A),然后把 (K) 设为 (A)(B<K) 同理。这样之后就能保证 (Age K,Bge K) 了。

    这样处理之后就可以和之前一样,进行 (min(a_i,a_{i+1})) 次一类操作和 (max(a_i-a_{i+1},0)) 次二类操作了。

    由于存在任意的(可以为一类或二类)从 (i+1) 出发的 (K) 次操作,故执行完之前的操作之后要把 (a_{i+1}) 重新加上 (K)。又由于这 (K) 次操作免费,故这时要让答案减掉 (K)

    (O(n))

    Code

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    typedef long long ll;
    
    const int N = 1e5 + 5;
    
    int n, a[N];
    
    void work()
    {
    	ll ans = 0, A = 0, B = 0, C = 0; read(n);
    	for (int i = 1; i <= n; i++) read(a[i]); a[n + 1] = 0;
    	for (int i = 1; i <= n; i++, std::swap(B, C))
    	{
    		ll k = 0;
    		if (A + B > a[i + 1])
    		{
    			k = A + B - a[i + 1];
    			if (k > A) B -= k - A, k = A;
    			if (k > B) A -= k - B, k = B;
    			A -= k; B -= k;
    		}
    		a[i + 1] -= A + B + k; ll cnt1 = 0, cnt2 = 0;
    		if (a[i] < a[i + 1]) cnt1 = a[i];
    		else cnt1 = a[i + 1], cnt2 = a[i] - a[i + 1];
    		a[i] -= cnt1; a[i + 1] -= cnt1; A += cnt1; a[i] -= cnt2; C += cnt2;
    		ans += cnt1 + cnt2 - k; a[i + 1] += k;
    	}
    	printf("%lld
    ", ans);
    }
    
    int main()
    {
    	int T; read(T);
    	while (T--) work();
    	return 0;
    }
    
  • 相关阅读:
    Yii2 框架目录
    实现超链接在本页面的跳转
    操作系统
    shell笔记
    软RAID5制作流程
    nginx学习之简化安装篇(一)
    Javascript中实现继承的方式
    JavaScript中的函数和C#中的匿名函数(委托、lambda表达式)
    JavaScript中变量、执行环境、作用域与C#中的异同
    Javascript与C#对变量的处理方式
  • 原文地址:https://www.cnblogs.com/xyz32768/p/13292579.html
Copyright © 2011-2022 走看看