【题目】
题目传送门种树
题目描述:
A 城市有一个巨大的圆形广场,为了绿化环境和净化空气,市政府决定沿圆形广场外圈种一圈树。园林部门得到指令后,初步规划出 n 个种树的位置,顺时针编号 1 到 n。并且每个位置都有一个美观度 Ai,如果在这里种树就可以得到这 Ai 的美观度。但由于 A 城市土壤肥力欠佳,两棵树决不能种在相邻的位置(i 号位置和 i + 1 号位置叫相邻位置。值得注意的是 1 号和 n 号也算相邻位置!)。最终市政府给园林部门提供了 m 棵树苗并要求全部种上,请你帮忙设计种树方案使得美观度总和最大。如果无法将 m 棵树苗全部种上,给出无解信息。
输入格式:
输入的第一行包含两个正整数 n、m 。
第二行n个整数 Ai。
输出格式:
输出一个整数,表示最佳植树方案可以得到的美观度。如果无解输出“Error!”,不包含引号。
样例数据:
【样例1】
输入
7 3 1 2 3 4 5 6 7
输出
15
【样例2】
输入
7 4 1 2 3 4 5 6 7
输出
Error!
备注:
【数据规模】
对于全部数据:m ≤ n;-1000 ≤ Ai ≤ 1000
N的大小对于不同数据有所不同:
数据编号 | N的大小 | 数据编号 | N的大小 |
1 | 30 | 11 | 200 |
2 | 35 | 12 | 2007 |
3 | 40 | 13 | 2008 |
4 | 45 | 14 | 2009 |
5 | 50 | 15 | 2010 |
6 | 55 | 16 | 2011 |
7 | 60 | 17 | 2012 |
8 | 65 | 18 | 199999 |
9 | 200 | 19 | 199999 |
10 | 200 | 20 | 200000 |
【分析】
无解情况:如果 2 * m > n,那么就无解(因为树不能种在相邻的位置)
先考虑一下这道题的简化版,即如果树可以种在相邻位置,那我们直接从大到小排序选前 m 个就可以了
现在加了限制,假设第一次选的时候 A[ x ] 最大,按照之前的思路,我们试图选 A[ x ],然后同时删去 A[ x ],A[ x - 1 ],A[ x + 1 ],不过这样做有一个问题,就是虽然 A[ x - 1 ] 和 A[ x + 1 ] 都小于 A[ x ],但是有可能 A[ x - 1 ] + A[ x + 1 ] 就比 A[ x ] 大了,这种时候肯定选 A[ x - 1 ] 和 A[ x + 1 ] 比选 A[ x ] 更优
正解的做法是选到 A[ x ] 的时候,还是将 A[ x - 1 ] 和 A[ x + 1 ] 删掉,将 A[ x ] 的值改为 A[ x - 1 ] + A[ x + 1 ] - A[ x ],这相当于有一个“反悔”的操作,如果后来再次选到了 A[ x ],那就相当于选到了 A[ x - 1 ] 和 A[ x + 1 ]
由于有删点的操作,所以说到后面 x 两边的点不一定就是 x - 1 和 x + 1,我们要用两个数组记录一下它两边的点
【代码】
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
int a[N],l[N],r[N];
bool del[N];
priority_queue<pair<int,int> >q;
int main()
{
int n,m,i,x,t,ans=0;
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i)
scanf("%d",&a[i]);
if(2*m>n)
{
printf("Error!");
return 0;
}
for(i=1;i<=n;++i)
{
l[i]=i-1;
r[i]=i+1;
q.push(make_pair(a[i],i));
}
l[1]=n;
r[n]=1;
memset(del,false,sizeof(del));
for(i=1;i<=m;++i)
{
t=q.top().first;
x=q.top().second;
q.pop();
while(del[x])
{
t=q.top().first;
x=q.top().second;
q.pop();
}
ans+=t;
a[x]=a[l[x]]+a[r[x]]-a[x];
del[l[x]]=true;
del[r[x]]=true;
l[x]=l[l[x]];
r[x]=r[r[x]];
r[l[x]]=x;
l[r[x]]=x;
q.push(make_pair(a[x],x));
}
printf("%d",ans);
return 0;
}