信息学竞赛总是时不时与数学产生微妙的关系,中位数及带权中位数问题有时常常成为解题的关键,今日有时间,所以梳理一下。
先从一到简单的题看起:
士兵站队问题
在一个划分成网格的操场上,n个士兵散乱地站在网格点上。网格点由整数坐标(x,y)表示。士兵们可以沿网格边上、下、左、右移动一步,但在同一时刻任一网格点上只能有一名士兵。按照军官的命令,士兵们要整齐地列成一个水平队列,即排列成(x,y),(x+1,y),…,(x+n-1,y)。如何选择x和y的值才能使士兵们以最少的总移动步数排成一列。
分析:这个问题我们可以把X,Y分开看,两者互不影响。其实就是求所以横坐标的中点,也就是中位数,那么为什么呢?
我们可以把所选定的位置左右的两个点看成一对,只要所选位置在两者之间,那么长度恒等于两点的线性距离和,所以我们可以根据每一对不断缩小我们所选位置的范围,最后如果有奇数个点,那么就会在中间的那个点上,如果是偶数那么在中间两个数和他们所构成的区间,这样想就容易发现中位数这一规律了。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int x[10010],y[10010]; int main() { freopen("sol.in","r",stdin); freopen("sol.out","w",stdout); int n,i,sum=0,s; cin>>n; for (i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]); sort(x+1,x+n+1); sort(y+1,y+n+1); s=y[(1+n)/2]; for (i=1;i<=n;i++) sum+=abs(y[i]-s); for (i=1;i<=n;i++) x[i]-=i; sort(x+1,x+n+1); s=x[(1+n)/2]; for (i=1;i<=n;i++) sum+=abs(x[i]-s); cout<<sum<<endl; fclose(stdin); fclose(stdout); return 0; }
[问题描述]
一些村庄被建在一条笔直的高速公路边上。我们用一条坐标轴来描述这条高速公路,每一个村庄的坐标都是整数。没有两个村庄坐标相同。两个村庄间的距离,定义为它们坐标值差的绝对值。
人们需要在一些村庄建立邮局——当然,并不是每一个村庄都必须建立邮局。邮局必须被建在村庄里,因此它的坐标和它所在的村庄坐标相同。每个村庄使用离它最近的那个邮局,建立这些邮局的原则是:所有村庄到各自所使用的邮局的距离总和最小。
你的任务是编写一个程序,在给定了每个村庄的坐标和将要建立的邮局数之后,按照上述原则,合理地选择这些邮局的位置。
输入文件的文件名是post.in
文件的第输入文件中同一行相邻两项之间用一个或多个空格隔开。
一行是包含两个整数:第一个整数是村庄的数目V,1〈=V〈=300,第二个整数是将建立的邮局数P,1〈=P〈=30且P〈=V。
文件的第二行按照递增顺序列出了V个整数。这V个整数分别表示了各村庄的位置坐标。对于每一个位置坐标X,1〈=X〈=10000。
输出文件名是post.out
文件的第一行是一个整数S,表示你所求出的所有村庄到离它最近邮局的距离的总和。
相应地,文件的第二行按照递增顺序列出了P个整数,分别表示你所求出每个邮局的建立位置。虽然对于同一个S,可能会有多种邮局建立的方案,但只需输出邮局位置尽量靠前的一组。
Example
Post.in
10 5
1 2 36 7 9 11 22 44 50
Post.out
9
2 7 2244 50
这一道题是很经典的区间动态规划题,在预处理中就用到了上述思想。
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int n,m,len[320][320],f[320][320],a[320],s[320][320],m1[320][320]; void print(int x,int y) { if (x==0) return; print(x-1,s[x][y]); printf("%d ",a[m1[s[x][y]+1][y]]); } int main() { freopen("post.in","r",stdin); freopen("post.out","w",stdout); int i,j,k; scanf("%d%d",&n,&m); for (i=1;i<=n;i++) scanf("%d",&a[i]); for (i=1;i<=n;i++) for (j=i;j<=n;j++) { m1[i][j]=(i+j)/2; int temp1=0; for (k=i;k<=j;k++) { len[i][j]+=abs(a[k]-a[m1[i][j]]); } } memset(f,127,sizeof(f)); for (i=1;i<=n;i++) { f[1][i]=len[1][i]; s[1][i]=0; } for (i=2;i<=m;i++) for (j=i;j<=n;j++) for (k=i-1;k<=j-1;k++) if (f[i][j]>f[i-1][k]+len[k+1][j]) { f[i][j]=min(f[i][j],f[i-1][k]+len[k+1][j]); s[i][j]=k; } cout<<f[m][n]<<endl; print(m,n); return 0; }中位数解决了,那么就来看一下带权中位数问题,这个问题如果不知道,一定会觉得某些题十分的高大上,无从下手。例如
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; struct data { int x,y,w; };data num[50003]; int n,i,j,k; double sum,ans,xx,yy; int xl,yl; int cmp(data a,data b) { return a.x<b.x; } int cmp1(data a,data b) { return a.y<b.y; } int main() { freopen("ball.in","r",stdin); freopen("ball.out","w",stdout); scanf("%d",&n); sum=ans=xx=yy=0; for (i=1;i<=n;i++) { scanf("%d",&num[i].w); sum+=num[i].w; } for (i=1;i<=n;i++) scanf("%d%d",&num[i].x,&num[i].y); sort(num+1,num+n+1,cmp); double mid=sum/2; for (i=1;i<=n;i++) { xx+=num[i].w; if (xx>mid) { xl=num[i].x; break; } } for (i=1;i<=n;i++) ans+=num[i].w*(abs(num[i].x-xl)); sort(num+1,num+n+1,cmp1); for (i=1;i<=n;i++) { yy+=num[i].w; if (yy>mid) { yl=num[i].y; break; } } for (i=1;i<=n;i++) ans+=num[i].w*(abs(num[i].y-yl)); printf("%0.2lf",ans); }