标签:src 也会 顺序 问题 贪心 编号 min for 前缀
两种思路其实只差在写法上
看不懂的就直接看代码吧qwq
n只怪物围成一圈,每只怪物拥有体力a和爆炸伤害b
如果怪物 i 死亡(体力小于等于0),则与他相邻的下一只怪物将受到 b[i] 点伤害
(如果 i<n ,则下一只怪物为 i+1 ;如果 i==n,则下一只怪物为 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]
取小输出即可
把处理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;
}
可以发现,思路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的这两只的关系
(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;
}
Codeforces 1334C - Circle of Monsters(差值取前缀和 / 贪心)
标签:src 也会 顺序 问题 贪心 编号 min for 前缀
原文地址:https://www.cnblogs.com/stelayuri/p/12677416.html