P3819 松江1843路

题目描述

涞坊路是一条长L米的道路,道路上的坐标范围从0到L,路上有N座房子,第i座房子建在坐标为x[i]的地方,其中住了r[i]人。

松江1843路公交车要在这条路上建一个公交站,市政府希望让最多的人得到方便,因此希望所有的每一个的居民,从家到车站的距离的总和最短。

公交站应该建在哪里呢?

输入输出格式

输入格式:

第一行输入L、N。

接下来N行,每行两个整数x[i]和r[i]。

输出格式:

一个整数,最小的每个人从家到车站的距离的总和。

输入输出样例

输入样例#1

100 3
20 3
50 2
70 1

输出样例#1:

110

输入样例#2

100 2
0 1
100 10

输出样例#2

100

输入样例#3

10000000000 5
3282894320 391
4394338332 929
6932893249 181
7823822843 440
9322388365 623

输出样例#3

5473201404068

说明

样例解释1

当建在坐标40的时候,所有人距离车站的距离总和为 |20−40|×3+|50−40|×2+|70−40|×1=110。

数据范围和约定

对于10%的数据,1≤N≤50,R[i]=1。

对于30%的数据,1≤N≤100,R[i]≤10,1≤L≤1000。

对于70%的数据,1≤N≤1000,R[i]≤100,1≤L≤10^6。

对于全部数据,1≤L≤10^10,1≤N≤10^5,0≤x[i]≤L,1≤r[i]≤1000

这一道题一眼看上去好像没有什么头绪,不过有一句谚语说的好:“暴力出奇迹”

我看到这一题,就觉得应该会有某种规律,比如说这个最优点会出现在某个特定的位置

因此,我打了一个暴力程序和一个出数据的程序,来找每一个点的距离,时间是O(L)

我还打了一个前缀和的优化:

假设当前在点k,那么左边距离总和就是

for(1-t) sum+=(k-x[i])*r[i]

用整式乘法得到

for(1-t) sum+=k*r[i]
for(1-t) sum-=x[i]*r[i]

两个式子分别用sum1[]和sum2[]来记录前缀和



右边的距离总和是

for(t+1 - n) sum+=(x[i]-k)*r[i]

用整式乘法得到

for(t+1 - n) sum+=x[i]*r[i]
for(t+1 - n) sum-=k*r[i]

两个式子也可以用sum1[]和sum2[]来记录前缀和

然后,我发现这个点好像总是在某个房子那里

为了验证这个猜想,我们应该用证明,但是我不会证明,于是我就打了一个对拍来判断是否满足这个猜想

对拍的时候每次判断vio.out是否为yes就行了

暴力程序如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<queue>
#include<stack>
#include<cmath>
#include<map>
using namespace std;
inline int read(){
    int x=0,f=0;char s=getchar();
    while(!isdigit(s))f|=s=='-',s=getchar();
    while( isdigit(s))x=(x<<1)+(x<<3)+s-48,s=getchar();
    return !f?x:-x;
}
struct node{
	int x,r;
	inline bool operator<(const node k)const{
		return x<k.x;
	}
}a[110];
int n,L;
int sum1[110],sum2[110];
int main(){
	freopen("data.in","r",stdin);
	freopen("vio.out","w",stdout);
	L=read();n=read();
	for(int i=1;i<=n;i++)a[i].x=read(),a[i].r=read();
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++)
		sum1[i]=sum1[i-1]+a[i].r,
		sum2[i]=sum2[i-1]+a[i].r*a[i].x;
	int now=1,minn=999999999,pos;
	for(int i=a[1].x;i<=a[n].x;i++){
		if(a[now+1].x==i)now++;
		int left=i*sum1[now]-sum2[now];
		int right=(sum2[n]-sum2[now])-i*(sum1[n]-sum1[now]);
		if(left+right<minn)minn=left+right,pos=i;
		else if(left+right==minn){
			if(a[now].x==i)
				pos=i;
			}
		//printf("%d ",left+right);
	}
	//printf("\n%d %d\n",minn,pos);
	for(int i=1;i<=n;i++)if(pos==a[i].x){printf("yes\n");return 0;}
	printf("no\n");
	return 0; 
}

相信自动生成数据的程序和对拍各位都会打

我对拍了十几分钟,发现这个方案可行,于是就可以直接模拟了

时间复杂度O(nlogn)主要是快排的时间

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<queue>
#include<stack>
#include<cmath>
#include<map>
using namespace std;
typedef unsigned long long ULL;
const int N=1e5+10;
struct node{
	ULL x,r;
	inline bool operator<(const node k)const{
		return x<k.x;
	}
}a[N];
int n;ULL L;
ULL sum1[N],sum2[N];
int main(){
	cin>>L>>n;
	for(int i=1;i<=n;i++)cin>>a[i].x>>a[i].r;
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++)
		sum1[i]=sum1[i-1]+a[i].r,
		sum2[i]=sum2[i-1]+a[i].r*a[i].x;
	ULL minn=1000000000000000010;
	for(int i=1;i<=n;i++){
		ULL left=a[i].x*sum1[i]-sum2[i];
		ULL right=(sum2[n]-sum2[i])-a[i].x*(sum1[n]-sum1[i]);
		if(left+right<minn)minn=left+right;
	}
	cout<<minn<<endl;
	return 0; 
}

如果在考场上,没有时间考虑方法了,就直接打部分分吧

反正

对于70%的数据1≤L≤10^6

其他的数据1<=L<=10^10

那我们就可以枚举每一个n

保险点,考场计算机每秒又10^8

那我们就可以用不超过10^8的时限

枚举每个房子周围的800个点(保险点嘛)

其实这样也可以歪打正着,拿到所有的分数

猜你喜欢

转载自blog.csdn.net/zsyzClb/article/details/84328942