P4293 [WC2010]能量场

P4293 [WC2010]能量场

题意

给你 \(n\) 个粒子,每个粒子有两个权值 \(m_i,c_i\) 每个相邻有序对 \((a,b)\) 会产生 \(m_am_b(c_a-c_b)\) 的贡献。现让你处理两个问题:

  1. 找出一个有序对使贡献最大。
  2. 找出一个序列成环后贡献和最大。

思路

我们将贡献转化一下:

\[m_am_b(c_a-c_b)=m_ac_am_b-m_bc_bm_a \]

那么这就形成了一个叉积的形式。即将每个点 \(i\) 转化为 \(x=m_ic_i,y=m_i\) 的向量。

那么第一问就等价于求叉积最大的两个向量。具体怎么求后面再说。

那么第二问就是将若干个向量依次首尾相接地叉积和。因为所有点都在第一象限,所以这等价于求一个多边形的面积的两倍。(不会的可以自己根据叉积意义推下)

那么我们让贡献和最大,相当于求一个构成多边形面积最大的序列——凸包。于是我们求一下凸包就行了。

至于第一问,我们要快速得到两个点叉积的最大值,发现叉积最大的两个点一定在凸包上。并且发现,顺次遍历所有点并用一个指针记录另一个点的位置,发现叉积的绝对值是单调的。那么我们用类似半平面交的方法扫两遍凸包就行了,即正反各扫一遍(因为边界条件可能错误,但扫两遍一定会统计完全)。

实现

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<cmath>
using namespace std;
namespace star
{
	const int maxn=5e4+10;
	int n,m=1,ans,Ans[2],q[maxn];
	struct vec{
		double x,y;
		int id;
		vec(double x=0,double y=0,int id=0):x(x),y(y),id(id){}
		vec operator + (const vec &a) const {return vec(x+a.x,y+a.y);}
		vec operator - (const vec &a) const {return vec(x-a.x,y-a.y);}
		double operator * (const vec &a) const {return x*a.y-y*a.x;}
		bool operator < (const vec &a) const {return x<a.x or (x==a.x and y<a.y);}
	}a[maxn];
	inline void work(){
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			double a,b;
			scanf("%lf%lf",&a,&b);
			star::a[i]=vec(a*b,a,i);
		}
		sort(a+1,a+1+n);
		q[1]=1;
		for(int i=2;i<=n;i++){
			while(m>1 and (a[q[m]]-a[q[m-1]])*(a[i]-a[q[m]])<=0) m--;
			q[++m]=i;
		}
		int tmp=m;
		for(int i=n-1;i;i--){
			while(m>tmp and (a[q[m]]-a[q[m-1]])*(a[i]-a[q[m]])<=0) m--;
			q[++m]=i;
		}
		for(int i=1,j=2;i<=m;i++){
			while(fabs(a[q[i]]*a[q[j]])<fabs(a[q[i]]*a[q[j+1]])) j=j%(m-1)+1;
			double res=a[q[i]]*a[q[j]];
			if(ans<fabs(res)){
				ans=fabs(res);
				if(res>0)Ans[0]=i,Ans[1]=j;
				else Ans[0]=j,Ans[1]=i;
			}
		}
		for(int i=m,j=m-1;i;i--){
			while(fabs(a[q[i]]*a[q[j]])<fabs(a[q[i]]*a[q[j==1?m-1:j-1]])) j=j==1?m-1:j-1;
			double res=a[q[i]]*a[q[j]];
			if(ans<fabs(res)){
				ans=fabs(res);
				if(res>0)Ans[0]=i,Ans[1]=j;
				else Ans[0]=j,Ans[1]=i;
			}
		}
		printf("%d %d\n%d\n",a[q[Ans[0]]].id,a[q[Ans[1]]].id,m-1);
		for(int i=1;i<m;i++) printf("%d ",a[q[i]].id);
	}
}
signed main(){
	star::work();
	return 0;
}

补充

洛谷的另外一篇题解在代码在数据较小时进行了特判以水过第一个测试点,实际上如果不加特判其根本无法通过此题,原因很可能就是没有进行反方向统计答案。

posted @ 2021-03-06 21:58  Star_Cried  阅读(60)  评论(0编辑  收藏  举报