牛场围栏详解

John计划为他的牛场建一个围栏,以限制奶牛们的活动。他有N种可以建造围栏的木料,长度分别是l1,l2…lN,每种长度的木料无限。修建时,他将把所有选中的木料拼接在一起,因此围栏的长度就是他使用的木料长度之和。但是聪明的John很快发现很多长度都是不能由这些木料长度相加得到的,于是决定在必要的时候把这些木料砍掉一部分以后再使用。不过由于John比较节约,他给自己规定:任何一根木料最多只能削短M米。当然,每根木料削去的木料长度不需要都一样。不过由于测量工具太原始,John只能准确的削去整数米的木料,因此,如果他有两种长度分别是7和11的木料,每根最多只能砍掉1米,那么实际上就有4种可以使用的木料长度,分别是6, 7, 10, 11。
Clevow是John的牛场中的最聪明的奶牛,John请她来设计围栏。Clevow不愿意自己和同伴在游戏时受到围栏的限制,于是想刁难一下John,希望John的木料无论经过怎样的加工,长度之和都不可能得到她设计的围栏总长度。
不过Clevow知道,如果围栏的长度太小,John很快就能发现它是不能修建好的。因此她希望得到你的帮助,找出无法修建的最大围栏长度。

输入格式:

输入文件的第一行包含两个整数N, M ,分别表示木料的种类和每根木料削去的最大值。以下各行每行一个整数li(1<li<3000),表示第i根木料的原始长度。

输出格式:

输出文件仅一行,包含一个整数,表示不能修建的最大围栏长度。如果任何长度的围栏都可以修建或者这个最大值不存在,输出-1。

样例输入:

2 1
7 
11

样例输出:

15

数据范围:

(1<N<100, 0<M<3000)
(1<li<3000),

时间限制:

5S

空间限制:

256M

我们可以由N和M以及I1 ,I2…IN,得到实际上可使用的木料长度,我们可以定义一个一维数组L:array[0..maxl] of boolean,L[i]为true表示有长度i的木料,这一部分算法很简单,在此就不再赘述了。

 

然后我们的目标就是找到不能用已有的木料修建的最大围栏长度。其中有一个问题是我们可以事先排除所有无解的情况,无解的情况有两种:第一种是任意长度的围栏都可以修建,第二种是这个最大值不存在。

 

我们先来考虑第一种情况,任意长度的围栏都可以修建等价于有长度为1的木料,即L[1]=true,这是非常容易证明的:

充分性:长度为k的围栏可以用k根长度为1的木料修建。

必要性:若任意长度的围栏都可以修建,则长度为1的围栏可以修建。即必须有长度为1的木料。

所以存在长度为1的木料是任意长度的围栏都可以修建的充要条件。

我们只需通过判断L[1]来排除第一种无解情况。

 

再来考虑第二种情况,第二种情况要比第一种复杂些。但是我们仍然可以证明最大值不存在等价于所有木料长度的最大公约数大于1:(这种情况不事先排除,在求解过程中也可将其排除,竞赛时可以不用考虑如此周密)

先证充分性:设所有木料长度的最大公约数为p,p>1,则所有的木料都可以表示成p*Li,则所有木料可以修建的围栏长度为S= k1*p*L1 + k2*p* L2+…+ kt* p*Lt其中t为所有不同长度的木料的种类数,则S=p*( k1*L1+ k2* L2+…+ kt*Lt),所有可修建的长度都为p的整数倍,即所有不为p的整数倍的数都不可能修建,因此不存在最大值。

再证必要性:若不存在最大值,则我们考虑长度最短的一根木料,不妨设长度为r,我们可以分别考虑模r的每一个剩余类,记K i={x| x∈Z,x≡i(modr)},i=0,1,…,r-1(这些数论中的定义参见附录)

K 0中的长度是都可以修建的(要得到k*r,只需取k根长度为r的木料),令Qi为Ki中的长度为不能修建的围栏的最大值(i=1,2,…,r-1),则不能修建的围栏的最大长度为max{ Q1,Q2,…, Qr-1}。若不存在最大值,则至少有一个剩余类不存在最大值。不妨设Kj(j>0, j∈Z)不存在最大值,则在Kj中的长度都不可能被修建,证明如下:

当Kj中存在可以被修建的围栏的长度,取其中最短的长度,不妨设为k*r+j,k∈Z,其中k必然大于0(这一点可以利用r的最小性证明),则(k-1)*r+j为无法修建的围栏长度,且(k+u)*r+j,u>0,u∈Z都可以修建(在修建长度为k*r+j的围栏所用木料的基础上多增加u根长度为r的木料),所以Kj中存在最大值,矛盾。

设模r的剩余类中存在最大值的剩余类为Ka1,Ka2,…,Kaw,w为存在最大值的剩余类的个数,我们可以证明(a1,a2,…,aw)>1。证明如下:

可以先证一个引理1:若(i,j)=1,则取0*i,1*i,…,(j-1)*i,可以证明这j个数模j两两不同余,即这j个数可以取遍模j的完系。证明如下:

若p*i与q*i同余,不妨设0≤p<q<j,则j|q*i-p*i,即j|(q-p)*i,因为(i,j)=1,所以j|q-p,0<q-p<j,所以矛盾。因此这j个数模j两两不同余。

可以推出另一个引理2:若(i,j)=d,i=p*d,j=q*d,则取0*i,1*i,…,(q-1)*i,可以证明这q个数模q两两不同余,这一点可以直接由上面引理1得到,则这q个数模j分别为0*d,1*d,…,(q-1)*d。

再证一个引理3:i|r,j|r,若(i,j)=1,令S=a*i+b*j,a,b>=0且a,b∈Z,则S可取遍r的完系。证明如下:

由(p,q)=1,我们可以取i,i*2,…,i*j这j个数是模j的一个完系,且ij|r,因此S可取遍r的一个完系。

同样我们还可推出一个引理4:i|r,j|r,(i,j)=d,i=p*d,j=q*d,r=k*d,令S=a*i+b*j,a,b>=0且a,b∈Z,则S可取遍模r分别为0*d,1*d,…,(k-1)*d的剩余类。(由引理3,仿照引理2推得)

依次考虑每一个存在最大值的剩余类Ka1,Ka2,…, Kaw

则对于每一个剩余类Kai,则所有的剩余类Kj都有最大值,其中(r,ai)|j(这一点可由引理2得到),令bi=(r,ai),则Kbi有最大值。

依次考虑Kb1,K b 2,…, Kbw

令d=(b1,b2,…,bw),则所有的剩余类Kj都有最大值,其中d|j(这是有引理4得到的)。

因此不存在最大值时,d>1,原先的木料也可以表示为,k*r+bi,因此所有的木料的最大公约数大于1。

所以最大值不存在等价于所有木料长度的最大公约数大于1。

 

考虑完无解情况,其实在分析过程中也把握了题目的本质,我们可以通过求得每一个剩余系的能修建的围栏的长度的最小值来推出不能修建的最大值。

那么,我们很容易联想到图论中求最短路径的Dijkstra算法,套用在本题上,即是把每一个剩余类抽象成一个点,而所有的木料即是点与点之间的边,因此我们需要保存邻接矩阵,但是邻接矩阵可能会很大,因此构造邻接矩阵需要很大的时间和空间上的花费,但是通过先前证明时的仔细分析,我们已经将问题转化到了剩余类上,而每个剩余类可用木料的木料都是相同的,因此每个剩余类的边都有相同的规律,因此仅需保存一张对应表,对于所有点都适用。

仍然沿用r在证明时的定义。

定义一维数组q:array[0..maxl] of integer

对于长度为k的木料,则在模r的意义下,赋值q[k mod r]=k div r,即是使用一根长为k的木料相当于k div r根长为r的木料再加k mod r的长度,当然我们还应注意到会有多根木料在模r的意义下同余的情况,那么我们只需记录最短的木料(长的木料可由短的木料代替,而短的则不可能由长的代替)。

我们还可以将数组q处理一下,可使数据更紧凑。

定义一维数组o:array[0..maxl] of integer和p:array[0..maxl] of integer

我们对于每一个有赋值的q[i](i>0,当i=0时在模r的意义下在加一根r仍然在原来的剩余类中),将i记录在数组o中,将q[i]对应的记录在数组p,记录时顺序记录即可,这样的好处是,不同木料种类为k,则具体实现时循环只需循环k次,而不是maxl次。

对于点i,若i+o[j]<r,则有连向i+o[j]的边,边上的权值为p[j],若i+o[j]≥r,则有连向(i+o[j]) mod r的边,边上的权值为p[j]+1(这一点是很明显的,将i+o[j]折算成(i+o[j]) mod r+r),这样我们遍可套用经典算法来完成每个剩余类的最大值的计算。

关于这个过程我在此就测试样例做一下演示:

初始时有7,11两种木料,每种木料可以削去1,即有6,7,10,11四种木料,取r=6,就形成0,1,2,3,4,5六个剩余类。

对于数组q,q[0]=1,q[1]=1,q[4]=1,q[5]=1

对于数组o和p,o[1]=0,p[1]=1,o[2]=1,p[2]=1,o[3]=4,p[3]=4,o[4]=5,p[4]=5,k=4

重复利用数组q,来保存每个点的权值

打(*)表示无须再访问。*表示未赋值(暂时还未能修建这类长度的围栏)

    q[0]   q[1]   q[2]   q[3]   q[4]   q[5]

开始时   1(*)   1     *     *      1      1

第1次   1(*)   1(*)   2     *      1      1    选择q[1]

第2次   1(*)   1(*)   2     3      1(*)   1    选择q[4]

第3次   1(*)   1(*)   2     3      1(*)   1(*)  选择q[5]

第4次   1(*)   1(*)  2(*)   3     1(*)   1(*)   选择q[2]

第5次   1(*)   1(*)   2(*)   3(*)   1(*)   1(*)   选择q[3]

跳出循环

 

最后还需将最小的可修建长度变换成不能修建的最大值,这一步变换也很简单,其实前面证明时也已提到过,对于q[i],最大值即为(q[i]-1)*r+I,所有剩余类的最大值的最大值即为所求结果。

当然在跳出循环时若还存在未赋值的剩余类,则说明这种情况即是第二种无解情况。

本题至此就可以圆满解决。时间复杂度即为Dijkstra算法的复杂度O(N^2),N<=3000

 

附录

定义1

设m为一给定的正整数,则全体整数可以分为m个集合K0,K1,…,Km-1,这里K r={x| x∈Z,x≡r(modm)},r=0,1,…,m-1

我们称K0,K1,…,Km-1为模m的剩余类

定义2

若m个整数a0,a1,…, am-1中不存在两个数属于模m的同一个剩余类,则称此m个数a0,a1,…,am-1为模m的一个完全剩余系,简称模m的完系


 #include<bits/stdc++.h>
using namespace std;
int n,m;
int length[4001234],minl,maxl,kinum;
int dis[3001];
bool used[3001];
int way[3001][3001];
const int INF=1401401003;
int gcd(int x,int y)
{
if(y==0) return x;
return gcd(y,x%y);
}
void dijkstra()
{
dis[0]=0;
used[0]=1;
int nownum=0;
for(int i=0;i<minl;i++)
{
dis[i]=way[0][i];
}
for(int i=1;i<minl;i++)
{
int minle=INF+1,minnu=-1;
for(int j=0;j<minl;j++)
{
if(used[j]) continue;
if(minle>dis[j])
{
minnu=j;
minle=dis[j];
}
}
used[minnu]=1;
for(int j=1;j<minl;j++)
{
if(j==minnu) continue;
dis[j]=min(dis[j],dis[minnu]+way[minnu][j]);
}
}
}
int main()
{
int asd=0;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int len;
cin>>len;
for(int j=0;j<=m;j++)
{
if(j>=len) break;
length[++kinum]=len-j;
}
}
asd=length[1];
for(int i=2;i<=kinum;i++)
{
asd=gcd(max(asd,length[i]),min(asd,length[i]));
if(asd==1) break;
}
minl=INF;
for(int i=1;i<=kinum;i++)
{
minl=min(minl,length[i]);
}
if(asd!=1||minl==1)
{
cout<<"-1\n";
return 0;
}
//Input is done.
for(int i=0;i<minl;i++)
for(int j=0;j<minl;j++)way[i][j]=INF;
for(int i=0;i<minl;i++) way[i][i]=0;
for(int i=1;i<=kinum;i++)
{
for(int j=0;j<minl;j++)
{
int pg=(j+length[i])%minl;
way[j][pg]=min(way[j][pg],length[i]);
}
}
dijkstra();
int ans=dis[1];
for(int i=1;i<minl;i++)
{
if(dis[i]>ans) ans=dis[i];
}
ans-=minl;
cout<<ans<<endl;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值