P4945 最后的战役 题解
Math_rad_round · · 题解
update: 修正代码错误
本题有两种不同做法,复杂度也不同。
这道题明显在某秒取第
操作
这样我们把题目简化到只需处理第
方案一:DP
很容易想到,设
前一个是正常收集,后一个是翻倍。
时间复杂度和空间复杂度都是
方案二:转化+贪心
这道题的题解中,有一个人睿智的提出了一种贪心方法:
如果一次加倍都不用,答案明显是
而在
所以我们可以设
很可惜的是这位同学的贪心方法错误,这道题现在类似于 P1484 正确方法是把这些数丢进优先队列,取出一个数时,将 两边的数-这个数 丢回队列。这样取出这个数等于放弃原来的数而取相邻的两个。详细解法见 P1484 的题解区 链接 。
时间复杂度
代码
DP:
#include<iostream>
#include<map>
using namespace std;
long long f[50002][501];
long long y[50002];
long long a1[50002];
long long a2[50002];
map<int,long long> u;
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a1[i]>>a2[i];
y[i]=max(y[i-1],a2[i]);
}
for(int i=1;i<=n;i++){
int k;cin>>k;
u[a1[i]]+=a2[i];
y[i]=max(y[i],u[k]);
}
f[1][0]=y[1];
for(int i=2;i<=n;i++){
f[i][0]=f[i-1][0]+y[i];
for(int j=1;j<=m;j++){
f[i][j]=max(f[i-1][j]+y[i],f[i-2][j-1]+y[i]*2);
}
}
long long ans=0;
for(int i=0;i<=m;i++){
ans=max(ans,f[n][i]);//不一定把m次用完
}
cout<<ans;
return 0;
}
转化贪心:
#include<iostream>
#include<map>
#include<queue>
using namespace std;
struct dui{
int s,t;
};
bool operator < (dui a,dui b){
return a.s<b.s;
}
priority_queue<dui> d;
long long y[50002];
long long a1[50002];
long long a2[50002];
long long t[50002];
bool q[50002];
int fr[50002],be[50002],no[50002];
map<int,long long> u;
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a1[i]>>a2[i];
y[i]=max(y[i-1],a2[i]);
}
for(int i=1;i<=n;i++){
int k;cin>>k;
u[a1[i]]+=a2[i];
y[i]=max(y[i],u[k]);
}
long long ans=y[1];
for(int i=1;i<n;i++){
ans+=y[i+1];//累加本来就有的
t[i]=y[i+1]-y[i];
fr[i]=i-1;be[i]=i+1;no[i]=t[i];
d.push(dui{t[i],i});
}
no[0]=no[n]=0;be[n]=n;
for(int i=0;i<m;i++){
dui o=d.top();d.pop();int h=o.t;
if(o.s<=0)break;//如果收益是负数自然就不需要用了
if(q[h]==1){
m++;continue;
}
q[fr[h]]=1;q[be[h]]=1;
no[h]=no[fr[h]]+no[be[h]]-o.s;
d.push(dui{no[h],h});
fr[h]=fr[fr[h]];be[h]=be[be[h]];
ans+=o.s;
}
cout<<ans;
return 0;
}