二分答案小结
by QTY_YTQ
我们一般是根据条件求解问题答案,有时候我们却可以枚举答案并判断是否可行或是否最优来得到最终的真实答案,我们此时可以使用二分枚举,通过对半切割,不断缩小最终答案所在的区间,最后找到答案,这样可以让枚举时间效率变为log(n),使时间效率得到提升。当我们遇到有诸如“使最大值最小”这样要求的问题时,可以考虑二分答案求解,下面是一些二分答案的一点总结。
二分答案比较合适的写法:
有时候会这样写:
while l<r do begin mid:=(l+r) div 2; if cheak(mid)=true then r:=mid-1 else l:=mid; end;
然而这样写适合于求最大值最小这样的情况,如果求最小值最大,当l+1=r且cheak(mid)=false时,l会被赋值为mid,而mid=(l+r) div 2=l,会一直循环的。
最好这样写:
while l<=r do begin mid:=(l+r) div 2; if cheak(mid)=true then begin ans:=mid; r:=mid-1; end else l:=mid+1; end;
收入计划(income.???)
【问题描述】
高考结束后,同学们大都找到了一份临时工作,渴望挣得一些零用钱。从今天起,Matrix67将连续工作N天(1<=N<=100000)。每一天末他可以领取当天及前面若干天里没有领取的工资,但他总共只有M(1<=M<=N)次领取工资的机会。Matrix67已经知道了在接下来的这N天里每一天他可以赚多少钱。为了避免自己滥用零花钱,他希望知道如何安排领取工资的时间才能使得领到工资最多的那一次工资数额最小。
注意:Matrix67必须恰好领工资M次,且需要将所有的工资全部领走(即最后一天末需要领一次工资)。
【输入数据】
第一行输入两个用空格隔开的正整数N和M。
以下N行每行一个不超过10000正整数,依次表示每一天的薪水。
【输出数据】
输出领取到的工资的最大值最小是多少。
【输入样例】
7 5
100
400
300
100
500
101
400
【输出样例】
500
【样例说明】
采取下面的方案可以使每次领到的工资不会多于500。这个答案不能再少了。
100 400 300 100 500 101 400 每一天的薪水
<------1 <-------2 <---3 <---4 <---5 领取工资的次数
500 400 500 101 400 领取到的工资
分析:
要求我们使领到的工资最大值最小,自然会想到二分,我们可以二分领到工资最最大值,并利用贪心的思想进行判断,每次从左向右扫描,对于每一天,如果这一天工资加之前没领取的工资小于等于最大值,那么可以在与之前的工资一起领取;如果超过了,就必须在分开来在下一次领取,也就是让每一次领取的工资在不超过最大值前提下尽可能大,这样可以得到至少需要另几次工资,若最少领取工资次数大于m,说明不可行,真实答案会大于这个工资最大值,改变下界,继续二分,否则改变上界,继续二分,直到找到答案。
代码:
program income; var a:array[0..100000]of longint; n,i,m,s,t,l,r,mid:longint; flag:boolean; begin assign(input,'income.in'); reset(input); assign(output,'income.out'); rewrite(output); readln(n,m); for i:=1 to n do begin read(a[i]);r:=r+a[i]; if a[i]>l then l:=a[i]; end; while l<r do begin mid:=(l+r) div 2; s:=0; t:=0; for i:=1 to n do begin if s+a[i]>mid then begin s:=0; t:=t+1; end; if s+a[i]<=mid then s:=s+a[i]; end; t:=t+1; if t>m then flag:=false else flag:=true; if flag=true then r:=mid else l:=mid+1; end; writeln(r); close(input); close(output); end.
关押罪犯(prison.???)
【问题描述】
S城现有两座监狱,一共关押着N名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c的冲突事件。每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S城Z市长那里。公务繁忙的Z市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。在详细考察了N名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使Z市长看到的那个冲突事件的影响力最小?这个最小值是多少?
格式
【输入格式】
输入文件的每行中两个数之间用一个空格隔开。
第一行为两个正整数N和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。
接下来的M行每行为三个正整数aj,bj,cj,表示aj号和bj号罪犯之间存在仇恨,其怨气值为cj。
数据保证1<=aj<=bj<=N,0<=cj<=1000000000,且每对罪犯组合只出现一次。
【输出格式】
输出文件共1行,为Z市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0。
【输入样例】
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
【输出样例】
3512
【样例说明】
分配方法:市长看到的冲突事件影响力是3512(由2号和3号罪犯引发)。其他任何分法都不会比这个分法更优。
【数据范围】
对于30%的数据有N≤15。
对于70%的数据有N≤2000,M≤50000。
对于100%的数据有N≤20000,M≤100000。
分析:
方法一:二分+二分图染色
将罪犯看成是点,二分最大事件影响力,对于每对相互仇视的罪犯,若怨气值超过该影响力的话给两个罪犯连一条边,由于只有两个监狱,不在第一个,就在第二个,可以使用二分图判定是否成立。如果每对怨气超出该影响力的罪犯处于不同监狱,说明不矛盾,则继续查找左区间使影响力尽可能小。
方法二:贪心+并查集
如果某对罪犯(x,y)处在不同监狱,那么只能有两种情况,x在1号监狱y在2号监狱,x在2号监狱y在1号监狱,分别标为(x1,y2)和(x2,y1),将x1和y2合并,x2和y1合并,显然如果出现(x1,y1),(x2,y2)则是矛盾的,因为他们在同一监狱。那么我们可以对怨气值从大到小排序,然后从前往后对点进行合并,如果出现矛盾,说明至此已无法在让这一对罪犯处于不同监狱了,那么最大影响力的最小值就一定是这一对罪犯的怨气值。
代码1(二分+二分图染色):
program prison; var f:array[1..20000,0..1000]of longint; a,b,c:array[1..100000]of longint; d:array[1..20000]of longint; n,i,m,x,y,v,l,r,mid,g:longint; function dfs(x,y:longint):boolean; var i,t:longint; begin d[x]:=y; for i:=1 to f[x,0] do begin t:=f[x,i]; if d[t]=y then exit(false); if d[t]=0 then if dfs(t,-y)=false then exit(false); end; exit(true); end; begin readln(n,m); for i:=1 to m do begin readln(x,y,v); a[i]:=x; b[i]:=y; c[i]:=v; if v>r then r:=v; end; l:=0; while l<r do begin g:=0; mid:=(l+r) div 2; fillchar(f,sizeof(f),0); fillchar(d,sizeof(d),0); for i:=1 to m do if c[i]>mid then begin inc(f[a[i],0]);f[a[i],f[a[i],0]]:=b[i]; inc(f[b[i],0]);f[b[i],f[b[i],0]]:=a[i]; end; for i:=1 to n do if d[i]=0 then if dfs(i,1)=false then begin g:=1; break end; if g=1 then l:=mid+1 else r:=mid; end; writeln(r); end.
代码2(贪心+并查集):
program prison; var a,b,v:array[0..100000]of longint; f:array[0..200000]of longint; n,i,m,x1,x2,y1,y2,j:longint; function find(x:longint):longint; var i,j,k:longint; begin i:=x; j:=x; while i<>f[i] do i:=f[i]; while j<>i do begin k:=f[j];f[j]:=i;j:=k; end; exit(i); end; procedure qsort(l,h:longint); var i,j,t,m:longint; begin i:=l; j:=h; m:=v[(i+j) div 2]; repeat while v[i]>m do inc(i); while m>v[j] do dec(j); if i<=j then begin t:=v[i]; v[i]:=v[j]; v[j]:=t; t:=a[i]; a[i]:=a[j]; a[j]:=t; t:=b[i]; b[i]:=b[j]; b[j]:=t; inc(i); dec(j); end; until i>j; if i<h then qsort(i,h); if j>l then qsort(l,j); end; begin readln(n,m); for i:=1 to m do readln(a[i],b[i],v[i]); for i:=1 to n*2 do f[i]:=i; qsort(1,m); for i:=1 to m do begin x1:=find(a[i]); y1:=find(b[i]); x2:=find(a[i]+N); y2:=find(b[i]+N); if (x1=y1)or(x2=y2) then begin j:=i; break; end; if x2<>y1 then f[x2]:=y1; if x1<>y2 then f[y2]:=x1; end; if j>0 then writeln(v[j]) else writeln(0); end.
架设电话线(phoneline.???)
【问题描述】
FJ打算将电话线引到自己的农场,但电信公司并不打算为他提供免费服务。于是,FJ必须为此向电信公司支付一定的费用。
FJ的农场周围分布着N(1 <= N <= 1,000)根按1..N顺次编号的废弃的电话线杆,任意两根电话线杆间都没有电话线相连。一共P(1 <= P <= 10,000)对电话线杆间可以拉电话线,其余的那些由于隔得太远而无法被连接。第i对电话线杆的两个端点分别为A_i、B_i,它们间的距离为L_i (1 <= L_i <= 1,000,000)。数据中保证每对{A_i,B_i}最多只出现1次。编号为1的电话线杆已经接入了全国的电话网络,整个农场的电话线全都连到了编号为N的电话线杆上。也就是说,FJ的任务仅仅是找一条将1号和N号电话线杆连起来的路径,其余的电话线杆并不一定要连入电话网络。
经过谈判,电信公司最终同意免费为FJ连结K(0 <= K < N)对由FJ指定的电话线杆。对于此外的那些电话线,FJ需要为它们付的费用,等于其中最长的电话线的长度(每根电话线仅连结一对电话线杆)。如果需要连结的电话线杆不超过K对,那么FJ的总支出为0。
请你计算一下,FJ最少需要在电话线上花多少钱。
【输入格式】
第1行: 3个用空格隔开的整数:N,P,以及K
第2..P+1行: 第i+1行为3个用空格隔开的整数:A_i,B_i,L_i
【输入样例】
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
【输入说明】
一共有5根废弃的电话线杆。电话线杆1不能直接与电话线杆4、5相连。电话线杆5不能直接与电话线杆1、3相连。其余所有电话线杆间均可拉电话线。电信公司可以免费为FJ连结一对电话线杆。
【输出格式】
输出一行一个整数,为FJ在这项工程上的最小支出。如果任务不可能完成,输出-1。
【输出样例】
4
【输出说明】
FJ选择如下的连结方案:1->3;3->2;2->5,这3对电话线杆间需要的电话线的长度分别为4、3、9。FJ让电信公司提供那条长度为9的电话线,于是,他所需要购买的电话线的最大长度为4。
分析:
我们先二分最长电话线的长度L,如果有电话线长度超过这一值,说明如果它在所需修建的那条路径上,那么电信公司会为它付钱,因此我们将这些边的权值都设为1,其余的都设为0,然后求1到n的最短路径,于是我们得到了以L为最大长度时,修建从1到n电话线路径电信公司所要支付费用的电话线杆的最小数量,如果这一值比k要小,说明我们可以让电信公司为更多电话线杆支付费用,因而我们可以降低L长度,逼近最优解;反之如果超出k,就应让电信公司所要支付的数量减少,这就需要FJ多付一些钱(也就是让L更大),从而继续二分,如图在L=4时修改的图如下,红色的线构成了一条最短路径,可知最少电信公司需要支付1个电话线杆的费用,没有超过k,故符合。
未经修改原图
当最长电话线长L为4时修改后的图
代码:
program phoneline; var w:array[0..1000,0..5000]of longint; a,b:array[0..1000,0..1000]of longint; q:array[0..5000]of longint; dis:array[0..1000]of longint; g:array[0..1000]of boolean; n,i,m,j,k,h,t,p,x,y,l,r,v,mid,min:longint; begin assign(input,'phoneline.in'); reset(input); assign(output,'phoneline.out'); rewrite(output); readln(n,p,m); for i:=1 to p do begin readln(x,y,v); inc(w[x,0]); w[x,w[x,0]]:=y; inc(w[y,0]); w[y,w[y,0]]:=x; a[x,y]:=v; a[y,x]:=v; if v>r then r:=v; end; l:=0; b:=a; while l<r do begin mid:=(l+r) div 2; a:=b; fillchar(g,sizeof(g),false);fillchar(q,sizeof(q),0); for i:=1 to n do dis[i]:=maxlongint div 3; for i:=1 to n do for j:=1 to n do if a[i,j]>mid then a[i,j]:=1 else a[i,j]:=0; dis[1]:=0; q[1]:=1; h:=0; t:=1; g[1]:=true; repeat inc(h); x:=q[h]; g[x]:=false; for i:=1 to w[x,0] do begin y:=w[x,i]; if dis[y]>dis[x]+a[x,y] then begin dis[y]:=dis[x]+a[x,y]; if g[y]=false then begin t:=t+1; q[t]:=y; g[y]:=true; end; end; end; until h=t; if dis[n]>=maxlongint div 3 then begin writeln(-1); close(input); close(output); halt; end; if dis[n]<=m then r:=mid else l:=mid+1; end; writeln(r); close(input); close(output); end.
聪明的质检员(qc.cpp/c/pas)
【问题描述】
小T是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有n 个矿石,从1到n逐一编号,每个矿石都有自己的重量wi 以及价值vi。检验矿产的流程是:见图
若这批矿产的检验结果与所给标准值S 相差太多,就需要再去检验另一批矿产。小T不想费时间去检验另一批矿产,所以他想通过调整参数W 的值,让检验结果尽可能的靠近标准值S,即使得S-Y 的绝对值最小。请你帮忙求出这个最小值。
【输入描述】
第一行包含三个整数 n,m,S,分别表示矿石的个数、区间的个数和标准值。
接下来的 n 行,每行2 个整数,中间用空格隔开,第i+1 行表示i 号矿石的重量wi 和价值vi。
接下来的 m 行,表示区间,每行2 个整数,中间用空格隔开,第i+n+1 行表示区间[Li,Ri]的两个端点Li 和Ri。注意:不同区间可能重合或相互重叠。
【输出描述】
输出只有一行,包含一个整数,表示所求的最小值。
【样例输入】
5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3
【样例输出】
10
【数据范围及提示】
当 W 选4 的时候,三个区间上检验值分别为20、5、0,这批矿产的检验结果为25,此时与标准值S 相差最小为10。
【数据范围】
对于 10%的数据,有1≤n,m≤10;
对于 30%的数据,有1≤n,m≤500;
对于 50%的数据,有1≤n,m≤5,000;
对于 70%的数据,有1≤n,m≤10,000;
对于 100%的数据,有1≤n,m≤200,000,0 < wi, vi≤106,0 < S≤1012,1≤Li≤Ri≤n。
分析:
本题题意不太好懂,但明白后也就不难做了。
二分W值,利用前缀和求出Y值,与进行比对,如果Y<=S查找左区间,否则查找右区间。逐步让Y逼近S值,直到二者之差的绝对值最小。
代码:
program qc; var a,b,w,v,x,y,g:array[0..200001]of int64; n,m,l,r,mid,s,k,j:int64; i:longint; function kkk(mid:int64):int64; var i:longint; k:int64; begin k:=0; for i:=1 to n do begin a[i]:=a[i-1]+ord(w[i]>mid); b[i]:=b[i-1]+ord(w[i]>mid)*v[i]; end; for i:=1 to m do k:=k+(a[y[i]]-a[x[i]-1])*(b[y[i]]-b[x[i]-1]); kkk:=k; end; begin readln(n,m,s); for i:=1 to n do begin readln(w[i],v[i]);if w[i]>r then r:=w[i]; end; for i:=1 to m do readln(x[i],y[i]); l:=0; while l<=r do begin mid:=(l+r) div 2; k:=kkk(mid); if k<=s then r:=mid-1 else l:=mid+1; g[mid]:=k; if l>r then begin j:=kkk(r); k:=kkk(l); if abs(j-s)<abs(k-s) then writeln(abs(j-s)) else writeln(abs(s-k)); j:=abs(s-k); end; end; end.
猜数游戏(bales.???)
【问题描述】
为了提高自己低得可怜的智商,奶牛们设计了一个新的猜数游戏,来锻炼她们的逻辑推理能力。
游戏开始前,一头指定的奶牛会在牛棚后面摆N(1 <= N<= 1,000,000)堆干草,每堆有若干捆,并且没有哪两堆中的草一样多。所有草堆排成一条直线,从左到右依次按1..N编号,每堆中草的捆数在1..1,000,000,000之间。
然后,游戏开始。另一头参与游戏的奶牛会问那头摆干草的奶牛Q(1 <= Q <= 25,000)个问题,问题的格式如下:
编号为Ql..Qh(1 <= Ql <= Qh <= N)的草堆中,最小的那堆里有多少捆草?
对于每个问题,摆干草的奶牛回答一个数字A,但或许是不想让提问的奶牛那么容易地得到答案,又或许是她自己可能记错每堆中干草的捆数,总之,她的回答不保证是正确的。
请你帮助提问的奶牛判断一下,摆干草的奶牛的回答是否有自相矛盾之处。
【输入格式】
* 第1行: 2个用空格隔开的整数:N 和 Q
* 第2..Q+1行: 每行为3个用空格隔开的整数Ql、Qh、A,描述了一个问题以及它对应的回答。
【输出格式】
* 第1行: 如果摆干草的奶牛有可能完全正确地回答了这些问题(也就是说,能找到一种使得所有回答都合理的摆放干草的方法),输出0,否则输出1个1..Q中的数,表示这个问题的答案与它之前的那些回答有冲突之处。
【样例输入】
20 4
1 10 7
5 19 7
3 12 8
11 15 12
【样例输出】
3
【输入说明:】
编号为1..10的草堆中,最小的那堆里有7捆草,编号为5..19的草堆中同样如此;编号为3..12的草堆中最小的堆里是8捆草,11..15堆中的最小的堆里是12
捆。
【输出说明】
对于第3个问题“3 12”的回答“8”与前面两个回答冲突。因为每堆中草的捆数唯一,从前两个回答中我们能推断出,编号为5..10的干草堆中最小的那堆里有7捆干草。很显然,第3个问题的回答与这个推断冲突。
分析:
初看本题,似乎无从下手,不过不要紧,慢慢分析,就会找到正确的方法。
本题难在哪呢?显然我们可以通过二分最先出现矛盾回答的编号i,在此编号i回答前的每一个回答都应是正确而没有矛盾的,只能是该回答或后面的回答才有矛盾。如果1~i-1回答出现了矛盾,说明最先出现矛盾问题还在前面, 如果1~i回答只有i是矛盾的,说明i就是答案,否则答案还应在i的后面,继续查寻。那么问题来了,怎么判断是否有矛盾呢?本题难就难在如何判断矛盾。
这看上去很复杂,但我们可以思考一下矛盾是从何而来的,显然矛盾只能来自两个方面:
1. 相同的最小值出现的互不相交且不同的多个区间内。
2. 同一区间内有多个被认为正确的最小值。
这里强调一下由于我们二分的是最先出现矛盾的位置,在此之前的回答都被假设为正确的,所以才有以上两种情况。
对于第一种情况——
红,蓝不相交,但他们拥有相同最小值,根据题目中所有草垛值是不同说法判断这是矛盾的
对于第二种情况——
为了便于处理我们将不是第一种情况的所有有相同最小值的区间放在一起处理
这里又有了两种情况:
第二种情况A情形
由于两区间相交,因为草垛值都是不同的,则7只能处于两区间相交的地方
如果相交的地方被别的区间确定为另一个最小值所在的地方,说明矛盾了。
第二种情况B情形
由于一个区间包含另一个区间,由于草垛值是不同的,则7只能处于蓝色并集区间
如果被包含地方被别的区间确定为最小值所在的地方,说明矛盾了。
根据以上所述,我们得到这样一个方法,先二分最先出现矛盾的位置,后面的区间我们不看,只看这个区间以及前面的区间。我们将所有最小值相同的区间放在一起,判断是否为第一种情况,如果并不是,就求出它们的交集/并集,这样就确定了每个最小值对应的必然区间,再与其他区间进行比对,如果出现区间重合,且之间没有空点,说明矛盾,修改上下界,继续查找。
显然如果直接这样处理,很麻烦也容易超时,我们可以进行优化。
我们先将i之前区间按最小值从大到小排序,对于每一组最小值对应的必然区间,如果该区间已经被覆盖,说明矛盾,否则不矛盾,进行覆盖。
覆盖操作,判断是否被覆盖可以用并查集加速,将被覆盖的区间合并在集合内,将最左边的点作为集合的代表,这样当我们知道某个区间的右端点,可以利用并查集快速知道此区间是否被覆盖以及从该点向左最远覆盖到哪里,以此知道区间是否被完全的覆盖了,的到是否矛盾,具体如何判断,如何合并参见代码。
代码:
program bales; var x,y,val,c,q:array[0..26000]of longint; f:array[0..1002000]of longint; g:array[0..1002000]of boolean; n,i,m,l,r,mid,ans:longint; function max(x,y:longint):longint; begin if x>y then max:=x else max:=y; end; function min(x,y:longint):longint; begin if x<y then min:=x else min:=y; end; function find(x:longint):longint; var i,j,k:longint; begin i:=x; j:=x; while i<>f[i] do i:=f[i]; while j<>i do begin k:=f[j]; f[j]:=i; j:=k; end; exit(i); end; procedure qsort(l,h:longint); var i,j,t,m:longint; begin i:=l; j:=h; m:=val[(i+j) div 2]; repeat while val[i]>m do inc(i); while m>val[j] do dec(j); if i<=j then begin t:=val[i]; val[i]:=val[j]; val[j]:=t; t:=x[i]; x[i]:=x[j]; x[j]:=t; t:=y[i]; y[i]:=y[j]; y[j]:=t;t:=c[i]; c[i]:=c[j]; c[j]:=t; inc(i); dec(j); end; until i>j; if i<h then qsort(i,h); if j>l then qsort(l,j); end; function cheak(mid:longint):boolean; var i,j,k,l,r,s,t,r1,r2,ll,rr:longint; begin s:=0; fillchar(q,sizeof(q),0); for i:=0 to n+1 do begin f[i]:=i; g[i]:=false; end; for i:=1 to m do if c[i]<=mid then begin s:=s+1; q[s]:=i; end; i:=0; repeat i:=i+1; k:=0; for j:=i+1 to s do if val[q[j]]<>val[q[i]] then begin k:=j-1; break;end; l:=0; r:=n+1; ll:=n+1; rr:=0; if k=0 then k:=s; for j:=i to k do begin l:=max(l,x[q[j]]); r:=min(r,y[q[j]]); ll:=min(ll,x[q[j]]); rr:=max(rr,y[q[j]]); end; if l>r then exit(false); t:=find(r); if (g[t]=true)and(l>=t) then exit(false); r1:=find(rr);r2:=find(rr+1); if (g[r2]=true) then f[r2]:=r1; g[r1]:=true; r2:=r1; while ll<=r2 do begin r1:=find(r2-1); if (g[r1]=true)or(ll<=r1) then begin g[r1]:=true; f[r2]:=r1; end; r2:=r1; end; i:=k; until i>=s; exit(true); end; begin assign(input,'bales.in'); reset(input); assign(output,'bales.out'); rewrite(output); readln(n,m); for i:=1 to m do begin readln(x[i],y[i],val[i]); c[i]:=i; end; qsort(1,m); l:=1; r:=m; while l<=r do begin mid:=(l+r) div 2; if cheak(mid)=true then l:=mid+1 else begin ans:=mid; r:=mid-1; end; end; writeln(ans); close(input); close(output); end.
盛夏的果实(fruit.pas/c/cpp)
【试题描述】
丛林中共有 棵果树,每一棵果树上都有数量不等的果实。果树之间有单向边连接,而且从任何一棵果树开始不可能通过若干单向边走回自身。你提着一个篮子从编号为1的果树出发,选择一条路径走到编号为 的果树。每当你走到一棵果树的时候,你都会将这棵果树的所有果实采摘下来,放入篮子中,假设这个过程是不花费任何时间的。而当你在路上行走的时候,每走1分钟,你都会从篮子中拿出一个果实吃掉(如果篮子里还有果实的话)。
你的任务是求出你所携带的篮子至少要能够承担多少个果实的重量,才能够顺利地选择一条路径完成旅途,并且在途中不扔掉任何果实。(当到达第 棵果树时,还是要将这棵果树的全部果实放入篮子中)。
【输入文件】(fruit.in)
输入文件第一行为两个整数 ,分别表示果树的个数与单向边的条数。所有的果树从1到 编号。
接下来一行, 个用空格隔开的整数,分别表示编号1~ 的果树上果实的个数。
接下来 行,每行三个用空格隔开的整数 ,表示从 到 有一条单向边相连,这条边通过所需的时间为 (分钟)。
【输出文件】(fruit.out)
输出文件有且仅有一行,一个整数,表示篮子最少需要承担多少个果实的重量。
【限制条件】
两颗不同树之间可能有多条边直接相连,但是没有一条边连接两颗相同的树。
一定存在至少一条从树1到达树 的路径。
每棵树上果实的数量,通过一条边所需的时间都是1~10000之间的整数。
对于30%的数据, 。
对于60%的数据, 。
【样例输入】
4 4
5 7 6 4
1 2 3
1 3 3
2 4 100
3 4 1
【样例输出】
9
分析:
本题是一个带限制的最短路,先二分能承担果实的数量,将树作为点,之间的路径为边,边权为走这段路所要吃掉的果子数目,用SPFA求最短路,在松弛操作时,要注意在路上吃果子,到了一棵树会摘下所有果子,也就是说每条边实际长度为到一点时篮子中所剩果子数-路上吃掉的果子数+在另一点获得的果子树,还要注意的是当果子吃完了就不能再吃了,也就是如果篮子中所剩果子数-路上吃掉的果子数<0的话就将该值赋值为0,如果在某一点篮子的果实数超过最大承受值,就不能对该边松弛。这样求最短路,求出到第n棵树时篮子里果实的量,如果超过了最大承受值或不存在最短路,说明该答案不成立,于是在右区间内查找答案,否则在左区间查找所要的答案。
代码:
program fruit; var a:array[0..1000,0..5000]of longint; w:array[0..1000,0..1000]of longint; dis,v:array[0..1000]of longint; q:array[0..3003]of longint; g:array[0..1000]of boolean; n,i,m,x,y,z,l,r,u,mid,h,t:longint; function max(x,y:longint):longint; begin if x>y then max:=x else max:=y; end; begin assign(input,'fruit.in'); reset(input); assign(output,'fruit.out'); rewrite(output); readln(n,m); for i:=1 to n do read(v[i]); for i:=1 to m do begin readln(x,y,z); a[x,0]:=a[x,0]+1; a[x,a[x,0]]:=y; w[x,y]:=z; r:=r+z; end; l:=0; while l<r do begin mid:=(l+r) div 2; fillchar(q,sizeof(q),0); fillchar(g,sizeof(g),false); for i:=1 to n do dis[i]:=maxlongint div 3; dis[1]:=v[1]; q[1]:=1; h:=0; t:=1; g[1]:=true; repeat inc(h); h:=((h-1) mod 3000)+1;x:=q[h]; g[x]:=false; for i:=1 to a[x,0] do begin y:=a[x,i]; u:=max(dis[x]-w[x,y],0)+v[y]; if (u<dis[y])and(u<=mid) then begin dis[y]:=u; if g[y]=false then begin t:=t+1; t:=((t-1) mod 3000)+1; q[t]:=y;g[y]:=true; end; end; end; until h=t; if dis[n]>=maxlongint div 3 then l:=mid+1 else r:=mid; end; writeln(r); close(input); close(output); end.
圣诞节的花环(flow.pas/c/cpp)
【题目描述】
圣诞节就要到了。现在有个困难的任务摆在你眼前——装饰房间。
你手头上有n朵大小相同的花,第i朵花的重量为wi。现在打算用一根绳将这n朵花按顺序穿起来,挂在天花板上。绳子被m个点固定,也就是绳子的一头被固定在1号点,另外一头固定在m号点,中间部分需要固定在剩余的点。当然,装饰还有一些规则要注意:
(i) 每一段需要包含非0的偶数个花朵。正因如此,我们可以将每一段划分为两个半段。
(ii) 为了减小你的客人撞到花环的可能,花环不能挂的太低:也就是说,每个半段不能超过d朵花。
(iii) 最后,你需要让所有半段的重量的最大值最小。
【输入格式】
第一行包含三个正整数n,m和d(1 ≤ n ≤ 15000,2 ≤ m ≤ 10000,1 ≤ d ≤ 2000,且n*d ≤ 5000000)。
接下来一行包含n个正整数w1,w2,…,wn(1 ≤ wi ≤ 10000),代表对应花的重量。
【输出格式】
输出一行一个整数,代表最小的所有半段重量的最大值。如果没有方案满足条件,那么你只要输出“BAD”(不包括引号)。
【样例输入1】
4 3 10
10 10 20 20
【样例输出1】
20
【样例输入2】
6 4 10
1 1 100 100 1 1
【样例输出2】
100
【样例输入3】
6 3 10
1 1 100 100 1 1
【样例输出3】
200
【样例输入4】
1 2 2
333
【样例输出4】
BAD
分析:
首先二分答案。确定每半段重量的最大值。用动态规划来检验。开始时我们会用一个数组f,f[n]表示前n个最少需要多少段,如果段数<=m-1就是可行的。但这样是过不了全部数据的。因为事实上的单调性是这样:如果能够构成k段,那么可以构成k+2段。正确的的方法是用f[n][0 or 1]表示前n个分成了偶数段或者奇数段的最小段数,得到状态转移方程f[i,k]:=min(f[i,k],f[i-j*2,1-k]);这之中j表示半段花的个数。最后检验f[n][(m-1)&1]是否<=m-1 。
代码:
program flow; var f:array[0..15000,0..1]of longint; w,a:array[0..15000]of longint; n,i,m,j,l,r,d,mid,k,g:longint; function min(x,y:longint):longint; begin if x<y then min:=x else min:=y; end; function max(x,y:longint):longint; begin if x>y then max:=x else max:=y; end; begin assign(input,'flow.in'); reset(input); assign(output,'flow.out'); rewrite(output); readln(n,m,d); for i:=1 to n do begin read(w[i]); if w[i]>l then l:=w[i]; a[i]:=a[i-1]+w[i]; end; r:=a[n]; while l<r do begin mid:=(l+r) div 2; fillchar(f,sizeof(f),$7f); f[0,0]:=0; for i:=1 to n do for j:=1 to min(d,i div 2) do begin if i-j*2>=0 then for k:=0 to 1 do begin if max(a[i]-a[i-j],a[i-j]-a[i-j*2])<=mid then f[i,k]:=min(f[i,k],f[i-2*j,1-k]+1) else break; end else break; end; if f[n,(m-1) and 1]<=m-1 then r:=mid else l:=mid+1; end; if r=a[n] then writeln('BAD') else writeln(r); close(input); close(output); end.
yk赚钱记(money.pas/c/cpp)
【题目描述】
又有老师让 yk 做事情了,不过这次的任务虽然有时体力活,但是 yk 做的心甘情愿,为什么呢?因为有 money可以拿啦~啦啦啦,yk 好开心啊好开心~~
这次的任务是让yk 去铺水管,学校一共有 N个中转点,有 M条可供选择的水管道路,一条水管道路连接两个中转点,最后 yk铺完的水管必须保证任意两个中转点之间都可以互相送水(直接相连或间接相连都可以) ,并且所铺的水管数尽量少。铺每条水管道路有不同的报酬,也需要耗不同大小的体力,由于 yk 喜欢money但是不喜欢动,所以他希望他平均每单位的体力所赚到的钱最多。
【输入格式】
第一行两个整数N,M,表示有N 个中转点和 M 条可供选择的水管道路。
下面 M 行,每行四个整数,第 i+1 行的 u,v,w,c,表示点 u 到点 v 间铺水
管可得报酬w元,需要耗费 c单位的体力
【输出格式】
一行一个实数,表示平均每单位体力最多赚多少钱。保留 4位小数。
【样例】
money.in
5 5
1 2 20 5
1 3 20 5
1 4 20 5
1 5 20 5
2 3 23 1
money.out
5.1875
【数据规模】
100% N<=400, M<=10000
分析:
本题是求最优比率生成树,也就是使r=(∑xi×vi)/(∑xi×ui)最小,这之中xi表示第i个点是否在生成树中,在的话就是1,不在为0。二分比率,将各边边权值修改为v[i]-mid*u[i],然后求最大生成树权值之和,若大于等于0,说明符合条件且比率偏小,查找右区间,否则不符合,查找左区间,直到r,l之差满足精度范围为止,这样可以使权值和逼近0,使得比率最大。
代码:
program money; var x,y,u,v:array[0..10000]of longint; a:array[0..10000]of real; f:array[0..400]of longint; n,i,m,j:longint; l,r,t2,mid:real; function find(x:longint):longint; var i,j,k:longint; begin i:=x; j:=x; while i<>f[i] do i:=f[i]; while i<>j do begin k:=f[j]; f[j]:=i; j:=k; end; end; procedure qsort(l,h:longint); var i,j,t:longint; m,tmp:real; begin i:=l; j:=h; m:=a[(i+j) div 2]; repeat while a[i]<m do inc(i); while m<a[j] do dec(j); if i<=j then begin tmp:=a[i]; a[i]:=a[j]; a[j]:=tmp; t:=x[i]; x[i]:=x[j]; x[j]:=t; t:=y[i]; y[i]:=y[j]; y[j]:=t; t:=u[i]; u[i]:=u[j]; u[j]:=t; t:=v[i]; v[i]:=v[j]; v[j]:=t; inc(i); dec(j); end; until i>j; if i<h then qsort(i,h); if j>l then qsort(l,j); end; function work(mid:real):real; var i,j,k,c:longint;s:real; begin for i:=1 to m do a[i]:=v[i]-mid*u[i]; qsort(1,m);s:=0; c:=0; for i:=1 to n do f[i]:=i; for i:=m downto 1 do begin j:=find(x[i]); k:=find(y[i]); if j<>k then begin f[j]:=k; s:=s+a[i]; c:=c+1; end; if c=n-1 then break; end; exit(s); end; begin assign(input,'money.in'); reset(input); assign(output,'money.out'); rewrite(output); readln(n,m); fillchar(v,sizeof(v),0); fillchar(u,sizeof(u),0); for i:=1 to m do begin readln(x[i],y[i],v[i],u[i]); end; l:=0; r:=1000; while r-l>=0.0000001 do begin mid:=(l+r)/2; if work(mid)>=0 then l:=mid else r:=mid; end; writeln(l:0:4); close(input); close(output); end.