第八章 高效算法设计

分治法求最大连续和

注意范围是[x,y)

#include<bits/stdc++.h>
using namespace std;
int maxsum(int *A,int x,int y){

    if(y-x==1) return A[x];
    int m=x+(y-x)/2;
    int maxs = max(maxsum(A,x,m),maxsum(A,m,y));
    int v,L,R;
    v=0; L=A[m-1];
    for(int i=m-1;i>=x;i--) L=max(L,v+=A[i]);
    v=0; R=A[m];
    for(int i=m;i<y;i++) R=max(R,v+=A[i]);
    return max(maxs, L+R);
}
int main()
{
    int A[]={1,-1,2,3,-2};
    printf("%d\n",maxsum(A,0,4));
    return 0;
}

归并排序,注意范围还是[x,y)

归并就是先分治排序最小范围的序列

然后归并,归并紫书上有个图,思路很清晰

#include<bits/stdc++.h>
using namespace std;
void merge_sort(int *A,int x,int y,int *T){
    if(y-x>1){
        int m=x+(y-x)/2;
        int p=x,q=m,i=x;
        merge_sort(A,x,m,T);
        merge_sort(A,m,y,T);
        while(p<m||q<y){
            if(q>=y||(p<m&&A[p]<=A[q])) T[i++]=A[p++];
            else T[i++]=A[q++];
        }
        for(i=x;i<y;i++) A[i]=T[i];
    }
}
int main()
{
    int A[]={0,2,1};
    int T[100];
    merge_sort(A,0,3,T);
    for(int i=0;i<3;i++) printf("%d ",A[i]);

    return 0;
}

逆序对问题

同样的分治归并法,利用归并排序从m往前扫的特点,去数逆序对数

因为序列会排上序,所以直接加(m-p)即可

#include<bits/stdc++.h>
using namespace std;
int cnt=0;
void merge_sort(int *A,int x,int y,int *T){

    if(y-x>1){
        int m=x+(y-x)/2;
        int p=x,q=m,i=x;
        merge_sort(A,x,m,T);
        merge_sort(A,m,y,T);
        while(p<m||q<y){
            if(q>=y||(p<m&&A[p]<=A[q])) T[i++]=A[p++];
            else { T[i++]=A[q++]; cnt+=m-p; }
        }
        for(int i=x;i<y;i++) A[i]=T[i];
    }
}
int main()
{
    int A[]={0,2,1,-1,-3};
    int T[100];
    merge_sort(A,0,5,T);
    printf("%d\n",cnt);

    return 0;
}

快速排序:

从起始点选定一个点,从后往前扫,再从前往后扫

再选第二个点,知道选到最后

#include<bits/stdc++.h>
using namespace std;
void quickSort(int a[],int left,int right)
{
    int i=left;
    int j=right;
    int temp=a[left];
    if(left >= right) return ;
    while(i!=j){
        while(i<j&&a[j]>=temp) j--;
        if(j>i) a[i]=a[j];
        while(i<j&&a[i]<temp) i++;
        if(i<j) a[j]=a[i];
    }
    a[i]=temp;
    quickSort(a,left,i-1);
    quickSort(a,i+1,right);
}
int main()
{
    int A[]={0,2,1,-1,-3};
    int T[100];
    quickSort(A,0,4);
    for(int i=0;i<5;i++)
    printf("%d ",A[i]);

    return 0;
}

快速选择问题

根据快速排序划分后的标记点,判断序列要找的k是否大于或者小于它,然后进行方向性的枚举

#include<bits/stdc++.h>
using namespace std;
int k=2;
int quickSort(int a[],int left,int right)
{
    int i=left;
    int j=right;
    int temp=a[left];
    printf("temp:%d   ** \n",a[left]);
    if(left >= right) return 0;
    while(i!=j){
        while(i<j&&a[j]>=temp) j--;
        if(j>i) a[i]=a[j];
        while(i<j&&a[i]<temp) i++;
        if(i<j) a[j]=a[i];
    }
    for(int i=0;i<5;i++)
    printf("%d ",a[i]); printf("\n");
    a[i]=temp; printf("i = %d\n",i);
    if(k<i)
    quickSort(a,left,i-1);
    else if(k>i)
    quickSort(a,i+1,right);
    else return a[k];
}
int main()
{
    int A[]={0,2,1,-1,-3}; // -3 -1 0 1 2
    int T[100];
    k=0;
    printf("%d\n",quickSort(A,0,4));

    return 0;
}

二分查找折半查找

迭代实现

#include<bits/stdc++.h>
using namespace std;
int bsearch(int *A,int x,int y,int v){
    int m;
    while(x<y){
        m=x+(y-x)/2;
        if(A[m]==v) return m;
        else if(A[m]>v) y=m;
        else x=m+1;
    }
    return -1;
}
int main()
{
    int A[]={0,2,1,-1,-3}; // -3 -1 0 1 2
    int T[100];
    sort(A,A+5);
    printf("%d\n",bsearch(A,0,5,-3));

    return 0;
}

二分查找求上下界

STL中也有这两个函数可以直接用

#include<bits/stdc++.h>
using namespace std;
int lower_bound(int *A,int x,int y,int v){
    int m;
    while(x<y){
        m=x+(y-x)/2;
        if(A[m]>=v) y=m;
        else x=m+1;
    }
    return x;
}
int upper_bound(int *A,int x,int y,int v){
    int m;
    while(x<y){
        m=x+(y-x)/2;
        if(A[m]<=v) x=m+1;
        else y=m;
    }
    return y;
}
int main()
{
    int A[]={0,2,1,-1,-3,2}; // -3 -1 0 1 2
    int T[100];
    sort(A,A+5);
    printf("%d\n",lower_bound(A,0,5,2));
    printf("%d\n",upper_bound(A,0,5,2));

    return 0;
}

棋牌覆盖问题

递归分治解决,边界为k==1

#include<bits/stdc++.h>
using namespace std;
int cnt=0;
int merge_qipan(int k)
{
    if(k==1)  return ++cnt;
    merge_qipan(k-1);
    merge_qipan(k-1);
    merge_qipan(k-1);
    merge_qipan(k-1);
    return ++cnt;
}
int main()
{
    int k;
    scanf("%d",&k);
    printf("%d\n",merge_qipan(k));

    return 0;
}

循环日程表问题:出不来

巨人与鬼:暂时忽略

贪心法:

最优装载问题:纯贪心

部分背包问题:按比值贪心

区间相关问题:选择不相交区间,按b排序

第一种:a1>a2 选择a1 然后:a1<a2<a3从前面不相交的往后选

区间选点问题:按b排序,第一个区间取最后一个点

区间覆盖问题:

预处理:

先切掉[s,t]外的部分

按a排序,起点非s,无解

否则选择起点为s的最长区间

然后新的起点为上一个区间的终点

Huffman编码:

先排序把字符排成表p,创建新节点队列,每次两两合并

构造法:

例题8-1:UVA120煎饼

书上给的分析很明白,其实就是让去找一个可行解,但不知道是不是最优

这种就叫构造算法吧,每次拍一次最大的,最大的排号就不用管了

问题是用数组实现起来真的很麻烦,看到一个666的题解,双端队列,还有reverse翻转,注意翻转是有顺序的。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    for(string strLine;getline(cin,strLine);cout<<'0'<<endl){
        cout<<strLine<<endl;
        istringstream iss(strLine);
        deque<int> Stack;
        for(int nDiam;iss>>nDiam;Stack.push_front(nDiam)) ;
        for(deque<int>::iterator i=Stack.begin();i!=Stack.end();i++){
            deque<int>::iterator iMax = max_element(i,Stack.end());
            if(iMax != i) {
                if(iMax != Stack.end()-1){
                    reverse(iMax,Stack.end());
                    cout<<distance(Stack.begin(),iMax) + 1<<' ';
                }
                reverse(i,Stack.end());
                cout<<distance(Stack.begin(),i)+1<<' ';
            }
        }
    }
    return 0;
}

例题8-2联合国大厦UVa1605

也是按紫书给的思路,共两层,第一层跟i一样放国家,下一层按列,这样每个第一层的国家会和第二层而且是每一个国家都挨着

注意国家只能用大小写字母

#include<bits/stdc++.h>
using namespace std;
int a[60][60];
int b[60][60];
int main()
{
    int n;
    while(~scanf("%d",&n)){
        printf("%d %d %d\n",2,n,n);
        for(int i=0;i<n;i++)
        for(int j=0;j<n;j++){
            a[i][j]=i;
            b[i][j]=j;
        }
        for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            if(a[i][j]>25) printf("%c",a[i][j]-26+'a');
            else
            printf("%c",a[i][j]+'A');
        }
        printf("\n");
        }
        printf("\n");
        for(int i=0;i<n;i++){

        for(int j=0;j<n;j++){
             if(b[i][j]>25) printf("%c",b[i][j]-26+'a');
            else
            printf("%c",b[i][j]+'A');

        }
        printf("\n");
        }
    printf("\n");
    }
    return 0;
}

中途相遇法:从两个方向解决问题,最后汇总

8-3 UVa1152 和为0的四个值

这个题的Hash写的很精妙,好好体会

#include<bits/stdc++.h>
using namespace std;
struct HashMAP{
    static const int mask = 0x7fffff;
    int p[8388608],q[8388608];
    void Clear()
    {
        for(int i=0;i<=mask; ++ i) q[i]=0;
    }
    int & operator [] (int k){
        int i;
        for(i=k&mask; q[i]&&p[i]!=k;i=(i+1)&mask) ;
            p[i]=k;
        return q[i];
    }
}Hash;
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        Hash.Clear();
        int n,sum=0,a[4100],b[4100],c[4100],d[4100];
        scanf("%d",&n);
        for(int i=0;i<n;i++) scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
            Hash[a[i]+b[j]]++;
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
            sum+=Hash[-(c[i]+d[j])];
        printf("%d\n",sum);
        if(t) printf("\n");
    }
    return 0;
}

问题分解:

例题8-4 传说中的车 UVa11134

和前面讲的区间相关问题类似,首先把图分为两个一维轴,然后根据给定的区间,按b排序,从前面找i的位置,找到便正确

#include<bits/stdc++.h>
using namespace std;
struct pp {
    int x1,x2,y1,y2,x,y,num;
}B[5050];
bool cmp1(pp a,pp b) { if(a.x2==b.x2) return a.x1<b.x1; else return a.x2<b.x2; }
bool cmp2(pp a,pp b) { if(a.y2==b.y2) return a.y1<b.y1; else return a.y2<b.y2; }
bool cmp3(pp a,pp b) { return a.num<b.num; }
int main()
{
    int n;
    while(scanf("%d",&n)&&n){
        for(int i=0;i<n;i++) { scanf("%d%d%d%d",&B[i].x1,&B[i].y1,&B[i].x2,&B[i].y2); B[i].x=0; B[i].y=0; B[i].num=i+1; }
        sort(B,B+n,cmp1);
//        for(int i=0;i<n;i++) printf("%d %d %d %d\n",B[i].x1,B[i].y1,B[i].x2,B[i].y2);
        int AA=0,BB=0;
        for(int i=1;i<=n;i++)
        for(int j=0;j<n;j++) {
            if(B[j].x1<=i &&B[j].x2>=i &&!B[j].x)
            {
                B[j].x=i;  AA++; break;
            }
        }
        sort(B,B+n,cmp2);
//        for(int i=0;i<n;i++) printf("%d %d %d %d\n",B[i].x1,B[i].y1,B[i].x2,B[i].y2);

        for(int i=1;i<=n;i++)
        for(int j=0;j<n;j++) {
            if(B[j].y1<=i &&B[j].y2>=i &&!B[j].y)
            {
                B[j].y=i;  BB++; break;
            }
        }
        if(AA==n&&BB==n){
            sort(B,B+n,cmp3); for(int i=0;i<n;i++) printf("%d %d\n",B[i].x,B[i].y);
        } else printf("IMPOSSIBLE\n");
    }
    return 0;
}

等价转换

例题8-5 UVa11054酒的交易

等价转换就是每一次都只考虑a2家酒庄需要多少劳力给a1家酒庄送酒,结果便是最优

#include<bits/stdc++.h>
using namespace std;int main(){
    int n;while(cin>>n&&n){
        int ans=0,a,last=0;
        for(int i=0;i<n;i++)
        {cin>>a;ans+=abs(last);last+=a; }
        cout<<ans<<endl;}
return 0;}

扫描法:扫面法再枚举时维护一些重要的量,简化计算

例题8-6 两性亲子 UVa1606

极角排序的这个题并不会

#include<bits/stdc++.h>
using namespace std;
struct Node{
public :
    int x,y,z;
    double rad;
    bool operator < (const Node& rhs) const {
        return rad < rhs.rad;
    }
}c[1000],d[1000];
bool Turn(Node&a,Node&b)
{
    return a.x*b.y>=a.y*b.x;
}
int main()
{
    int N;
    while(~scanf("%d",&N)&&N){
        for(int i=0;i<N;i++) scanf("%d%d%d",&c[i].x,&c[i].y,&c[i].z);
        if(N<=2) { printf("%d\n",N); continue; }
        int maxn=0;
        for(int i=0;i<N;i++){
            int k=0;
            for(int j=0;j<N;j++){
                if(i!=j){
                    d[k].x = c[j].x-c[i].x;
                    d[k].y = c[j].y-c[i].y;
                    if(c[j].z){
                        d[k].x = -d[k].x;
                        d[k].y = -d[k].y;
                    }
                    d[k].rad = atan2(d[k].y,d[k].x);
                    k++;
                }
            }
            sort(d,d+k);
            int L=0;int ans=1;int R=0;
            while(L<k){
                if(L==R){
                    R=(R+1)%k; ans++;
                }
                while(L!=R&&Turn(d[L],d[R])){
                    ans++; R=(R+1)%k;
                }
                maxn = max(maxn,ans); L++; ans--;
            }
        }
        printf("%d\n",maxn);

    }
    return 0;
}

窗口滑动法

例题8-7 唯一的雪花 UVa11572

其实就是一个循环便利,只不过根据题意如果遇到与当前序列重复的数,就需要从前面找到这个重复的数然后删掉

每次都记录最大的序列长度,便利一遍,即得结果,下面两个实现,一个set,一个map

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int B[maxn];
int main()
{
    int t,N;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&N);
        for(int i=0;i<N;i++) scanf("%d",&B[i]);
        set<int> s;
        int L=0,R=0,ans=0;
        while(R<N){
            while(R<N&&!s.count(B[R])) { s.insert(B[R]); R++; }
            ans=max(ans,R-L);
            s.erase(B[L++]);
        }
        printf("%d\n",ans);
    }
    return 0;
}

用map算出上一次相同数的位置last数组

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int A[maxn],last[maxn];
map<int,int > cur;
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        cur.clear();
        for(int i=0;i<n;i++){
            scanf("%d",&A[i]);
            if(!cur.count(A[i])) last[i]=-1;
            else last[i]=cur[ A[i] ];
            cur[ A[i] ]=i;
        }
        int L=0,R=0,ans=0;
        while(R<n){
            while(R<n&&last[R]<L) R++;
            ans=max(ans,R-L);
            L++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

使用数据结构:

再不用改变主算法的情况下,提高运行效率

求f(i)i到k的最小值,要求f(1)...f(n-k+1)

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-09 20:29:34

第八章 高效算法设计的相关文章

UVa 1210 (高效算法设计) Sum of Consecutive Prime Numbers

题意: 给出n,求把n写成若干个连续素数之和的方案数. 分析: 这道题非常类似大白书P48的例21,上面详细讲了如何从一个O(n3)的算法优化到O(n2)再到O(nlogn),最后到O(n)的神一般的优化. 首先筛出10000以内的素数,放到一个数组中,然后求出素数的前缀和B.这样第i个素数一直累加到第j个素数,就可表示为Bj - Bi-1 枚举连续子序列的右端点j,我们要找到Bj - Bi-1 = n,也就是找到Bi-1 = Bj - n. 因为Bj是递增的,所以Bi-1也是递增的,所以我们就

高效算法设计举例

摘自算法<竞赛入门经典训练指南> 例题 年龄排序(Age Sort,UVa 11462) 给定若干居民的年龄(都是1~100之间的整数),把它们按照从小到大的顺序输出. [输入] 输入包含多组测试用例.每组数据的第一行为整数n(0<n<=2 000 000),即居民总数:下一行包含n个不小于1.不大于100的整数,即各居民的年龄.输入结束标志为n=0. 输入文件约有25mb,而内存限制只有2mb. [输出] 对于每组数据,按照从小到大的顺序输出各居民的年龄,相邻年龄用单个空格隔开.

集训第四周(高效算法设计)O题 (构造题)

A permutation on the integers from 1 to n is, simply put, a particular rearrangement of these integers. Your task is to generate a given permutation from the initial arrangement 1, 2, 3, . . . , n using only two simple operations. •  Operation 1: You

集训第四周(高效算法设计)A题 Ultra-QuickSort

原题poj 2299:http://poj.org/problem?id=2299 题意,给你一个数组,去统计它们的逆序数,由于题目中说道数组最长可达五十万,那么O(n^2)的排序算法就不要再想了,接下来的选择是快排,归并,看你喜欢了 这里列出归并的解法: #include"iostream" using namespace std; const int maxn=500000+10; int T[maxn]; int a[maxn]; long long sum; void merg

集训第四周(高效算法设计)N题 (二分查找优化题)

原题:poj3061 题意:给你一个数s,再给出一个数组,要求你从中选出m个连续的数,m越小越好,且这m个数之和不小于s 这是一个二分查找优化题,那么区间是什么呢?当然是从1到数组长度了.比如数组长度为10,你先找5,去枚举每一个区间为5的连续的数,发现存在这样的数,那么就可以继续往左找,反之则往右找,直到左右区间重合,即为正确答案,(有可能是右区间小于左区间,所以每次都应该求区间中点值) #include"iostream" #include"set" #incl

集训第四周(高效算法设计)M题 (扫描法)

原题:UVA11078 题意:给你一个数组,设a[],求一个m=a[i]-a[j],m越大越好,而且i必须小于j 怎么求?排序?要求i小于j呢.枚举?只能说超时无上限.所以遍历一遍数组,设第一个被减数为a[0],之后遇到比a[0]大的数就更新它,再拿这个被减数去减数组中的每一个元素,同时也要不断地更新这个m值. #include"iostream" #include"set" #include"cstring" #include"cst

集训第四周(高效算法设计)L题 (背包贪心)

Description John Doe is a famous DJ and, therefore, has the problem of optimizing the placement of songs on his tapes. For a given tape and for each song on that tape John knows the length of the song and the frequency of playing that song. His probl

(高效算法设计)之高维问题 废料堆 Garbage heap Uva 10755

#include <iostream> #include <algorithm> #define FOR(i,s,p) for(int i=(s);i<=(p);i++) using namespace std; void expand(char i, bool b[]){ b[0] = i & 1; i >>= 1; b[1] = i & 1; i >>= 1; b[2] = i & 1; } // 这里使用了二项式中的思想,

集训第四周(高效算法设计)P题 (构造题)

Description There are N<tex2html_verbatim_mark> marbles, which are labeled 1, 2,..., N<tex2html_verbatim_mark> . The N<tex2html_verbatim_mark> marbles are put in a circular track in an arbitrary order. In the top part of the track there