分治与二分三分用法介绍

今天主要是写了一些分治的题目主要是一般的分治、二分、三分、大概了解了在树上点分治的思想(代码就gg了)…… 做了有5道这方面的题目 今天所理解的分治就是不断想办法将问题规模变小而问题的性质不变 就可以通过这种方法递归


question 1 : poj3714 Raid;

题意 : 给你2n个点

前n个点一组 后n个点一组

n 是 1e5

问你前一组和后一组中任意两个点的最小距离

solve : 考虑一个问题给你n个点 问你任何两个点之间的最小距离

我们可以用分治的思想去解决这个问题

把n个点分成 n / 2 的两个部分

递归求出左边n / 2个点的任何两点的最小距离 和 右边n / 2 点的最小距离

然后用d 去记录两者的最小值 记录之后 从中间到两边 分别 划 d 的距离 不难发现所有距离可能小于d的点对都必须在这个区间之内。

然后对于左边的一个点 右边的可能与它距离小于d的点都在 纵坐标 y - d 到 y + d 之间 ;不难证明对于一个左边的点最多有6个右边的点与之对应使得 两点之间的距离可能小于d,有了这个结论就可以先将y排序然后暴力写出了

知道了上面那个题的做法 只需要再判断这两个点是不是位于左右两边就可以了。

AC code : 

#include<cstdio>

#include<cstring>

#include<cmath>

#include<algorithm>

using namespace std;

const double oo=1e50;

int n;

double ab(double x)

{

    return x>0?x:-x;

}

struct node

{

    double x,y;

    bool bel;

}a[200010],temp[200010];

bool cx(const node &a,const node &b)

{

    return a.x<b.x;

}

bool cy(const node &a,const node &b)

{

    return a.y<b.y;

}

double min(double x,double y)

{

    return x<y?x:y;

}

double dis(node p,node q)

{

    if (p.bel==q.bel) return oo;

    return sqrt((p.x-q.x)*(p.x-q.x)+(p.y-q.y)*(p.y-q.y));

}

double make(int l,int r)

{

    if (l==r) return oo;

    int m=(l+r)/2,i,j,k,ll,rr,p,q,x,y,z,cnt=0;

    double ans=min(make(l,m),make(m+1,r));

    for (i=l;i<=r;i++)

      if (ab(a[i].x-a[m].x)<=ans)

        temp[++cnt]=a[i];

    sort(temp+1,temp+cnt+1,cy);

    for (i=1;i<=cnt;i++)

      for (j=i+1;j<=cnt;j++)

      {

        if (temp[j].y-temp[i].y>ans) break;

        ans=min(ans,dis(temp[i],temp[j]));

      }

    return ans;

}

int main()

{

    int i,j,k,T;

    scanf("%d",&T);

    while (T--)

    {

        scanf("%d",&n);

        for (i=1;i<=n;i++)

        {

            scanf("%lf%lf",&a[i].x,&a[i].y);

            a[i].bel=0;

        }

        for (i=1;i<=n;i++)

        {

            scanf("%lf%lf",&a[i+n].x,&a[i+n].y);

            a[i+n].bel=1;

        }

        sort(a+1,a+2*n+1,cx);

        printf("%.3f\n",make(1,2*n));

    }

}

question 2 : codeforce 768 B 

题意:给一个数n,和一个区间[l,r] (r-l<1e5,n<2^50),每次可以把数n分成(n/2,n%2,n/2)知道所有数变成0或1,问区间内有多少个1?

solve :  不难发现 要询问区间长度最多为 1e5 不难想到去询问区间内每一个数是 1 还是 0 然后就通过递归n去判断区间的每一个 在l 到 r之间的1到底是多少 不难发现这棵树是一棵类似于完全二叉树的东西,递归层数不会超过50 然后就可以轻松愉快的ac 了 感觉像线段树。

AC code :

#include <iostream>

#include <algorithm>

#include <cstring>

using namespace  std;

long long l,r;

long long cnt = 0;

int ans = 0;

long long len = 1;

int dfs(long long n,long long ll,long long rr){

    if(n == 0 || ll > r || rr < l){

        return 0;

    }

    if(n == 1 && ll == rr){

        return 1;

    }

    long long mid = (ll + rr) / 2;

    return dfs(n % 2,mid,mid) + dfs(n / 2,ll,mid - 1)  + dfs(n / 2,mid + 1,rr);

}

int main(){

    ios_base :: sync_with_stdio(false);

    long long n;

    cin >> n >> l >> r;

    long long m = n;

    while(m > 1){

        len *= 2;

        len ++;

        m /= 2;

    }

    ans = dfs(n,1,len);

    cout << ans << endl;

    return 0;

}

question 3: codeforce 448C 

有n个木板 高度分别为h[i] 现在给木板刷漆可以横着刷也可以竖着刷一次只能横着刷一排(不可以跳过)或者竖着刷一个木板 问你最少刷多少次可以将所有木板刷完。

solve : 不难发现我们在横着刷的时候一定要刷完最小高度的一个才可以停止否则肯定会使得最后的结果增大,除了横着刷我们还可以竖着刷,如果竖着刷就一次性全部刷完才可能会达到最优的情况。所以现在就得到两种刷的方案 显然第二种方案需要n次才可以全部刷完,而对于第一种当刷完最短的那个木板之后我们就可以把这个木板的位置作为分开的地方将问题划分成左边部分和右边部分进行递归分治。

AC code :

#include <iostream>

#include <algorithm>

#include <cstring>


using namespace  std;

const int maxn = 5005;

const int INF = 1e9 + 7;


int h[maxn] = {0};

int merge(int l,int r){

    if(l > r){

        return 0;

    }

    if(l == r){

        if(h[l]){

//            cout << l << ' ' << r << endl;

            return 1;

        }

        return 0;

    }

    int ans = r - l + 1;

    int sum = 0;

    int mi = INF;

    for(int i = l ;i <= r;++i){

        mi = min(h[i],mi);

    }

    sum = mi;

    int ll = l;

//    cout << sum << endl;

    for(int i = l;i <= r;++i){

        h[i] -= mi;

        if(h[i] == 0){

            sum += merge(ll,i-1);

            ll = i + 1;

        }

    }

    sum += merge(ll,r);

//    cout << l << ' ' << r << ' ' << sum << endl;

    ans = min(ans,sum);

    return ans ;

}

int main(){

    ios_base :: sync_with_stdio(false);

    int n;

    cin >> n;

    for(int i = 1;i <= n;++i){

        cin >> h[i];

    }

    int ans = merge(1,n);

    ans = min(ans,n);

    cout << ans << endl;

    return 0;

}

question 4  NEERC  J Buoys

题意 : 一维坐标给你n个点 让你移动任何多个点 问你使得任意两个点之间的距离相等的最小移动距离 n <= 400

solve : 首先我就想去二分答案 但是我发现我不会写check (太菜了,要是汀老师写他肯定会)没办法我就只能去三分距离了 三分距离 然后暴力枚举那一个点不动计算其他点的移动距离求一个最小的就可以了 幸好这个题对精度要求不严格,(PS : NEERC的题目竟然是文件输入输出……)

AC code :

#include <iostream>

#include <algorithm>

#include <cstring>

#include <cstdio>

using namespace std;

const int maxn = 410;

const double INF = 1e12;

const double eps = 1e-12;

double ab(double x) {return x > 0.0 ? x : -x;}

double a[maxn];

double b[maxn];

double c[maxn];

double ans = INF;

int n;

double check(double d){

    double temp = 0;

    double s = INF;

    for(int i = 0;i < n;++i){

        temp = 0;

        for(int j = 0;j < n;++j){

            c[j] = a[i] + (j - i) * d;

            temp += ab(c[j] - a[j]);

        }

        s = min(s,temp);

        if(temp < ans){

            for(int k = 0;k < n;++k){

                b[k] = c[k];

                ans = min(ans,s);

            }

        }

    }


    return s;

}

int main(){

    freopen("input.txt","r",stdin);

    freopen("output.txt","w",stdout);

    scanf("%d",&n);

    for(int i = 0;i < n;++i){

        scanf("%lf",&a[i]);

    }

    double l = 0;

    double r = INF;

    while(r - l > eps){

        double mid = (2 * l + r) / 3;

        double midd = (l + 2 * r) / 3;

        if(check(mid) > check(midd)){

            l = mid;

        }

        else{

            r = midd;

        }

    }

    printf("%.4f\n",ans);

    for(int i = 0;i < n;++i){

        printf("%.10f ",b[i]);

    }

    return 0;

}

question 5: codeforce 578 C

一道三分题 挺水的难点在于卡你精度 …..

AC CODE : 

#include <iostream>

#include <cstdio>

#include <vector>

#include <cstring>

#include <set>

using namespace std;

const int maxn=200000+10;

const double INF = 1<<30;

double eps=1e-11;

set <int> st;

int cnt = 0;

double x;

int n;

typedef long long ll;

double a[maxn];

double b[maxn];

double _abs(double x){

    if(x<0) return -x;

    else return x;

}

double F(double x){

    for(int i=1;i<=n;i++){

        b[i]=a[i]-x;

    }

    double ans1=b[1],ans2=b[1];

    double _max=ans1,_min=ans2;

    for(int i=2;i<=n;i++){

        if(ans1>0){

            ans1+=b[i];

        }else{

            ans1=b[i];

        }

        

        _max=max(_max,ans1);

        

        if(ans2<0){

            ans2+=b[i];

        }else{

            ans2=b[i];

        }

        

        _min=min(_min,ans2);

    }

        

        

    return max(_abs(_min),_abs(_max));

}


int main(void){

    scanf("%d",&n);

    for(int i=1;i<=n;i++){

        scanf("%lf",&a[i]);

        if(st.count(a[i])){

            cnt ++;

        }

        else

        st.insert(a[i]);

    }

    if(cnt == n - 1 && a[1] == a[2]){

        cout << 0 << endl;

        return 0;

    }

    double L=-INF,R=INF;

    eps /= 2;

    while(R-L>eps){

        double m1=L+(R-L)/3;

        double m2=R-(R-L)/3;

        if(F(m1)<F(m2)){

            R=m2;

        }else{

            L=m1;

        }

//      

    }

    printf("%.9lf",F(L));

    return 0;

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值