二分算法~~~大综合

二分:一个非常神奇的算法:永远记住二分,分的是答案,直接在答案在的区间范围中二分,分出一个值,就判断是不是           答案,并进行转移

二分答案: 

如果已知候选答案的范围[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] ]

                 对于某个候选答案,如果“>λ的乘积个数"λ-1的乘积个数”≥k ,则答案为λ

 

        例如,在题目“Drying 烘干衣服”中 ==>

                 需要寻找第一个出现的“能”(true),即如果check(mid-1)==false && check(mid)==true ,则答案为mid.

 

1.codevs 1696 奇怪的函数

 时间限制: 1 s
 空间限制: 128000 KB
 题目等级 : 黄金 Gold
题目描述  Description

    自从得到上次的教训后,John的上课态度认真多了,也变得更爱动脑筋了。今天他又学习了一个新的知识:关于 xk 的位数。

    如果x大于0小于l,那么位数=1+小数部分×k,

    如果x≥l,那么位数=trunc(ln(x)/ln(10)×k)+1+小数部分×k。

    根据这些函数知识,他学会了求xk的位数了。但他又想到了另外一个问题,如果已知位数N,能不能求出使得 xk 达到或超过N位数字的最小正整数x是多少?

输入描述  Input Description

    输入一个正整数n(n≤2000000000)。

输出描述  Output Description

    输出使得 xk 达到n位数字的最小正整数x。

样例输入  Sample Input

    11

样例输出  Sample Output

    10

 1 #include
 2 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 #include
 2 #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 #include
 2 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 跳石头

 

 时间限制: 1 s
 空间限制: 128000 KB
 题目等级 : 黄金 Gold
 
题目描述  Description

一年一度的“跳石头”比赛又要开始了! 

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有N块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。 

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M块岩石(不能移走起点和终点的岩石)。

 

输入描述  Input Description

输入文件名为 stone.in。 

输入文件第一行包含三个整数L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。 

接下来N行,每行一个整数,第i行的整数Di(0 < Di < L)表示第i块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。 

 

输出描述  Output Description

输出文件名为stone.out。 

输出文件只包含一个整数,即最短跳跃距离的最大值。

 

样例输入  Sample Input

25 5 2

11 

14 

17

21

样例输出  Sample Output

4

数据范围及提示  Data Size & Hint

对于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 #include
 6 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全国联赛提高组

 时间限制: 1 s
 空间限制: 128000 KB
 题目等级 : 钻石 Diamond
题目描述 Description

S 城现有两座监狱,一共关押着N 名罪犯,编号分别为1~N。他们之间的关系自然也极

不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨

气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之

间的积怨越多。如果两名怨气值为c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并

造成影响力为c 的冲突事件。

每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,

然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,

如果影响很坏,他就会考虑撤换警察局长。

在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在

两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只

要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那

么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是少?

输入描述 Input Description

第一行为两个正整数N 和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。

接下来的M 行每行为三个正整数aj,bj,cj,表示aj 号和bj 号罪犯之间存在仇恨,其怨气值为cj。数据保证且每对罪犯组合只出现一次。

输出描述 Output Description

共1 行,为Z 市长看到的那个冲突事件的影响力。如果本年内监狱

中未发生任何冲突事件,请输出0。

样例输入 Sample Input

4 6

1 4 2534

2 3 3512

1 2 28351

1 3 6618

2 4 1805

3 4 12884

样例输出 Sample Output

3512

数据范围及提示 Data Size & Hint

罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件

影响力是3512(由2 号和3 号罪犯引发)。其他任何分法都不会比这个分法更优。

【数据范围】

对于30%的数据有N≤ 15。

对于70%的数据有N≤ 2000,M≤ 50000。

对于100%的数据有N≤ 20000,M≤ 100000。

分类标签 Tags 

二分法 并查集 树结构 大陆地区 NOIP全国联赛提高组 2010年
  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 #include
 28 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;ii)
 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 #include 3 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;ii) 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 MB

Description

公元 2044 年,人类进入了宇宙纪元。L 国有 n 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n−1 条航道连通了 L 国的所有星球。小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之间不会产生任何干扰。为了鼓励科技创新, L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后,这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的物流公司的阶段性工作就完成了。如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?

  Input

第一行包括两个正整数 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

6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5

Sample Output

11

HINT

将第 1 条航道改造成虫洞: 则三个计划耗时分别为:11,12,11,故需要花费的时间为 12。

将第 2 条航道改造成虫洞: 则三个计划耗时分别为:7,15,11,故需要花费的时间为 15。

将第 3 条航道改造成虫洞: 则三个计划耗时分别为:4,8,11,故需要花费的时间为 11。

将第 4 条航道改造成虫洞: 则三个计划耗时分别为:11,15,5,故需要花费的时间为 15。

将第 5 条航道改造成虫洞: 则三个计划耗时分别为:11,10,6,故需要花费的时间为 11。

故将第 3 条或第 5 条航道改造成虫洞均可使得完成阶段性工作的耗时最短,需要花费的时间为 11。

 我的错误代码:

  1 /*代码写的巨长,不仅爆了空间,结果还不对,直接绝望了。
  2    没有处理好的两个地方:1.储存lca的路径 2.枚举删除那条边的过程,
  3    我都是用了巨耗空间和时间的方法,直接不忍直视!
  4 */
  5 #include
  6 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;ii)
 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;ii)
 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;i2)
 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;ii)
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;ii)
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;ii)
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 #include
  3 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;ii)
 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;i1;++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;i2)
 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 }

 

转载于:https://www.cnblogs.com/c1299401227/p/5517752.html

你可能感兴趣的:(ui)