Codeforces 1334C - Circle of Monsters(差值取前缀和 / 贪心)


两种思路其实只差在写法上

看不懂的就直接看代码吧qwq


题面

Que




题意

n只怪物围成一圈,每只怪物拥有体力a和爆炸伤害b

如果怪物 i 死亡(体力小于等于0),则与他相邻的下一只怪物将受到 b[i] 点伤害

(如果 i<n ,则下一只怪物为 i+1 ;如果 i==n,则下一只怪物为 1)

如果相邻的下一只怪物不存在,则什么也不会发生

如果相邻的下一只怪物受到伤害后也死亡了,那么再下一只怪物会继续受到伤害(链式反应)

每次你能随便挑一只怪物开一枪,那只怪物的体力将会降低 1 点

问至少需要开多少枪才能解决掉所有怪物




解题思路 1

可以知道,只要挑出一只怪物作为最开始杀的那只,那接下来n-1只就按顺序杀下来就可以了

所以问题最主要的就是找出最开始的这一只怪物

又因为 n 最大有 3e5,每一次模拟都是O(n)的复杂度,总体时间复杂度O(n2)是绝对不可行的

想了一个多小时才想到 于是发现可以用循环数组存前缀和来模拟出所有情况的答案

但循环数组写起来太麻烦了,干脆直接开两倍长度的数组(3个6e5的long long不需要担心MLE)

读入数据存在ar和br两个数组中

并且让ar[i] == ar[i+n] && br[i] == br[i+n] ,i=1~n

再开一个 used 数组,used[i] 记录从编号为 1 的怪物杀到编号为 i 怪物的过程中需要用掉多少颗子弹

注意used只记录过程中,不记录杀编号为 1 的怪物用掉的子弹,处理过程如下:

  used[1] = 0 特殊处理

  i 从2开始,如果第 i 个怪物体力小于等于第 i-1 个怪物的爆炸伤害,说明只要前一只怪物死亡那这一只也会跟着死亡,对答案无贡献 used[i] = used[i-1]

  如果第 i 个怪物体力大于第 i-1 个怪物的爆炸伤害,说明还需要补上 ar[i] - br[i-1] 颗子弹才会死亡 used[i] = used[i-1] + ar[i] - br[i-1]

因为原本是个循环数组,所以used需要处理到2*n过(实际上只要2*n-1)

全部处理完后,那么所有情况就出来了

比如说我们选择以 i=1 的怪物作为开始

那么首先花费 ar[1] 颗子弹把这只怪物杀死

然后从第1只开始杀n只,最后一只怪物的编号为 i=n

所以过程中还需要多花费 used[n] - used[1] 颗子弹

所以这种情况的答案就是 ar[1] + used[n] - used[1]

推广,对于 i∈[1,n] 的每种情况,答案为 ar[i] + used[i+n-1] - used[i]

取小输出即可




程序 1

把处理used数组的步骤和计算答案的步骤合起来

则处理答案的范围为 n~2*n-1

(280ms/1000ms)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll LINF=0x3f3f3f3f3f3f3f3f;

ll ar[600050],br[600050],used[600050];
void solve()
{
    int n;
    cin>>n;
    ll ans=LINF;
    for(int i=1;i<=n;i++)
    {
        cin>>ar[i]>>br[i];
        ar[n+i]=ar[i];
        br[n+i]=br[i];
    }
    used[1]=0;
    for(int i=2;i<2*n;i++)
    {
        if(ar[i]<=br[i-1])
            used[i]=used[i-1];
        else
            used[i]=used[i-1]+ar[i]-br[i-1];
        if(i>=n)
            ans=min(ans,used[i]-used[i-n+1]+ar[i-n+1]);
    }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int T;cin>>T;
    for(int t=1;t<=T;t++)
        solve();
    return 0;
}



解题思路 2

可以发现,思路1中的used[i+n] - used[i]其实是一个定值(注意不是 i+n-1 和 i )

这个定值表示的就是忽略杀掉第一只怪物的花费,在过程中总共需要花费的子弹数

令这个定值为 k ,k的求法即为k = k + sum{max(0,ar[i]-br[i-1])}

有了这个定值,我们只需要找出杀第一只怪物最少的花费与其相加就是答案了

令杀第一只怪物最少的花费为 minn

与思路1的式子联立答案方程能得到

used[i+n] - used[i] + minn == used[i+n-1] - used[i] + ar[i]

ar[i] - minn == used[i+n] - used[i+n-1] == used[i] - used[i-1]

所以可以知道 ar[i] 与 minn 的差值就在于 used[i] - used[i-1] 这一步

而这个差值也就代表着某一步需要多消耗的子弹数

得到minn == ar[i] - (used[i] - used[i-1])

对于第 i 只怪物:

如果br[i-1] >= ar[i]

  说明前一只死亡后会顺带让第 i 只怪物死亡

  所以在 k 的计算过程中并没有把杀死第 i 只怪物的花费记录进去

  即代表此时的used[i] - used[i-1] == 0

  所以此时可以得到 minn = min(minn,ar[i])

如果br[i-1] < ar[i]

  说明前一只死亡后并不会顺带让第 i 只怪物死亡

  所以在 k 的计算过程中会加入 ar[i] 和 br[i-1] 的差值作为需要补的子弹数

  即代表此时的used[i] - used[i-1] == ar[i] - br[i-1]

  所以此时可以得到 minn = min(minn,br[i-1])

最后再判断下编号为1和编号为n的这两只的关系




程序 2

(280ms/1000ms)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll LINF=0x3f3f3f3f3f3f3f3f;

ll ar[300050],br[300050];
void solve()
{
    int n;
    ll k=0,minn=LINF,d;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>ar[i]>>br[i];
        d=ar[i]-br[i-1];
        if(i)
        {
            if(d>0)
                minn=min(minn,br[i-1]),k+=d;
            else
                minn=min(minn,ar[i]);
        }
    }
    d=ar[0]-br[n-1];
    if(d>0)
        minn=min(minn,br[n-1]),k+=d;
    else
        minn=min(minn,ar[0]);
    cout<<k+minn<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int T;cin>>T;
    for(int t=1;t<=T;t++)
        solve();
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/stelayuri/p/12677416.html