【CF521D】Shop(贪心)

点此看题面

  • 给定一个长度为(k)的序列。
  • (n)个操作,分单点赋值/单点加/单点乘三种。
  • 你可以从中选至多(m)个以任意顺序进行,要求最大化最终序列的乘积。
  • 求一个最优的操作方案。
  • (k,nle10^5)

初步思考

假设我们选了若干操作,显然我们先进行赋值、再进行加法、最后进行乘法一定最优。

然后我们还可以发现,我们是要求最大化最终序列的乘积,因此乘法无论是针对谁都一样,直接选较大的那些就可以了。

那么只要考虑如何处理赋值和加法两种操作就好了。

操作转化

显然一个点的若干赋值操作中我们最多只可能选择其中最大的进行。

而且赋值操作完全可以转化为加法操作。

然后我们考虑一个点的加法操作如果要选,肯定贪心地从大往小选。那么一个加法操作如果被选择了,则该点所有比它大的加法操作必然都被选掉了。

也就是说,一个加法操作执行时的数是确定的,因此我们很容易就可以把它转化为乘法操作。

注意,可以证明对于同一点,较大的加法操作转化为乘法操作之后一定更大(因为分子更大、分母更小),所以转化之后我们依然能保证选择一个加法操作时该点所有比它大的加法操作都被选择掉了。

当全都转化成乘法操作之后,直接贪心选择较大的那些就行了。

代码:(O(nlogn))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define DB long double
#define LL long long
#define pb push_back
using namespace std;
int k,n,m,a[N+5],op[N+5];
struct Data
{
	int p;DB x;I Data(CI i=0,DB a=0):p(i),x(a){}
	I bool operator < (Con Data& o) Con {return x>o.x;}//从大到小排序
}A[N+5];vector<Data> B[N+5],C;vector<Data>::iterator it;
I bool cmp(Con Data& x,Con Data& y) {return op[x.p]<op[y.p];}//输出时按操作类型顺序
int main()
{
	RI i,x,y;for(scanf("%d%d%d",&k,&n,&m),i=1;i<=k;++i) scanf("%d",a+i),A[i].x=a[i];
	for(i=1;i<=n;++i) scanf("%d%d%d",op+i,&x,&y),
		op[i]==1?(A[x].x<y&&(A[x]=Data(i,y),0)):((op[i]==2?B[x]:C).pb(Data(i,y)),0);
	for(i=1;i<=k;++i) A[i].p&&(B[i].pb(Data(A[i].p,A[i].x-a[i])),0);//赋值变加法
	LL t;for(i=1;i<=k;++i) for(sort(B[i].begin(),B[i].end()),t=a[i],//排序
		it=B[i].begin();it!=B[i].end();++it) C.pb(Data(it->p,(t+it->x)/t)),t+=it->x;//加法变乘法
	sort(C.begin(),C.end()),C.resize(m=min(m,(int)C.size())),sort(C.begin(),C.end(),cmp);//乘法直接贪心
	for(printf("%d
",m),it=C.begin();it!=C.end();++it) printf("%d ",it->p);return 0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF521D.html