【説明する】分治

拉呱

分治算法的基本思想是将一个规模为 N 的问题分解为 K 个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。

                  ——以上来自百度百科。

* 分治法解题的一般步骤:
1 分解,将要解决的问题划分成若干规模较小的同类问题;
- 二分法:区间对半分开
2 求解,当子问题划分得足够小时,用较简单的方法解决;
- 边界情况:可以直接操作
3 合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。
- 合并操作:根据不同的题目来确定

例题:

-------------------------------T1+T2-------------------------------

其实这个题用冒泡排序做的,但用归并排序也能做出来(分析一下此题与逆序对是有相同之处的)

由于两者的代码完全一样,就只放一个啦  (>^ω^<)喵

1.洛谷 P1116 车厢重组

题目描述

在一个旧式的火车站旁边有一座桥,其桥面可以绕河中心的桥墩水平旋转。一个车站的职工发现桥的长度最多能容纳两节车厢,如果将桥旋转180度,则可以把相邻两节车厢的位置交换,用这种方法可以重新排列车厢的顺序。于是他就负责用这座桥将进站的车厢按车厢号从小到大排列。他退休后,火车站决定将这一工作自动化,其中一项重要的工作是编一个程序,输入初始的车厢顺序,计算最少用多少步就能将车厢排序。

输入输出格式

输入格式:

输入文件有两行数据,第一行是车厢总数N(不大于10000),第二行是N个不同的数表示初始的车厢顺序。

输出格式:

一个数据,是最少的旋转次数。

输入输出样例

输入样例#1:
4
4 3 2 1 
输出样例#1:
6

2.洛谷 P1908 逆序对

题目描述

猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中ai>aj且i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。

输入输出格式

 输入格式:

第一行,一个数n,表示序列中有n个数。

第二行n个数,表示给定的序列。

 输出格式:

给定序列中逆序对的数目。

 输入输出样例

 输入样例#1:
6
5 4 2 6 3 1
 输出样例#1:
11

说明

对于50%的数据,n≤2500

对于100%的数据,n≤40000。

思路:

     归并过程为:比较A[i]和A[j]的大小,若A[i]≤A[j],则将第一个有序表中的元素A[i]复制到R[k]中,并令i和k分别加1,即使之分别指问后一单元,否则将第二个有序表中的元素A[j]复制到R[k]中,并令j和k分别加1;如此循环下去,直到其中的一个有序表取完,然后再将另一个有序表中剩余的元素复制到R中从下标k到下标t的单元.
     归并排序算法我们用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。对左右子区间的排序与原问题一样,所以我们可以调用同样的子程序,只是区间大小不一样。

代码酱来也~

#include <iostream>
#include <cstdio>
#define LL long long 

using namespace std;

const int M = 4e4 + 1;
int n;
LL ans;
int a[M],b[M];

void gsort(int l,int r)
{
    if(l==r) return;///不用再次排序
    int m=(l+r)>>1;///取中间 
    gsort(l,m),gsort(m+1,r);
    int i=l,j=m+1;
    int k=l;///将a数组的值赋值到b数组 
    while(i<=m && j<=r)
    {
        if(a[i]<=a[j])
            b[k++]=a[i++];///先赋值再++
        else
        {
            b[k++]=a[j++];///
            ans+=(LL)(m)-(LL)i+1;///记录当前所形成的逆序对的个数 
        }
    }
    while(i<=m) b[k++]=a[i++];///赋值剩余没有进行比较的 
    while(j<=r) b[k++]=a[j++];///
    for(int i=l;i<=r;i++)
        a[i]=b[i];///排完序后将排好序的在赋值回去 
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]);
    gsort(1,n);
    printf("%lld\n",ans);
    return 0;
}
View Code

--------------------------------T3--------------------------------

3.Codevs 1688 求逆序对

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

给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目

数据范围:N<=105。Ai<=105。时间限制为1s。

输入描述 Input Description

第一行为n,表示序列长度,接下来的n行,第i+1行表示序列中的第i个数。

输出描述 Output Description

所有逆序对总数.

样例输入 Sample Input

4

3

2

3

2

样例输出 Sample Output

3

数据范围及提示 Data Size & Hint

思路:

  这题有毒!!!where is the 数据范围???
  这数据范围太大了有没有!!!所以int类型的就会炸,将int改为long long就ok了!
  这题我交了好几遍!

代码酱来也~

#include <iostream>
#include <cstdio>
#define LL long long 

using namespace std;

const int M = 1e5 + 1;
int n;
LL ans;
int a[M],b[M];

void gsort(int l,int r)
{
    if(l==r) return;///不用再次排序
    int m=(l+r)>>1;///取中间 
    gsort(l,m),gsort(m+1,r);
    int i=l,j=m+1;
    int k=l;///将a数组的值赋值到b数组 
    while(i<=m && j<=r)
    {
        if(a[i]<=a[j])
            b[k++]=a[i++];///先赋值再++
        else
        {
            b[k++]=a[j++];///
            ans+=(LL)(m)-(LL)i+1;///记录当前所形成的逆序对的个数 
        }
    }
    while(i<=m) b[k++]=a[i++];///赋值剩余没有进行比较的 
    while(j<=r) b[k++]=a[j++];///
    for(int i=l;i<=r;i++)
        a[i]=b[i];///排完序后将排好序的在赋值回去 
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]);
    gsort(1,n);
    printf("%lld\n",ans);
    return 0;
}
View Code

--------------------------------T4--------------------------------

4.洛谷 P1115 最大子段和

题目描述

给出一段序列,选出其中连续且非空的一段使得这段和最大。

输入输出格式

输入格式:

输入文件maxsum1.in的第一行是一个正整数N,表示了序列的长度。

第2行包含N个绝对值不大于10000的整数A[i],描述了这段序列。

输出格式:

输入文件maxsum1.out仅包括1个整数,为最大的子段和是多少。子段的最小长度为1。

输入输出样例

输入样例#1:
7
2 -4 3 -1 2 -4 3
输出样例#1:
4

说明

【样例说明】2 -4 3 -1 2 -4 3

【数据规模与约定】

对于40%的数据,有N ≤ 2000。

对于100%的数据,有N ≤ 200000。

思路:

  这道题真的就是一道模板题,但是为什么我交了好几遍就是没有AC呢?

  原因出在第二个点上,因为第二个点里的数据似乎全部都是负的,所以在进行初始化的时候需要赋值为一个极小数(ans,lmax,rmax这三个)

  不然按一般的话,一定是从0开始,所以负数就出不来,所以……

代码酱来也~

#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long 

using namespace std;

const int M = 2e5 + 233;
int n;
LL ans;
int a[M];

LL calc(int l,int r)
{
    if(l==r) return a[l];///不用再次排序
    int m=(l+r)>>1;///取中间
    LL ret=max(calc(l,m),calc(m+1,r)); 
    
    /* find the maxl and maxr */
    int maxl=-1e6,maxr=-1e6;
    for(int i=m,s=0;i>=l;i--) s+=a[i],maxl=max(maxl,s);
    for(int i=m+1,s=0;i<=r;i++) s+=a[i],maxr=max(maxr,s);
    ret=max(ret,(LL)(maxl)+(LL)(maxr));
    return ret;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]);
    ans=-1e6;
    ans=max(ans,calc(1,n));
    printf("%lld\n",ans);
    return 0;
}
View Code

--------------------------------T5--------------------------------

5.洛谷P1010 幂次方

题目描述

任何一个正整数都可以用2的幂次方表示。例如

    137=2^7+2^3+2^0         

同时约定方次用括号来表示,即a^b 可表示为a(b)。

由此可知,137可表示为:

    2(7)+2(3)+2(0)

进一步:7= 2^2+2+2^0 (2^1用2表示)

    3=2+2^0   

所以最后137可表示为:

    2(2(2)+2+2(0))+2(2+2(0))+2(0)

又如:

    1315=2^10 +2^8 +2^5 +2+1

所以1315最后可表示为:

    2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)

输入输出格式

输入格式:

一个正整数n(n≤20000)。

输出格式:

符合约定的n的0,2表示(在表示中不能有空格)

输入输出样例

输入样例#1:
1315
输出样例#1:
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)

思路:

  如果遇到2或者1,特判一下,直接输出,其余的如果能够继续分解,就将其分解(分治~)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;

void work(int n)
{
    if(n==1)
    {
        printf("2(0)");
        return;
    }
    if(n==2)
    {
        printf("2");
        return;
    }
    else
    {
        int j=1,i=0;
        do
        {
            j*=2;
            if(j>n)///超出该数n 
            {
                j/=2;
                if(i==1) printf("2");///特判 
                else
                {
                    printf("2(");///输出形式 
                    work(i);
                    printf(")");
                }
                if(n-j!=0)///若还能够继续分解 
                {
                    printf("+");///用+连接 
                    work(n-j);///继续分解 
                }
                return;
            }
            else i++;///如果还不够大,继续加 
        }while(1);
    }
}

int main()
{
    int n;
    cin>>n;
    work(n);
    return 0;
}
View Code

--------------------------------T6--------------------------------

6.P1024 一元三次方程求解

题目描述

有形如:ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值>=1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。

提示:记方程f(x)=0,若存在2个数x1和x2,且x1<x2,f(x1)*f(x2)<0,则在(x1,x2)之间一定有一个根。

输入输出格式

输入格式:

一行,4个实数A,B,C,D。

输出格式:

一行,三个实根,并精确到小数点后2位。

输入输出样例

输入样例#1:
1 -5 -4 20
输出样例#1:
-2.00 2.00 5.00

思路:

  这题是一个很典型的数学问题,用二分法求零点(虽然有公式可以用,但是暴力枚举每个数也能过)

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define Db double

using namespace std;

Db a,b,c,d;

Db f(Db x)
{
    return a*x*x*x+b*x*x+c*x+d;
}

Db works(int x,int y)
{
    Db x1=(Db)(x),x2=(Db)(y);
    Db mid=(x1+x2)/2,t1,t2;
    while(x1<=x2)
    {
        t1=f(mid);
        t2=f(x1);
        if(t1*t2<=0) x2=mid-0.0001;
         else x1=mid+0.0001;
        mid=(x1+x2)/2;///更新 
    }
    return mid;
}

int main()
{
    scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
    Db f1,f2;
    for(int v=-100;v<=100;v++)
    {
//        f1=a*v*v*v+b*v*v+c*v+d;
        f1=f(v);
//        f2=a*(v+1)*(v+1)*(v+1)+b*(v+1)*(v+1)+c*(v+1)+d;
        f2=f(v+1);
        if(f1==0) printf("%.2lf ",Db(v));
        if(f1*f2<0)
        {
            Db ans=works(v,v+1);
            printf("%.2lf ",ans);
        }
    }
    return 0;
}
View Code

--------------------------------T7--------------------------------

7.luogu P1083 借教室

题目描述

在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。

面对海量租借教室的信息,我们自然希望编程解决这个问题。

我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。

我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提

供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。

现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。

输入输出格式

输入格式:

第一行包含两个正整数n,m,表示天数和订单的数量。

第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。

接下来有m行,每行包含三个正整数dj,sj,tj,表示租借的数量,租借开始、结束分别在

第几天。

每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。

输出格式:

如果所有订单均可满足,则输出只有一行,包含一个整数 0。否则(订单无法完全满足)

输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。

输入输出样例

输入样例#1:
4 3 
2 5 4 3 
2 1 3 
3 2 4 
4 2 4
输出样例#1:
-1 
2

说明

【输入输出样例说明】

第 1 份订单满足后,4 天剩余的教室数分别为 0,3,2,3。第 2 份订单要求第 2 天到

第 4 天每天提供 3 个教室,而第 3 天剩余的教室数为 2,因此无法满足。分配停止,通知第

2 个申请人修改订单。

【数据范围】

对于10%的数据,有1≤ n,m≤ 10;

对于30%的数据,有1≤ n,m≤1000;

对于 70%的数据,有1 ≤ n,m ≤ 10^5;

对于 100%的数据,有1 ≤ n,m ≤ 10^6,0 ≤ ri,dj≤ 10^9,1 ≤ sj≤ tj≤ n。

NOIP 2012 提高组 第二天 第二题

思路:

  二分答案水过2333

代码:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int M = 1e6 + 8;
int n,m;
int ma[M],sum[M];
int d[M],s[M],e[M];

bool calc(int mid)
{
    memset(sum,0,sizeof(sum));
    for(int i=1;i<=mid;i++)
    {
        sum[s[i]]+=d[i];///表示从开始时间往后租借多少天 
        sum[e[i]+1]-=d[i];
    }
    ///特判 
    if(sum[1]>ma[1]) return 1;///表示第一天就不够租借了 
    for(int i=2;i<=n;i++)
    {
        sum[i]+=sum[i-1];///前缀和,表示前几天一共有多少需要被租借
        if(sum[i]>ma[i])///如果不够了
            return 1; 
    }
    return 0;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&ma[i]);
//        sum[i]=ma[i]+sum[i-1];
    }
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&d[i],&s[i],&e[i]);
    int l=1,r=m;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(calc(mid)) r=mid;///记录下出现差错的编号 
        else l=mid+1;
///让修改的人的编号再大一点,知道找出不成功的编号或者最终的成功 
    }
    if(m!=r)
    {
        printf("-1\n");
        printf("%d",r);
    }
    else printf("0\n");
    return 0;
}
View Code

--------------------------------T8--------------------------------

8.luogu P2678 跳石头

题目背景

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

题目描述

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

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

输入输出格式

输入格式:

输入文件名为 stone.in。

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

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

输出格式:

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

输入输出样例

输入样例#1:
25 5 2 
2
11
14
17 
21
输出样例#1:
4

说明

输入输出样例 1 说明:将与起点距离为 2 和 14 的两个岩石移走后,最短的跳跃距离为 4(从与起点距离 17 的岩石跳到距离 21 的岩石,或者从距离 21 的岩石跳到终点)。

另:对于 20%的数据,0 ≤ M ≤ N ≤ 10。 对于50%的数据,0 ≤ M ≤ N ≤ 100。

对于 100%的数据,0 ≤ M ≤ N ≤ 50,000,1 ≤ L ≤ 1,000,000,000。

思路:

  这道题可以用二分答案+贪心来做

  首先我们定义l为0,r为给出的起点终点之间的长度,然后用二分答案做,将枚举到的答案检查一下是否行得通;

  若行得通,继续寻找更优的答案,若行不通,寻找更小的答案;

  二分答案是很好想的,也很好写,但是问题是check数组不太好想

  我们根据题目的意思可以得到最多取走的石子数为m,所以如果当前答案已经给确定,但根据这个答案求出的最终取走的石子数要大于m,那么当前答案不成立

  如何判断是否能被取走呢?因为题目为最短距离最大,所以只需判断从上一块石头跳到当前石头的距离是否比答案大即可;

  如果大于,就跳上去,因为给出d数组是按照距离远近来的;反之若小于,就将这块石头取走,因为不满足题意.这既为check函数

代码:

#include <iostream>
#include <cstdio>

using namespace std;

const int M = 5e4 + 1;
int L,n,m;
int d[M];

bool check(int x)
{
    int last=0;
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(d[i]-last<x)///make longer
            ans++;
        else 
            last=d[i];///record
    }
    if(ans>m)///if out of range 
        return 0;
    return 1;
}

int main()
{
    scanf("%d%d%d",&L,&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&d[i]);
    int l,r,mid;
    l=0,r=L;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(check(mid))
            l=mid+1;
        else
            r=mid-1;
    }
    printf("%d\n",l-1);
    return 0;
}
View Code

--------------------------------T9--------------------------------

9.luogu P1182 数列分段Section II

题目描述

对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小。

关于最大值最小:

例如一数列4 2 4 5 1要分成3段

将其如下分段:

[4 2][4 5][1]

第一段和为6,第2段和为9,第3段和为1,和最大值为9。

将其如下分段:

[4][2 4][5 1]

第一段和为4,第2段和为6,第3段和为6,和最大值为6。

并且无论如何分段,最大值不会小于6。

所以可以得到要将数列4 2 4 5 1要分成3段,每段和的最大值最小为6。

输入输出格式

输入格式:

输入文件divide_b.in的第1行包含两个正整数N,M,第2行包含N个空格隔开的非负整数A[i],含义如题目所述。

输出格式:

输出文件divide_b.out仅包含一个正整数,即每段和最大值最小为多少。

输入输出样例

输入样例#1:
5 3
4 2 4 5 1
输出样例#1:
6

说明

对于20%的数据,有N≤10;

对于40%的数据,有N≤1000;

对于100%的数据,有N≤100000,M≤N, A[i]之和不超过10^9。

思路:

  这道题可以用二分答案+前缀和来做,但是我搞了好久依旧是80分,不知道哪里错了...

代码:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int M = 1e5 + 8;
int n,m,maxx,minn;
int sum[M],a[M];
int ans;

bool calc(int now)
{
    int mm=maxx;
    int cnt=0,last=0;
    int i=1;
    while(cnt<m)
    {
        while(sum[i]-sum[last]<=now)
            i++;
        i--;
        mm=mm-(sum[i]-sum[last]);
        last=i;
        cnt++;
        if(mm<=0) return 1;
    }
    return 0;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        maxx+=a[i];
        if(minn<a[i])
         minn=a[i];
        sum[i]=sum[i-1]+a[i];
    }
    sum[n+1]=0x7fffffff;///因为之后要用到sum[n+1]来进行操作 
    int l=minn,r=maxx;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(calc(mid)) r=(ans=mid)-1;
        else l=mid+1;
    }
    printf("%d",ans);
    return 0;
}
My View Code
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int M = 1e5 + 8;
int n,m,maxx,minn;
int sum[M],a[M];
int ans;

bool calc(int now)
{
    int tmp=0,total=0;
    for(int i=1;i<=n;i++)
    {
        if(total+a[i]<=now)
            total+=a[i];
        else///若超出当前枚举到的答案最大值 
        {
            tmp++;///新的一段
            total=a[i]; 
        }
    }
    if(tmp<m) return 1;
    return 0;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        maxx+=a[i];
        if(minn<a[i])
         minn=a[i];
    }
    int l=minn,r=maxx;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(calc(mid)) r=(ans=mid)-1;
        else l=mid+1;
    }
    printf("%d",ans);
    return 0;
}
AC View Code

End.

posted @ 2017-06-18 21:24  夜雨声不烦  阅读(509)  评论(3编辑  收藏  举报