利用分治策略解题

今天封神讲分治和倍增,分治还是比较神奇的一种思想的,能解决不少问题,思路就是把一个大问题划分成小问题,然后通过递归求解小问题,最终得到大问题的解。

  • 归并排序
    归并排序是分治的一个典型应用,具体实现就是把一个长序列不断二分,分成的两部分比较大小后再合并到一个新的数组,最终得到排序后的结果。
    归并排序除了可以排序,还可以求逆序对,个人觉得比用树状数组要好想一些。
    POJ2299
    裸的求逆序对,方法是每次合并前后两个序列时,如果后面的数组要先于前面的插入,则这个数和前面的每一个数都构成一个逆序对。
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=500000+10;
int n;
long long a[maxn],tmp[maxn],ans;
void merge(int l,int m,int r){
     int i=l,j=m+1,k=l;
     while(i<=m&&j<=r){
         if(a[i]>a[j]){
             tmp[k++]=a[j++];
             ans+=m-i+1;
         }
         else{
             tmp[k++]=a[i++]; 
         }                  
     }
     while(i<=m) tmp[k++]=a[i++];
     while(j<=r) tmp[k++]=a[j++];
     for(int p=l;p<=r;p++){
         a[p]=tmp[p];        
     }
}
void merge_sort(int l,int r){
     if(l<r){
         int mid=(l+r)/2;
         merge_sort(l,mid);
         merge_sort(mid+1,r);
         merge(l,mid,r);
     }
}
int main(){
    while(scanf("%d",&n)){
        if(n==0) break;
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);        
        }
        ans=0;
        merge_sort(1,n);
        printf("%lld\n",ans);        
    } 
    return 0;
}
  • 平面上的最近点对
    这个貌似是个很洋气的题啊,数据范围很大,考虑分治策略。
    将平面的点按x坐标排序,然后将区间(l,r)分为(l,mid)和(mid+1,r),分别递归求解左右两个区间的最短距离,还有一种情况,两个点横跨两个区间,这样的点需要特殊处理。
    处理也挺麻烦的,详见代码。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include <iomanip>
#include<cmath>
using namespace std;
const int maxn=200000+10;
const double INF=1e100;
int n;
double ans;
struct node{
    long long x,y;
    int set;       
}p[maxn];
int t[maxn];

int cmp(node a,node b){
    if(a.x!=b.x) return a.x<b.x;   
    else return a.y<b.y;
}

int cmp2(int a,int b){
    return p[a].y<p[b].y;   
}

double dist(int x,int y){
    if(p[x].set==p[y].set) return INF;   
    double ans2= sqrt((p[x].x-p[y].x)*(p[x].x-p[y].x) + (p[x].y-p[y].y)*(p[x].y-p[y].y));
    return ans2;
}

double work(int l,int r){
    if(l==r) return INF;
    if(l==r-1) return dist(l,r);
    int m=(l+r)/2;
    double minl=work(l,m);
    double minr=work(m+1,r);
    double minlr=min(minl,minr);

    int k=0;
    for(int i=l;i<=r;i++){
        if(p[i].x-p[i+1].x<=minlr){
            k++;
            t[k]=i;                    
        }        
    }

    sort(t+1,t+k+1,cmp2);

    for(int i=1;i<=k;i++){
        for(int j=i+1;j<=k;j++){
                if(p[t[i]].y-p[t[j]].y >=minlr|| p[t[i]].set==p[t[j]].set) break;
            minlr=min(minlr,dist(t[i],t[j]));    
        }       
    }
    return minlr;
}

int main(){
    int T;
    scanf("%d",&T);
    for(int c=1;c<=T;c++){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
             scanf("%lld%lld",&p[i].x,&p[i].y); 
             p[i].set=1;      
        }
        for(int i=n+1;i<=2*n;i++){
             scanf("%lld%lld",&p[i].x,&p[i].y);   
             p[i].set=2;      
        }  
        n*=2;
        sort(p+1,p+n+1,cmp);
        ans=work(1,n);
        //printf("%.3lf\n",ans);
        cout<<setiosflags(ios::fixed)<<setprecision(3)<<ans<<endl;      
    }
    return 0;
}
                                                helenkeller
                                                 2016.7.3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值