[AtCoder Grand Contest 040]D - Balance Beam

Portal
首先考虑一个固定的顺序如何计算概率,就是设 d d d m a x i = 1 n ∑ j = 1 i b ( j ) − a ( j ) max_{i=1}^n\sum_{j=1}^ib(j)-a(j) maxi=1nj=1ib(j)a(j),然后 R i n g o Ringo Ringo从起点走 d d d秒的距离占总长的比例就是答案了。证明显然。
整数部分指那些被完全经过的平衡木,小数部分指那个被部分经过的平衡木(可能不存在)。
然后先考虑如何计算答案的整数部分。由上述式子可理解为一个平衡木的贡献为 b ( i ) − a ( i ) b(i)-a(i) b(i)a(i),若它要被作为整数部分,也就是被完全经过,就会产生代价 b ( i ) b(i) b(i),综合起来它的贡献就变了 − a ( i ) -a(i) a(i)。因为一个平衡木被完全经过的话一定是产生负贡献,所以若想合法(即总和不为负),肯定是需要其它平衡木的正贡献。那么一开始可以把所有 b ( i ) − a ( i ) > = 0 b(i)-a(i)>=0 b(i)a(i)>=0的平衡木的贡献 b ( i ) − a ( i ) b(i)-a(i) b(i)a(i)加起来,结果为 s u m sum sum,但可能其中的点 i i i是被要被完全经过的,可易发现减掉 b ( i ) b(i) b(i)就可以达成这个情况了,那么 b ( i ) − a ( i ) > = 0 b(i)-a(i)>=0 b(i)a(i)>=0的平衡木贡献就不是 − a ( i ) -a(i) a(i)了,是 − b ( i ) -b(i) b(i)
所以整数部分的计算就很容易了,就是要你选尽量多的点,使得它们的贡献(即 − m a x ( a ( i ) , b ( i ) ) -max(a(i),b(i)) max(a(i),b(i)))和加 s u m sum sum不小于 0 0 0,而这个按贡献绝对值从小到大排序后贪心做就行了。但小数部分怎么办呢?其实也很容易,就是先枚举是在哪个平衡木上,然后肯定是要最大化整数部分,类似上述做就行了,不过由于不能用枚举到的平衡木,所以只能二分来求,然后 s u m sum sum加上整数部分的贡献后剩余的秒数就是在这个平衡木上走的秒数了(并没有这么简单,还有一些特殊的地方,具体看代码)。最后答案就只要所有的情况下的答案取个 m a x max max就行了。
时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)的,可以通过本题。
官方题解做法(好像本质上没有区别。。。)

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0' && ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int stack[20];
inline void write(int x)
{
	if(x<0){putchar('-');x=-x;}
	if(!x){putchar('0');return;}
	int top=0;
	while(x)stack[++top]=x%10,x/=10;
	while(top)putchar(stack[top--]+'0');
}
inline void pr1(int x){write(x),putchar(' ');}
inline void pr2(int x){write(x),puts("");}
struct node
{
	int x,y;
}a[100010];
bool cmp(node a,node b){return max(a.x,a.y)<max(b.x,b.y);}
int n;
long long sum,s[100010];
inline int erfen(int pos)
{
	int l=1,r=n,p=0,d=max(a[pos].x,a[pos].y);
	while(l<=r)
	{
		int mid=l+r>>1;
		long long uf=s[mid];if(mid>=pos)uf-=d;
		if(uf<=sum)p=mid,l=mid+1;
		else r=mid-1;
	}return p;
}
inline long long gcd(long long x,long long y)
{
	if(x==0)return y;
	else return gcd(y%x,x);
}
int main()
{
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)a[i].x=read(),a[i].y=read();
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		s[i]=s[i-1]+max(a[i].x,a[i].y);
		if(a[i].y>a[i].x)sum+=a[i].y-a[i].x;
	}
	long long ax=0,ay=1,x,y,ul;
	for(int i=1;i<=n;i++)
	{
		x=erfen(i);long long uf=sum-s[x];if(x>=i)x--,uf+=max(a[i].x,a[i].y);
		if(a[i].y>a[i].x)uf-=a[i].y-a[i].x;
		x=x*a[i].y+min(uf+a[i].y-a[i].x,1LL*a[i].y),y=1LL*n*a[i].y;ul=gcd(x,y);
		if(x<=0)continue;
		x/=ul,y/=ul;
		long long he=gcd(ay,y);
		if(1.0*ay/he*x>1.0*y/he*ax)ax=x,ay=y;//爆ll
	}
	printf("%lld %lld\n",ax,ay);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值