二分答案.docx
《二分答案.docx》由会员分享,可在线阅读,更多相关《二分答案.docx(23页珍藏版)》请在冰豆网上搜索。
二分答案
二分答案
有人看到“二分答案”这个题目,可能会很不解。
题目过程可以二分,答案怎么也能二分呢?
事实上,当你看到找极大值中的最小值或者求极小值中的最大值的题目时,二分答案或许是一个不错的选择。
根据数据范围判断是否选用二分(二分能把时间复杂度降到logn),再和上文所说的条件结合,一般就不会错了。
先看一个例题。
【描述】
现在某组织中(记作R)有n个人,他们的联络网形成一棵以Saltless为根的树,有边相连代表两人可以直接联络。
每个人有一个代号,Saltless代号为1,且除Saltless外每个人的父节点的代号小于他自己的代号。
由于某些原因,Saltless给R的成员分别下达紧急任务,R需要分成m组行动,每个组必须满足如下条件:
1、每个组员仅分在本组中
2、至少有一个组员
3、任意两个组员无需通过本组外的人就可以联络(但可以通过本组组员进行联络)
每个人有一个能力指数,一个组的能力指数是全组人能力指数之和。
对于任意一种正确的分组,平均度就是m组中最小能力指数。
为了分组较为平均,Saltless希望平均度尽可能大。
【格式】
PROGRAMNAME:
organ
INPUTFORMAT:
第一行为三个数:
n,m,和Saltless的能力指数(1<=m<=n<=10000)。
接下来n-1行,每行两个数:
此人的父节点代号和他的能力指数(能力指数值为正整数,不超过30,行数就是他本身的代号)
OUTPUTFORMAT:
输出格式:
一个数,表示最大的平均度。
SAMPLEINPUT(organ.in)
722
14
15
21
22
34
43
SAMPLEOUTPUT(organ.out)
10
【Hint】
分组:
{1,3,6},{2,4,5,7}
看到这个题,出口很明显,可能首先想到DFS。
但是10000的数据DFS显然是不能承受的。
这时候我们采用二分答案。
我们先记录下所有人能力指数之和tot,然后假设它的中间值mid就是我们要找的最优解。
然后根据这个下限进行分组,把得到的组数num和m进行比较,如果num>m则表示分组过多,则答案在mid+1到tot的区间内;如果num当左右指针重合时,最优解也就找到了。
参考代码:
1programsai;
2var
3m,n,tot:
longint;
4mid,num,l,r:
longint;
5i:
integer;
6p,w,t:
array[0..10000]oflongint;
7functionget(x:
longint):
longint;//分组
8var
9i,num:
integer;
10begin
11num:
=0;
12t:
=w;
13fori:
=ndownto1do//从儿子找起,以消除后效性
14ift[i]=t[p[i]]+t[i]
15elseinc(num);//如果一个组的能力指数之和达到了中间值就加一组
16exit(num);
17end;
18begin
19assign(input,'sai.in');
20reset(input);
21assign(output,'sai.out');
22rewrite(output);
23readln(n,m,tot);
24w[1]:
=tot;
25fori:
=2tondo
26begin
27readln(p[i],w[i]);
28tot:
=tot+w[i];
29end;
30l:
=1;
31r:
=tot;
32whilel<=rdo
33begin
34mid:
=(l+r)>>1;//mid记录了临时的最优解
35num:
=get(mid);
36ifnum>=mthenl:
=mid+1elser:
=mid-1;//与组数进行比较,确定下一个搜索区间
37end;
38writeln(r);//右指针记录的是最优解
39close(input);
40close(output);
41end.
浅析二分答案
二分答案,顾名思义,大家应该想到了二分查找和二分法对题目的优化,我这里要说的二分答案也是如此。
使用范围:
二分答案必须在满足单调性的情况下才可以使用。
例如下面的一道例题:
一个美丽的夜晚,Bingo和他的MM坐在高高的N骨N堆旁看着天上稀稀拉拉的星星。
Bingo对着MM说:
“我们就像指数函数。
你是底数,我是指数。
不管你是大于一还是大于零小于一,我都可一让我们的幸福指数(不同于上面的指数)到正无穷。
”MM说“你是底数,我是指数,我要顺着你改变”。
Bingo一阵感动,但是,作为一个男人,Bingo怎么能委屈自己的MM呢。
Bingo说:
“不行,我会心疼你的。
”MM说:
“我也会心疼你啊”。
…………
【题目描述】
为了不让Bingo和他的MM谁更心疼,也就是让他们一样心疼对方,给你一个数n,请你找出最小的整数x使得xx的位数大于等于n。
【输入】
一个数,n。
【输出】
一个数x,满足xx的位数大于等于n,x-1x-1的位数小于n。
【样例】
heart.in
11
heart.out
10
【数据规模】
n<=1,000,000,000,000,000,000。
【时间,内存限制】
各个测试点1s,64M。
题目解析:
这道题目是典型的二分答案,因为随着mid的增大,mid^mid的位数会随之增大而增大,因此满足单调性,可以使用二分答案来求解;【代码如下】:
programHeart;
varl,r,mid:
qword;
n:
qword;
begin
readln(n);
l:
=0;
r:
=1shl59;
whilel<>rdo{注意这里,一个奇怪的跳出,这种跳出的优点在于不会为输出l还是r来就接}
begin
mid:
=(l+r)shr1;
iftrunc(mid*ln(mid)/ln(10))+1{换底公式求出它的位数,注意要加1}>=nthenr:
=midelsel:
=mid+1;
end;
writeln(l);
end.
是不是很短,其实二分并不复杂,其实很EASY。
本题的另外一个难点在于怎么由已知的一个十进制的数来求位数。
其实就是高一数学,用上指数函数的换底公式就可以了。
另外需要注意的一点便是二分边界的维护,这个要根据实际情况而定是要打成r:
=mid-1还是r:
=mid;
许多问题不易直接求解,但如果我们却可以判断某个解是否可行.构造函数ok(x),布尔型,若最终答案为ans,当x=ans时,ok(x)=true(反之亦可),则满足了二分的条件.假设有一个高效的求ok(x)的算法,则可以通过二分x来获得ans。
请看下面的题目:
奇怪的函数(xx.pas/c/cpp)
问题描述
使得x^x达到或超过n位数字的最小正整数x是多少?
输入数据
输入一个正整数n。
输出数据
输出使得x^x达到n位数字的最小正整数x。
输入样例
11
输出样例
10
时间限制
各测试点1秒
数据规模
n<=2000000000
分析:
直接求解不容易.但对于给定的x我们容易知道x^x的位数.且对于递增的x,位数不降.于是可以二分.
下面给出位数的求法:
令Xx=10p,只要求出p即可。
两边取对数:
Xlnx=pln10
所以p=xlnx/ln10,位数即[xlnx/ln10]+1
代码:
constoo=2000000000;
var
l,r,m,n:
int64;
functioncheck:
boolean;
begin
m:
=(l+r)shr1;
if(m*ln(m)/ln(10))>=n-1thenexit(true)
elseexit(false);
end;
begin
assign(input,'xx.in');reset(input);
readln(n);
close(input);
l:
=1;r:
=oo;
whilel<>rdo
ifcheckthenr:
=m
elsel:
=m+1;
assign(output,'xx.out');rewrite(output);
writeln(l);
close(output);
end.
电话网络(phone.pas/c/pp)
[题目描述]
由于地震使得连接汶川县城电话线全部损坏,假如你是负责将电话线接到震中汶川县城的负责人,汶川县城周围分布着N(1≤N≤1,000)根按1..N顺次编号的废弃的电话线杆,任意两根电话线杆间都没有电话线相连。
一共P(1≤P≤10,000)对电话线杆间可以拉电话线,其余的由于地震使得无法被连接。
第i对电话线杆的两个端点分别为Ai,Bi,它们间的距离为Li(1≤Li≤1,000,000)。
数据中保证每对(Ai,Bi)最多只出现1次。
编号为1的电话线杆已经接人了全国的电话网络,整个县城的电话线全都连到了编号为N的电话线杆上。
也就是说,你的任务仅仅是找一条将1号和N号电话线杆连起来的路径,其余的电话线杆并不一定要连人电话网络。
电信公司决定支援灾区免费为汶川县城连结K(0≤K对于此外的那些电话线,需要为它们付费,总费用等于其中最长的电话线的长度(每根电话线仅连接一对电话线杆)。
如果需要连接的电话线杆不超过K对,那么总支出为0。
请你计算一下,将电话线引到震中汶川县城最少需要在电话线上花多少钱?
[输入格式]
输入文件的第一行包含三个用空格隔开的整数:
N,P和K。
第二行到第P+1行:
每行分别都为空格隔开的整数:
Ai,Bi和Li。
[输出格式]
输出文件中仅包含一个整数,表示在这项工程上的最小支出。
如果任务不可能完成,则输出-1。
[输入样例]
571
125
314
248
323
529
347
456
[输出样例]
4
分析:
这是一道典型的二分答案题,先确定一个长度下限m,大于m的将为免费的。
这样对给出的图中,若某条边小于m,则赋值为0,否则赋值为1。
求1到N的最短路即可。
如果最短路<=电信公司提供的数目,说明m可行,接下来二分枚举比m小的值,反之二分枚举比m大的值。
代码:
const
maxn =1000;
maxm =10000;
oo =100000000;
var
g,gg :
array[1..maxn,1..maxn]oflongint;
d :
array[1..maxm]oflongint;
vis :
array[1..maxn]ofboolean;
p :
array[1..maxn]oflongint;
n,m,k,u,v,l,r,mid,i,j:
longint;
functionok(x:
longint):
boolean;
var
i,j,l,r,u,v :
longint;
begin
fori:
=1tondo
forj:
=1tondo
ifgg[i,j]ifgg[i,j]<=xtheng[i,j]:
=0elseg[i,j]:
=1;
l:
=1;r:
=1;d[1]:
=1;
fori:
=2tondop[i]:
=oo;
fori:
=2tondovis[i]:
=false;vis[1]:
=true;
whilel<=rdo
begin
u:
=d[l];
forv:
=1tondo
ifp[u]+g[u,v]
begin
p[v]:
=p[u]+g[u,v];
ifnotvis[v]then
begin
inc(r);
d[r]:
=v;
vis[v]:
=true;
end;
end;
vis[u]:
=false;
inc(l);
end;
ifp[n]<=kthenexit(true)elseexit(false)
end;
begin
assign(input,'phone.in');
reset(input);
assign(output,'phone.out');
rewrite(output);
readln(n,m,k);
fori:
=1tondoforj:
=1tondogg[i,j]:
=oo;
fori:
=1tomdo
begin
readln(u,v,gg[u,v]);
gg[v,u]:
=gg[u,v];
ifgg[u,v]>rthenr:
=gg[u,v]
end;
l:
=0;
g:
=gg;
whilelbegin
mid:
=(l+r)shr1;
ifok(mid)then
r:
=midelsel:
=mid+1
end;
ifok(l)thenwriteln(l)elsewriteln(-1);
close(output)
end.
集训第七次考试第四题二分答案
注:
二分应该时目前我用的最少的一种思想,已经两次遇到了。
什么样的题目能用二分做呢?
常见的有在一堆极大值中找最小值或在一堆极小值中找最大值,数据量大于10000为二分答案的提示,二分可以减小答案范围,变成log级别的。
二分的关键就是当l与r相等的时候是最大值,如果当前解比mid大,就在mid和r中找,反之在l和mid中找。
l和r为限定的最大值和最小值。
例题:
【描述】
现在根中有n个人,他们的联络网型成一棵以团藏为根的树,有边相连代表两人可以直接联络。
每个人有一个代号,团藏代号为1,且除团藏外每个人的父节点的代号小于他自己的代号。
晓的活动暂时放缓,团藏要当火影的野心一天比一天大,所以他给根的成员分别下达紧急任务,根需要分成m组行动,每个组必须满足如下条件:
1、每个组员仅分在本组中
2、至少有一个组员
3、任意两个组员无需通过本组外的人就可以联络(但可以通过本组组员进行联络)
每个人有一个能力指数(就是贤、力、精、幻、忍、体的综合指数),一个组的能力指数是全组人能力指数之和。
对于任意一种正确的分组,平均度就是m组中最小能力指数。
为了分组较为平均,团藏希望平均度尽可能大。
可怜的佐井迫于团藏的威胁,被要求算出这个最大值,不管怎么说,佐井是个好孩子~而且他只会拿笔画画,不会算这种问题~你就帮帮他好了~~
【格式】
PROGRAMNAME:
sai
INPUTFORMAT:
第一行为三个数:
n,m,和团藏的能力指数(1<=m<=n<=10000)。
接下来n-1行,每行两个数:
此人的父节点代号和他的能力指数(活动程度值为正整数,不超过30,行数就是他本身的代号)
OUTPUTFORMAT:
输出格式:
一个数,表示最大的平均度。
SAMPLEINPUT(filesai.in)
722
14
15
21
22
34
43
SAMPLEOUTPUT(filesai.out)
10
【Hint】
分组:
{1,3,6},{2,4,5,7}
分析:
这个题目描述告诉我们,我们的目标是在保证为m组的情况下,在各组都极大时找一个最小值,而数据量为10000,所以能用二分做。
二分时以每组最小价值为二分依据,设定一个最大价值:
总价值divm+1,一个最小价值1;二分mid:
=(l+r)shr1;判断当价值为mid时能达到的组数,如果小于要求组数,就证明价值太大了,令r等于mid反之令l等于mid,一直到l与r相等就找到了最优解。
代码:
var
n,m,l,r,i,mid,zhi:
longint;
s,f,sc:
array[1..10000]oflongint;
functioncal(x:
longint):
longint;{计算以x为答案时组数}
var
i,now,ans:
longint;
begin
sc:
=s;
ans:
=0;
fori:
=ndownto1do{依然是倒序}
ifsc[i]>=xthen{只要当前父节点已经能满足值为x,就算一组}
begin
inc(ans);{ans为组数}
now:
=f[i];{递归减去已经抛开的一枝}
whilenow<>0do
begin
sc[now]:
=sc[now]-sc[i];
now:
=f[now];
end;
end;
exit(ans);
end;
begin
readln(n,m,zhi);
s[1]:
=zhi;{s数组记录当前父节点的所有儿子的价值与其之和}
fori:
=2tondo
begin
readln(f[i],zhi);
s[i]:
=zhi;
end;
fori:
=ndownto2do{计算所有父节点的总值,倒序消除后效型}
inc(s[f[i]],s[i]);
ifm=1then
begin
writeln(s[1]);
exit;
end;{特殊情况}
l:
=1;
r:
=s[1]divm+1;{先找一个可能的最大值和最小值,然后逐步
缩小答案范围,这是二分答案的关键}
whiler-l>1do{一直找到r与l相等就证明已经找到了最优解}
begin
mid:
=(l+r)div2;{二分,效率为log级别}
ifcal(mid)=midelsel:
=mid;{cal计算以mid为答案时的组数
如果组数小,就证明值太大,
反之就是值太小,变换l,r}
end;
writeln(l);
end.
pku3657:
很有意思的题,大意是给你若干句形如a,b,c的描述,表示a~b的最小值为c,要你求第一句出现矛盾的话。
乍一看没什么好想法,模拟吧,不可能,线段树吧,判不出,怎么办?
回过头想想,为什么麻烦了,麻烦在了哪里,依我看,就是麻烦在了"求第一句出现矛盾的话"上,"第一句"会卡死了我们的思路,怎么解决呢?
二分答案!
二分的性质很显然,解会是连续的,而且二分答案之后就可以排序了,此为此题收获之一,二分答案以获得更多信息和更宽松的条件
好了,排完序之后容易想到按c从大到小排序,有如下两种情况可能产生矛盾:
如果一段区间的最小值已知为c1了,那么此为此题收获之二,细致的读题是ac的必要条件。
。
第二种情况我自己就没有想到。
。
这个染色的过程用什么东西来实现?
线段树?
麻烦。
模拟?
。
。
不予评价。
这都不是最佳选择。
由于每个点只能被染色一次,那么并查集就能够胜任这个过程了。
具体方法就是每个点的f[i]记录当前它被一整段区间所覆盖的最左边那个点,这样,每次给区间染色就可以用并查集做到均摊O(alpha(n))的了。
并查集的妙用,此为此题收获之三
看这个神奇的数据
103
1101
115
10104
输出3的同学要注意了,普通的离散化不能区分[a,a]U[b,b]和[a,b],那么我们离散化的时候往[a,b](a+1
离散化时的细节(好像离散化经常要插点啊什么的),此为此题收获之四
代码:
viewplaincopytoclipboardprint?
programsyj;{erfenans+rangecovering_usingbingchaset}
const
maxn=25000+5;
varn,q,nn,l,r,mid,i:
longint;
x,c,p,f,color:
array[0..maxn*4]oflongint;
a:
array[1..maxn]oflongint;
proceduresort1(l,r:
longint);
vari,j,k,z:
longint;
begin
i:
=l;j:
=r;k:
=x[(l+r)div2];
repeat
whilex[i]whilex[j]>kdodec(j);
ifi<=jthenbegin
z:
=x[i];x[i]:
=x[j];x[j]:
=z;
z:
=c[i];c[i]:
=c[j];c[j]:
=z;
inc(i);dec(j);
end;
untili>j;