二分:一个非常神奇的算法:永远记住二分,分的是答案,直接在答案在的区间范围中二分,分出一个值,就判断是不是 答案,并进行转移
二分答案:
如果已知候选答案的范围[min,max],有时候我们不必通过计算得到答案,只需在此范围内应用“二分”的过程,逐渐靠近答案(最后,得到答案)!
一、何时可以使用“二分答案”
不是任何题目都适合使用“二分答案”的,我Sam观察到一般有以下的一些特征:
A. 候选答案必须是离散的 ,且已知答案的范围是:[最小值min, 最大值max] (连续区间上不能进行二分操作)
例如,在题目“Kth Largest 第k大的数”中 ==> 答案是闭区间[a[1]b[1], a[n]b[n]]上的正整数
例如,在题目“Drying 烘干衣服”中 ==> 烘干时间t∈[0,maxT], maxT=max{a[i]}
B. 候选答案在区间[min,max]上某种属性一类一类的排列 (这样就能在此属性上进行二分操作 ),各个类别不交错混杂
例如,在题目“Kth Largest 第k大的数”中 ==>
(候选答案)第k大的数的值: a[1]b[1], ... , a[n]b[n]
(属性分类)>这个乘积的数有多少: n^2-1 ... 0
例如,在题目“Drying 烘干衣服”中 ==>
(候选答案)烘干时间: t=0, t=1, t=2, t=3, ..., t=maxT-1, t=maxT
(属性分类)能否烘干: 不能 不能 不能 能 ... 能 能
C. 容易判断某个点 是否为答案(即二分过程中,mid指向的点是否为答案)
例如,在题目“Kth Largest 第k大的数”中 ==> λ∈[ a[1]b[1], a[n]b[n] ]
对于某个候选答案,如果“>λ的乘积个数"
例如,在题目“Drying 烘干衣服”中 ==>
需要寻找第一个出现的“能”(true),即如果check(mid-1)==false && check(mid)==true ,则答案为mid.
1.codevs 1696 奇怪的函数
自从得到上次的教训后,John的上课态度认真多了,也变得更爱动脑筋了。今天他又学习了一个新的知识:关于 xk 的位数。
如果x大于0小于l,那么位数=1+小数部分×k,
如果x≥l,那么位数=trunc(ln(x)/ln(10)×k)+1+小数部分×k。
根据这些函数知识,他学会了求xk的位数了。但他又想到了另外一个问题,如果已知位数N,能不能求出使得 xk 达到或超过N位数字的最小正整数x是多少?
输入一个正整数n(n≤2000000000)。
输出使得 xk 达到n位数字的最小正整数x。
11
10
1 #include2 using namespace std; 3 #include 4 #include 5 #include 6 #define inf 2000000000 7 #define ll long long 8 inline ll f(ll x) 9 { 10 return x*(log(x)/log(10))+1;/*求位数的公式,n进制,n就做log的底数,别忘了加1*/ 11 } 12 int main() 13 { 14 ll l=1,r=inf; 15 ll n; 16 cin>>n; 17 while(1) 18 { 19 ll mid=(l+r)>>1; 20 if(f(mid)>=n) r=mid;/*确定好二分的边界*/ 21 else l=mid+1; 22 if(l==r) 23 { 24 cout< endl; 25 break; 26 } 27 } 28 return 0; 29 }
2.ccsu 1487 数列归并
Description
给定长度为N的序列A,其中1≤N≤100000,1≤A[i] ≤100000。现在要将A分成M段(1≤M≤N),每段有A中的1个或相邻的多个元素构成。例如A={1,3,4,6,7,8}分成3段的一种情况为B={1,(3,4),(6,7,8)}。
由于将A分成M段的情况有多种,现在要求最大子段和最小的情况。例如上述中B的子段和分别为{1,7,21}.
Input
每组输入的第一行为N和M,然后N行是序列A,为N个正整数。
Output
输出一个整数,占一行。求最小的情况下的最大子段和。
Sample Input
7 5
100
400
300
100
500
101
400
Sample Output
500
Hint
Sample中的分法为:100+400, 300+100, 500, 101, 400. 其中最大子段和为500,且此种情况为多种情况下最小的。
网上的答案:
1 #include2 #include 3 #define size 100010 4 #include 5 using namespace std; 6 int n,m; 7 int a[size]; 8 int ok(int x) 9 { 10 int sum = 0 , t = 0 , i = 0 , flag = 0 ,len; 11 while (i < n) 12 { 13 sum = a[i++]; 14 int j=i; 15 while (j < n && sum < x ) 16 { 17 sum+=a[j++]; 18 } 19 t++; 20 if(sum == x) {flag =1; len = j - i + 1; }/*恰好可以取到最大子段和*/ 21 else if( sum > x ) {j --; sum-=a[j];} /*如果加多了*/ 22 if( t > m ) return -1;/*当分组数超过m的时候,返回-1*/ 23 i=j; 24 } 25 if(flag == 1 && n - len >= m -1 ) return 1; 26 if(flag == 1 && t <= m) return 1;/*分组数不够,因为我们求的是最大子段和,如果可以求到相应的值,其余的部分够分为m-1份就可以了*/ 27 return 0; 28 } 29 int find_ans(int low , int heigh) 30 { 31 int ans = 0 , mid; 32 while ( low <= heigh) 33 { 34 mid = (low + heigh)/2; 35 int temp = ok(mid); 36 if(temp == -1){ low = mid +1 ;}/*分组分多了,说明这个最大值取小了*/ 37 else if(temp ) {heigh = mid - 1; ans = mid;}/*分组数刚好够或者不够,因为我们要求的是最大子段和的最小值,所以当恰好分为m份的时候,应该再把区间向左取小才可以*/ 38 else heigh = mid -1 ;/*而且只有当可以取到恰好的子段和时候,才可以更新ans,else 只能判断区间*/ 39 } 40 return ans; 41 } 42 int main() 43 { 44 int min,max; 45 while (scanf("%d%d",&n,&m)!=EOF) 46 { 47 min = max = 0; 48 for(int i=0;i ) 49 { 50 scanf("%d",&a[i]); 51 if(a[i] > min) min = a[i];/*确定查询区间*/ 52 max+=a[i]; 53 } 54 55 printf("%d\n",find_ans(min,max)); 56 } 57 return 0; 58 }
我的:
1 #include2 using namespace std; 3 #include 4 #define N 100000+10 5 int n,m; 6 int a[N]; 7 int read() 8 { 9 int ans=0; 10 char s; 11 s=getchar(); 12 while(s<'0'||s>'9') s=getchar(); 13 while(s>='0'&&s<='9') 14 {ans=ans*10+s-'0';s=getchar();} 15 return ans; 16 } 17 int check(int x) 18 { 19 int sum=0,t=0,i=1,len; 20 bool flag=false; 21 while(i<=n) 22 { 23 sum=a[i]; 24 i++; 25 int j=i; 26 while(j<=n&&sum<x) 27 { 28 sum+=a[j];/*分组,一超过最大字段和,就分为一组,并检验是>还是==,因为只有我们把分组尽量的平均才可能实现最大子段和最小*/ 29 j++; 30 } 31 t++; 32 if(sum==x){flag=true;len=j-i+1;} 33 else if(sum>x) {j--;sum-=a[j];} 34 if(t>m) return -1; 35 i=j; /*i表示下一个开始加的位置*/ 36 } 37 if(flag&&n-len+1>=m) return 1;/*可以取到mid,就回去更新,即使分组不够*/ 38 if(flag&&t<=m) return 1; 39 return 0;/*取不到,仅仅取小*/ 40 } 41 int search_answer(int l,int r) 42 { 43 int ans=0,mid; 44 while(l<r) 45 { 46 mid=(l+r)>>1;/*取出中点*/ 47 int flag=check(mid); 48 if(flag==-1) l=mid+1;/*检验中点,==-1,说明分组数多了,所以要把最大字段和扩大,就把l=mid+1*/ 49 else if(flag==1) {r=mid;ans=mid;}/*这表示当前是可以凑出mid,并且分组数<=m的,分组数少了,和可以取到一个最大子段和的时候,因为我们要求最小字段和,所以这种情况下,我们仍要取左区间*/ 50 else r=mid;/*取不到mid,是不能来更新答的*/ 51 } 52 return ans; 53 } 54 void input() 55 { 56 int minn=(1<<31)-1,maxx=0; 57 for(int i=1;i<=n;++i) 58 { 59 a[i]=read(); 60 minn=min(minn,a[i]); 61 maxx+=a[i];/*确定二分查找的范围,从最小的元素到sum*/ 62 } 63 printf("%d\n",search_answer(minn,maxx)); 64 65 } 66 int main() 67 { 68 while(scanf("%d%d",&n,&m)==2) 69 { 70 input(); 71 } 72 return 0; 73 }
3.codevs 4768 跳石头
一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有N块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M块岩石(不能移走起点和终点的岩石)。
输入文件名为 stone.in。
输入文件第一行包含三个整数L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。
接下来N行,每行一个整数,第i行的整数Di(0 < Di < L)表示第i块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出文件名为stone.out。
输出文件只包含一个整数,即最短跳跃距离的最大值。
25 5 2
2
11
14
17
21
4
对于20%的数据,0≤M≤N≤10。 对于50%的数据,0≤M≤N≤100。
对于50%的数据,0≤M≤N≤100。
对于100%的数据,0≤M≤N≤50,000,1≤L≤1,000,000,000。
1 /*注意数据中有n==0&&m==的特殊情况,所以要特判 2 基本思路:与上面那道题目一致,上面是n个数分为m分, 3 这个题目是把n+1个数,分为n+1-m份而已; 4 */ 5 #include6 using namespace std; 7 #include 8 #include 9 #define N 50101 10 int con[N],zum[N]; 11 int L,n,m,minn=(1<<31)-1; 12 inline int read() 13 { 14 int ans=0;char s; 15 s=getchar(); 16 while(s<'0'||s>'9') s=getchar(); 17 while(s>='0'&&s<='9') 18 {ans=ans*10+s-'0'; 19 s=getchar(); 20 } 21 return ans; 22 } 23 void input() 24 { 25 L=read();n=read();m=read(); 26 int x; 27 con[0]=0; 28 for(int i=1;i<=n;++i) 29 { 30 zum[i]=read(); 31 con[i]=zum[i]-zum[i-1]; 32 minn=min(con[i],minn); 33 } 34 con[n+1]=L-zum[n]; 35 m=n+1-m; 36 n++; 37 } 38 int check(int x)/*二分一个最小值*/ 39 { 40 int sum=0,i=1,j; 41 bool flag=false; 42 int fz=0; 43 while(i<=n) 44 { 45 sum=0; 46 j=i; 47 while(sum<x) 48 { 49 sum+=con[j]; 50 j++; 51 if(j>n&&sum /*这表明最后一组分的不够了,说明最小值取大了*/ 52 return 1; 53 } 54 if(sum==x) 55 { 56 flag=true; 57 } 58 i=j; 59 fz++; 60 if(flag&&fz>=m) 61 return 0; 62 if(!flag&&fz>=m) 63 return -1; 64 /*在能找到x的时候,为了让最小值尽量大,我们要取右区间,同时当我们分组分多了的时候,也要分右区间才可以*/ 65 } 66 67 return 1; 68 } 69 int find_ans(int l,int r) 70 { 71 int mid,ans; 72 while(l<=r)/*注意二分的边界要这么写,l<=r,内部的循环每次是l=mid+1,或者r=mid-1,其他情况可能会死循环*/ 73 { 74 mid=(l+r)>>1; 75 int temp=check(mid); 76 if(temp==-1) 77 l=mid+1; 78 if(temp==0) 79 l=mid+1,ans=mid; 80 if(temp==1) 81 r=mid-1; 82 } 83 return ans; 84 } 85 int main() 86 { 87 88 input(); 89 if(n==1&&m==1) 90 printf("%d\n",L); 91 else 92 printf("%d\n",find_ans(minn,L)); 93 94 return 0; 95 }
4.codevs 1069 关押罪犯
2010年NOIP全国联赛提高组
S 城现有两座监狱,一共关押着N 名罪犯,编号分别为1~N。他们之间的关系自然也极
不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨
气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之
间的积怨越多。如果两名怨气值为c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并
造成影响力为c 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,
然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,
如果影响很坏,他就会考虑撤换警察局长。
在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在
两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只
要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那
么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是少?
第一行为两个正整数N 和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。
接下来的M 行每行为三个正整数aj,bj,cj,表示aj 号和bj 号罪犯之间存在仇恨,其怨气值为cj。数据保证,且每对罪犯组合只出现一次。
共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。
分类标签 Tags
1 /*解法:二分+扩展并查集(加权并查集),二分一个最大值,先处理关系坏的,如果推出不可避免的要大于当前的最大值,说明最大值不够了,就二分一个更大的数。*/ 2 /*关于”扩展并查集“: 3 读了飘过的小牛的“扩展并查集”,对于加权并查集又有了新的理解: 4 http://blog.csdn.net/niushuai666/article/details/6981689 5 特别是"关系域更新": 6 当然,这道题理解到这里思路已经基本明确了,剩下的就是如何实现,在实现过程中,我们发现,更新关系域是一个很头疼的操作,网上各种分析都有,但是都是直接给出个公式,至于怎么推出来的都是一笔带过,让我着实头疼了很久,经过不断的看discuss,终于明白了更新操作是通过什么来实现的。下面讲解一下 7 仔细再想想,rootx-x 、x-y、y-rooty,是不是很像向量形式?于是我们可以大胆的从向量入手: 8 tx ty 9 | | 10 x ~ y 11 对于集合里的任意两个元素x,y而言,它们之间必定存在着某种联系,因为并查集中的元素均是有联系的(这点是并查集的实质,要深刻理解),否则也不会被合并到当前集合中。那么我们就把这2个元素之间的关系量转化为一个偏移量(大牛不愧为大牛!~YM)。 12 由上面可知: 13 x->y 偏移量0时 x和y同类 14 x->y 偏移量1时 x被y吃 15 x->y 偏移量2时 x吃y 16 有了这个假设,我们就可以在并查集中完成任意两个元素之间的关系转换了。 17 不妨继续假设,x的当前集合根节点rootx,y的当前集合根节点rooty,x->y的偏移值为d-1(题中给出的询问已知条件) 18 (1)如果rootx和rooty不相同,那么我们把rooty合并到rootx上,并且更新relation关系域的值(注意:p[i].relation表示i的根结点到i的偏移量!!!!(向量方向性一定不能搞错)) 19 此时 rootx->rooty = rootx->x + x->y + y->rooty,这一步就是大牛独创的向量思维模式 20 上式进一步转化为:rootx->rooty = (relation[x]+d-1+3-relation[y])%3 = relation[rooty],(模3是保证偏移量取值始终在[0,2]间) 21 (2)如果rootx和rooty相同(即x和y在已经在一个集合中,不需要合并操作了,根结点相同),那么我们就验证x->y之间的偏移量是否与题中给出的d-1一致 22 此时 x->y = x->rootx + rootx->y 23 上式进一步转化为:x->y = (3-relation[x]+relation[y])%3, 24 若一致则为真,否则为假。 25 26 */ 27 #include28 using namespace std; 29 #include 30 #include 31 #include 32 #include 33 #define N 20100 34 #define M 100100 35 struct Edge{ 36 int u,v,w; 37 }edge[M]; 38 vector<int>relat[N]; 39 int n,m,maxx=0; 40 int father[N],val[N]; 41 int read() 42 { 43 int ans=0;char s; 44 s=getchar(); 45 while(s<'0'||s>'9') s=getchar(); 46 while(s>='0'&&s<='9') 47 { 48 ans=ans*10+s-'0'; 49 s=getchar(); 50 } 51 return ans; 52 } 53 int cmp(Edge p,Edge q) 54 { 55 return p.w>q.w; 56 } 57 void input() 58 { 59 n=read();m=read(); 60 for(int i=1;i<=m;++i) 61 { 62 edge[i].u=read(); 63 edge[i].v=read(); 64 edge[i].w=read(); 65 relat[edge[i].u].push_back(edge[i].v); 66 relat[edge[i].u].push_back(edge[i].w); 67 relat[edge[i].v].push_back(edge[i].u); 68 relat[edge[i].v].push_back(edge[i].w); 69 maxx=max(maxx,edge[i].w); 70 } 71 sort(edge+1,edge+m+1,cmp);/*必须先对已有的关系排序,因为这道题目符合贪心策略,先处理关系坏的一定优,防止关系略差的顶掉关系差的*/ 72 } 73 /*路径压缩的过程注意两点:一是更新子节点与父亲节点的关系--val,二是检查集合中的点是不是已经冲突了,就是推出的关系(两人在一个监狱),可是会有冲突了*/ 74 int find(int x,bool &flag,int v) 75 { 76 if(father[x]!=x) 77 { 78 int temp=father[x]; 79 father[x]=find(father[x],flag,v); 80 val[x]=(val[x]+val[temp])%2; 81 if(val[x]==0) 82 { 83 int size=relat[x].size(); 84 int i; 85 for(i=0;i i) 86 if(relat[x][i]==father[x]) 87 { 88 if(relat[x][i+1]>v) 89 flag=true; 90 break;/*因为关系不重复,跳出即可*/ 91 } 92 93 } 94 } 95 return father[x]; 96 } 97 int check(int x) 98 { 99 for(int i=1;i<=n;++i) 100 { 101 father[i]=i; 102 val[i]=0; 103 } 104 bool flag=false,qudao=false;/*bool 记录最大值是不是够,*/ 105 for(int i=1;i<=m;++i) 106 { 107 int r1=find(edge[i].u,flag,x); 108 int r2=find(edge[i].v,flag,x); 109 if(flag) break; 110 if(r1==r2) 111 { 112 if(val[edge[i].u]==val[edge[i].v]) 113 { 114 if(edge[i].w>x) 115 { 116 flag=true;/*怒气最大值不够用了*/ 117 break; 118 } 119 if(edge[i].w==x) qudao=true; 120 /*如果放到一个监狱中,就要判断能否恰好取到*/ 121 } 122 } 123 else /*合并集合*/ 124 { 125 if(edge[i].w>x) 126 { 127 val[r1]=(val[edge[i].v]+1-val[edge[i].u]+2)%2; 128 father[r1]=r2; 129 } 130 else 131 { 132 val[r1]=(val[edge[i].v]-val[edge[i].u]+2)%2; 133 father[r1]=r2;/*如果放到一个监狱中,就要判断能否恰好取到*/ 134 if(edge[i].w==x) qudao=true; 135 } 136 137 } 138 } 139 if(qudao) return 0; 140 if(flag) return -1; 141 return 1; 142 143 } 144 int find_ans(int l,int r) 145 { 146 int ans,mid;/*二分*/ 147 while(l<=r) 148 { 149 mid=(l+r)>>1; 150 int temp=check(mid); 151 if(temp==-1) 152 l=mid+1; 153 if(temp==0) 154 {ans=mid;r=mid-1;} 155 if(temp==1) 156 r=mid-1; 157 } 158 return ans; 159 } 160 int main() 161 { 162 input(); 163 printf("%d\n",find_ans(0,maxx)); 164 return 0; 165 }
5.二分+最短路 codevs:3336. 电话网络
★★☆ 输入文件:phone.in
输出文件:phone.out
简单对比
时间限制:1 s 内存限制:128 MB
由于地震使得连接汶川县城的电话线全部损坏,假如你是负责将电话线接到震中汶川县城的负责人,汶川县城周围分布着 N ( 1 ≤ N ≤ 1000 )根按 1..N 顺次编号的废弃的电话线杆去,任意两根电话线杆间都没有电话线相连。一共 P(1 ≤ P ≤ 10000) 对电话线杆间可以拉电话线,其余的由于地震使得无法被连接。
第 i 对电话线杆的两个端点分别为 Ai , Bi ,它们间的距离为 Li ( 1 ≤ Li ≤ 1000000 )。数据保证每对( Ai,Bi )最多只出现 1 次。编号为 1 的电话线杆已经接入了全国的电话网络,整个县城的电话线全都连到了编号为 N 的电话线杆上。也就是说,你的任务仅仅是找一条将 1 号和 N 号电话线杆连起来的路径,其余电话线杆并不一定要连入电话网络。
电信公司决定支援灾区免费为汶川县城连接 K ( 0 ≤ K < N )对由你指定的电话线杆。对于此外的那些电话线,需要为它们付费,总费用等于其中最长的电话线的长度(每根电话线仅连接一对电话线杆)。如果需要连接的电话线杆不超过 K 对,那么总支出为 0 。
请你计算一下,将电话线引到震中汶川县城最少需要在电话线上花多少钱?
[ 输入格式 ]
输入文件的第一行包含三个用空格隔开的整数: N,P 和 K 。
第二行到第 P+1 行:每行分别都为三个用空格隔开的整数: Ai,Bi 和 Li 。
[ 输出格式 ]
输出文件中仅包含一个整数,表示在这项工程上的最小支出。如果任务不可能完成,则输出- 1 。
[ 输入样例 ]
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
[ 输出样例 ]
4
1 /*解法:二分+最短路:这里的最短路是抽象意义上的最短路,dis表示的从1--i需要连几条线,所以我们二分一个“最长的电话线的长度x”,
当电话线长度>x,就+1进行转移,否则+0进行转移,同时记录路径上是否刚好有权值为x的边*/ 2 #include3 using namespace std; 4 #define N 1001 5 #include 6 #include 7 #define P 100010 8 struct Edge{ 9 int v,last,w; 10 }edge[P*2]; 11 int head[N],t=0; 12 bool visit[N]; 13 int dis[N],pre_path[N]; 14 int n,p,k; 15 inline int read() 16 { 17 int ans=0; 18 char s; 19 s=getchar(); 20 while(s<'0'||s>'9') s=getchar(); 21 while('0'<=s&&s<='9') 22 { 23 ans=ans*10+s-'0'; 24 s=getchar(); 25 } 26 return ans; 27 } 28 void add_edge(int u,int v,int w) 29 { 30 ++t; 31 edge[t].v=v; 32 edge[t].w=w; 33 edge[t].last=head[u]; 34 head[u]=t; 35 } 36 void input(int &maxx) 37 { 38 n=read();p=read();k=read(); 39 for(int i=1;i<=p;++i) 40 { 41 int a,b,c; 42 a=read();b=read();c=read(); 43 add_edge(a,b,c); 44 add_edge(b,a,c); 45 maxx=max(maxx,c); 46 } 47 } 48 int dijkstra(int x,bool <)/*dijikstra算法求最短路径*/ 49 { 50 memset(pre_path,0,sizeof(pre_path)); 51 memset(visit,false,sizeof(visit)); 52 memset(dis,99,sizeof(dis)); 53 bool flag=false; 54 dis[1]=0; 55 for(int i=1;i i) 56 { 57 int maxx=(1<<31)-1,p; 58 for(int i=1;i<=n;++i) 59 if(!visit[i]&&dis[i]<maxx) 60 { 61 maxx=dis[i];p=i; 62 } 63 visit[p]=true; 64 for(int l=head[p];l;l=edge[l].last) 65 { 66 if(!visit[edge[l].v]) 67 { 68 if(edge[l].w>x)/*边权>x,+1转移*/ 69 { 70 if(dis[edge[l].v]>dis[p]+1) 71 { 72 pre_path[edge[l].v]=p; 73 dis[edge[l].v]=dis[p]+1; 74 } 75 } 76 else { 77 if(dis[edge[l].v]>dis[p]) 78 { 79 pre_path[edge[l].v]=p; 80 dis[edge[l].v]=dis[p]; 81 } 82 } 83 } 84 } 85 } 86 if(dis[n]==1667457891) lt=false;/*表是1--n不连通*/ 87 if(dis[n]>k)return -1;/*连的边多了,说明*/ 88 int i=n; 89 /*记录路径的目的是判断x是不是刚好是路径上边的权值,注意不能在dijkstra中判断权值是不是等于x,因为更新的边不一定是最终路径中的边*/ 90 while(pre_path[i]) 91 { 92 int j=pre_path[i]; 93 for(int l=head[j];l;l=edge[l].last) 94 { 95 if(edge[l].v==i&&edge[l].w==x) 96 { 97 flag=true;break; 98 } 99 } 100 if(flag) break; 101 i=pre_path[i]; 102 } 103 if(flag) return 0;/*能取到x,返回0*/ 104 return 1; 105 } 106 int find_ans(int l,int r) 107 { 108 bool lt=true;/*表示1--n是否连通*/ 109 int ans,mid; 110 while(l<=r) 111 { 112 mid=(l+r)>>1; 113 int temp=dijkstra(mid,lt); 114 if(temp==-1) l=mid+1; 115 if(temp==0) ans=mid,r=mid-1; 116 if(temp==1) r=mid-1; 117 if(!lt) return -1; 118 } 119 return ans; 120 } 121 int main() 122 { 123 int maxx=0; 124 input(maxx);/*确定二分范围*/ 125 printf("%d\n",find_ans(0,maxx)); 126 return 0; 127 }
6.二分+LCA:BZOJ 4326: NOIP2015 运输计划
Time Limit: 30 Sec Memory Limit: 128 MBDescription
公元 2044 年,人类进入了宇宙纪元。L 国有 n 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n−1 条航道连通了 L 国的所有星球。小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之间不会产生任何干扰。为了鼓励科技创新, L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后,这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的物流公司的阶段性工作就完成了。如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?
第一行包括两个正整数 n,m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。接下来 n−1 行描述航道的建设情况,其中第 i 行包含三个整数 ai,bi 和 ti,表示第 i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。数据保证 1≤ai,bi≤n 且 0≤ti≤1000。接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j 个运输计划是从 uj 号星球飞往 vj号星球。数据保证 1≤ui,vi≤n
Output
输出文件只包含一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。
Sample Input
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
Sample Output
HINT
我的错误代码:
1 /*代码写的巨长,不仅爆了空间,结果还不对,直接绝望了。 2 没有处理好的两个地方:1.储存lca的路径 2.枚举删除那条边的过程, 3 我都是用了巨耗空间和时间的方法,直接不忍直视! 4 */ 5 #include6 using namespace std; 7 #include 8 #include 9 #define N 300010 10 #include 11 vector<int> ques[N]; 12 vector<int> path[N]; 13 int father[N],ance[N]; 14 int maxx=0; 15 bool LCA[N]={false}; 16 struct Edge{ 17 int u,v,last,ti; 18 }edge[N*3]; 19 int ans[N]; 20 int q; 21 int head[N],dis[N],t=0; 22 bool root[N]={false}; 23 int cnt[N];//n ge bian 24 int contflag[N]={false}; 25 int n,m,a,b,ti; 26 int read() 27 { 28 int ans=0;char s; 29 s=getchar(); 30 while(s<'0'||s>'9') s=getchar(); 31 while('0'<=s&&s<='9') 32 { 33 ans=ans*10+s-'0'; 34 s=getchar(); 35 } 36 return ans; 37 } 38 void add_edge(int u,int v,int w,int p) 39 { 40 edge[p].u=u; 41 edge[p].v=v; 42 edge[p].ti=w; 43 edge[p].last=head[u]; 44 head[u]=p; 45 } 46 void input() 47 { 48 n=read();m=read(); 49 for(int i=1;i i) 50 { 51 a=read();b=read();ti=read(); 52 add_edge(a,b,ti,i); 53 root[b]=true; 54 father[i]=i; 55 } 56 father[n]=n; 57 q=n-1; 58 for(int i=1;i<=m;++i) 59 { 60 a=read();b=read(); 61 ques[a].push_back(i); 62 ques[a].push_back(b); 63 ques[b].push_back(i); 64 ques[b].push_back(a); 65 q++; 66 add_edge(a,b,0,q); 67 } 68 } 69 int find(int x) 70 { 71 return father[x]==x?father[x]:father[x]=find(father[x]); 72 } 73 void tarjan(int k,int w,int p) 74 { 75 dis[k]=w; 76 ance[k]=k; 77 path[k].push_back(p); 78 int size=path[k].size(); 79 for(int l=head[k];l;l=edge[l].last) 80 { 81 for(int i=0;i i) 82 path[edge[l].v].push_back(path[k][i]); 83 tarjan(edge[l].v,w+edge[l].ti,l); 84 father[edge[l].v]=k; 85 ance[edge[l].v]=k; 86 } 87 LCA[k]=true; 88 int sze=ques[k].size(); 89 for(int i=1;i 2) 90 { 91 if(LCA[ques[k][i]]) 92 { 93 int zu=ance[find(ques[k][i])]; 94 ans[ques[k][i-1]]=dis[k]+dis[ques[k][i]]-2*dis[zu]; 95 maxx=max(maxx,ans[ques[k][i-1]]); 96 } 97 } 98 } 99 int check(int x) 100 { 101 int flag[1001]={0}; 102 memset(contflag,false,sizeof(contflag)); 103 int cont=m; 104 memset(cnt,0,sizeof(cnt)); 105 for(int i=n;i<=q;++i) 106 { 107 int sum=ans[i-(n-1)]; 108 int u=edge[i].u; 109 int v=edge[i].v; 110 int sze=path[u].size(); 111 for(int i=0;i i) 112 { 113 if(sum>x&&sum-edge[path[u][i]].ti<=x) 114 { 115 if(sum-edge[path[u][i]].ti==x) flag[++flag[0]]=path[u][i]; 116 cnt[path[u][i]]++; 117 if(!contflag[i]) 118 { 119 contflag[i]=true; 120 cont--; 121 } 122 } 123 } 124 sze=path[v].size(); 125 for(int i=0;i i) 126 { 127 if(sum>x&&sum-edge[path[v][i]].ti<=x) 128 { 129 if(sum-edge[path[v][i]].ti==x) flag[++flag[0]]=path[v][i]; 130 cnt[path[v][i]]++; 131 if(!contflag[i]) 132 { 133 contflag[i]=true; 134 cont--; 135 } 136 } 137 } 138 } 139 int biaozhi=false; 140 int qq=0; 141 for(int i=1;i i) 142 if(cnt[i]==m) 143 { 144 qq++; 145 for(int j=1;j<=flag[0];++j) 146 if(flag[j]==i) 147 { 148 biaozhi=true;break; 149 } 150 } 151 if(qq==0||cont>0) return -1; 152 if(qq>=1&&biaozhi) return 0; 153 return 1; 154 } 155 int find_answe(int l,int r) 156 { 157 int answe,mid; 158 int temp; 159 while(l<=r) 160 { 161 mid=(l+r)>>1; 162 int temp=check(mid); 163 if(temp==-1) l=mid+1; 164 if(temp==0) answe=mid,r=mid-1; 165 if(temp==1) r=mid-1; 166 } 167 return answe; 168 } 169 int main() 170 { 171 input(); 172 for(int i=1;i<=n;++i) 173 if(!root[i]) 174 { 175 tarjan(i,0,0); 176 break; 177 } 178 printf("%d\n",find_answe(0,maxx)); 179 return 0; 180 }
正确代码(不知道为什么这个代码在BZOJ上可以过,在codevs上最后一个点就超时):
1 /*真是一道好题,lca+二分+树上标记*/ 2 #include3 using namespace std; 4 #define N 300010 5 #include 6 #include 7 #include 8 struct Edge{ 9 int v,w,last; 10 }edge[N<<1]; 11 struct LCA{ 12 int a,b,zu,di; 13 }lca[N]; 14 int n,m,t=0; 15 bool visit_lca[N]={false}; 16 vector<int> que[N]; 17 int head[N],dis[N],father[N],fa[N],ance[N],sum[N],maxx=-1,e[N]; 18 int read() 19 { 20 int ans=0;char s; 21 s=getchar(); 22 while(s<'0'||s>'9') s=getchar(); 23 while('0'<=s&&s<='9') 24 { 25 ans=ans*10+s-'0'; 26 s=getchar(); 27 } 28 return ans; 29 } 30 void add_edge(int u,int v,int w) 31 { 32 ++t; 33 edge[t].v=v; 34 edge[t].w=w; 35 edge[t].last=head[u]; 36 head[u]=t; 37 } 38 void input() 39 { 40 int a,b,ti; 41 n=read();m=read(); 42 for(int i=1;i i) 43 { 44 a=read(); 45 b=read(); 46 ti=read(); 47 add_edge(a,b,ti); 48 add_edge(b,a,ti); 49 } 50 for(int i=1;i 1;++i) 51 { 52 lca[i].a=read(); 53 lca[i].b=read(); 54 que[lca[i].a].push_back(i); 55 que[lca[i].a].push_back(lca[i].b); 56 que[lca[i].b].push_back(i); 57 que[lca[i].b].push_back(lca[i].a); 58 } 59 } 60 int find(int x) 61 { 62 return (father[x]==x)?father[x]:father[x]=find(father[x]); 63 } 64 void tarjan(int k,int w1) 65 { 66 ance[k]=k; 67 father[k]=k; 68 dis[k]=w1; 69 for(int l=head[k];l;l=edge[l].last) 70 { 71 if(edge[l].v!=fa[k]) 72 { 73 fa[edge[l].v]=k; 74 e[edge[l].v]=l; 75 tarjan(edge[l].v,w1+edge[l].w); 76 father[edge[l].v]=k; 77 ance[edge[l].v]=k; 78 } 79 } 80 visit_lca[k]=true; 81 int sze=que[k].size(); 82 for(int i=1;i 2) 83 { 84 int v=que[k][i]; 85 if(visit_lca[v]) 86 { 87 lca[que[k][i-1]].zu=ance[find(v)]; 88 lca[que[k][i-1]].di=dis[k]+dis[v]-2*dis[lca[que[k][i-1]].zu]; 89 maxx=max(maxx,lca[que[k][i-1]].di); 90 } 91 } 92 } 93 void dfs(int x) 94 { 95 for(int l=head[x];l;l=edge[l].last) 96 { 97 if(edge[l].v!=fa[x]) 98 { 99 dfs(edge[l].v); 100 sum[x]+=sum[edge[l].v];/*标记上传*/ 101 } 102 } 103 } 104 int check(int x) 105 { 106 int tot=0,dec=0; 107 bool biaozhi=false; 108 memset(sum,0,sizeof(sum)); 109 for(int i=1;i<=m;++i) 110 { 111 if(lca[i].di>x)/*只记录不符合要求的边*/ 112 { 113 dec=max(dec,lca[i].di-x);/*至少需要删的边的长度*/ 114 sum[lca[i].a]++; 115 sum[lca[i].b]++; 116 sum[lca[i].zu]-=2; 117 tot++; 118 } 119 if(lca[i].di==x) biaozhi=true; 120 } 121 dfs(1); 122 int flag=false; 123 for(int i=1;i<=n;++i) 124 if(sum[i]==tot&&edge[e[i]].w>=dec) 125 {/*sum[i]==tot,表示在这tot条路径上的公共点,只有这些边删了才有意义*/ 126 if(edge[e[i]].w==dec) biaozhi=true; 127 flag=true; 128 break; 129 } 130 /*这里的逻辑关系一开始没有分析好,biaozhi表示可以取到x,(包括两种情况,一是本来就有lca[i].di==x,二是删边之后达到了x的状态,这两种都要考虑得到),flag表示能不能删边达到<=x的状态*/ 131 if(flag&&biaozhi)return 0; 132 if(flag) return 1; 133 return -1; 134 } 135 int find_ans(int l,int r) 136 { 137 int mid,ans=0; 138 while(l<=r) 139 { 140 mid=(l+r)>>1; 141 int temp=check(mid); 142 if(temp==-1) 143 l=mid+1;//zhao bu dao 144 if(temp==0) 145 ans=mid,r=mid-1; 146 if(temp==1) 147 r=mid-1; 148 } 149 return ans; 150 } 151 int main() 152 { 153 input(); 154 fa[1]=1; 155 tarjan(1,0); 156 printf("%d\n",find_ans(0,maxx)); 157 return 0; 158 }