题目描述
涞坊路是一条长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个点(保险点嘛)
其实这样也可以歪打正着,拿到所有的分数