zoukankan      html  css  js  c++  java
  • 7.31 基本算法1.2

     前缀和与差分

    介绍:前缀和与差分是一种互补的状态,给定序列A,设其差分序列为B,则序列A的差分序列B的前缀和序列就是原序列A,A的前缀和序列S的差分序列也是原序列A。

                  关于一维前缀和,二维前缀和,差分的知识点总结参考:https://blog.csdn.net/K_R_forever/article/details/81775899

     

    例题:

    poj3263 Tallest cow(区间修改,差分数组,前缀和)

     1 #include<iostream>
     2 #include<algorithm>
     3 #include<map>
     4 using namespace std;
     5 const int maxn = 10010;
     6 
     7 map<pair<int,int>, bool>existed;
     8 int c[maxn], d[maxn];
     9 
    10 int main(){
    11     int n, p, h, m;
    12     cin>>n>>p>>h>>m;
    13     for(int i = 1; i <= m; i++){
    14         int a, b;  cin>>a>>b;
    15         if(a > b)swap(a, b);
    16         if(existed[make_pair(a,b)])continue;
    17         d[a+1]--;  d[b]++;
    18         existed[make_pair(a,b)] = 1;
    19     }
    20     for(int i = 1; i <= n; i++){
    21         c[i] = c[i-1]+d[i];
    22         cout<<h+c[i]<<'
    ';
    23     }
    24     return 0;
    25 }
    View Code

    思路:求每头牛的相对大小关系,可以想到运用前缀和与差分这一知识点。假设C[p]=0,表示最高的牛为基准且为0,则其他C[i]表示第i头牛与最高牛的身高差距,则i牛的身高为C[i]+H。又因为时间复杂度问题,重复对C[i]数组的操作时间复杂度更高,所以新建立一个D数组为其差分数组,则D的前缀和序列即为C。注意输入区间如果重复则筛去。

     

    BZOJ1218(二维前缀和)

     

     1 #include<set>
     2 #include<map>
     3 #include<cstdio>
     4 #include<cmath>
     5 #include<queue>
     6 #include<vector>
     7 #include<string>
     8 #include<cstring>
     9 #include<iostream>
    10 #include<algorithm>
    11 using namespace std;
    12 typedef long long ll;
    13 const int inf = 0x3f3f3f3f;
    14 const int maxn = 5e3 + 10;
    15 int m[maxn][maxn];
    16 int main()
    17 {
    18     int n, r;
    19     scanf("%d%d", &n, &r);
    20     memset(m, 0, sizeof(m));
    21     while(n--)
    22     {
    23         int x, y, v;
    24         scanf("%d%d%d", &x, &y, &v);
    25         m[x + 1][y + 1] = v;
    26     }
    27     for(int i = 1; i <= 5001; ++i)
    28         for(int j = 1; j <= 5001; ++j)
    29             m[i][j] += m[i][j - 1] + m[i - 1][j] - m[i - 1][j - 1];
    30     int ans = 0;
    31     for(int i = r; i <= 5001; ++i)
    32     {
    33         for(int j = r; j <= 5001; ++j)
    34         {
    35             int x = i - r, y = j - r;
    36             ans = max(ans, m[i][j] - m[i][y] - m[x][j] + m[x][y]);
    37         }
    38     }
    39     printf("%d
    ", ans);
    40     return 0;
    41 }
    View Code

     

     

    思路:

    看到块状炸弹式膨胀题型,求某一区域内被计算的个数等,可以考虑矩形二维前缀和,差分方法

     

    二分

    介绍:  

    1.1.最大值最小化:

        //最大值最小化 
        while(l < r){
            int mid = (l + r) >> 1;
            if(a[mid] >= x)  r = mid;   //有可能mid就是答案 
            else  l = mid+1;
        } 

    1.2.最小值最大化:

        //最小值最大化
        while(l < r){
            int mid = (l + r + 1) >> 1;
            if(a[mid] >= x)  l = mid;   //有可能mid就是答案 
            else  r = mid - 1;
        } 

     

    2.实数域上的二分:

        while(l + 1e-5 < r){
            double mid = (l + r) / 2;
            if(calc(mid))  r = mid;
            else l = mid;
        }

    3.三分求单峰函数极值

    4.二分答案转化为判定:把求最优解问题,转化为给定一个值mid,判定是否存在一个可行方案(judge函数)达到mid的问题

     

    例题:

    POJ 2018(二分查询标记)

     

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cstring>
     5 using namespace std;
     6 double a[100005],b[100005],sum[100005];
     7 
     8 int main(){
     9     int n,L;
    10     cin>>n>>L;
    11     for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
    12     double eps=1e-5;
    13     double l=-1e6,r=1e6;
    14     while(r-l>eps){
    15         double mid=(r+l)/2;
    16         for(int i=1;i<=n;i++) b[i]=a[i]-mid;
    17         for(int i=1;i<=n;i++) sum[i]=sum[i-1]+b[i];
    18         double ans=-1e10,minval=1e10;
    19         for(int i=L;i<=n;i++){
    20             minval=min(minval,sum[i-L]);
    21             ans=max(ans,sum[i]-minval);
    22         }
    23         if(ans>=0) l=mid; else r=mid;
    24     }
    25     cout<<int(r*1000)<<endl;
    26     return 0;
    27 }
    View Code

     

    思路:求一个子段,他的和最大,且字段长度不小于L(将数列中的每个数都减去二分的值,通过前缀和判断其其字段和非负)

     

     

    POJ 1050(矩阵前缀和)

     

     1 #include<iostream>
     2 #include<cstdio> 
     3 #define ll long long 
     4 #define fo(i,j,n) for(register int i=j; i<=n; ++i)
     5 using namespace std;
     6 int a[105][105],n;
     7 int sum[105][105];
     8 // 矩阵前缀和
     9 void init(){
    10     fo(i,1,n){
    11         fo(j,1,n){
    12             sum[i][j] = sum[i-1][j] + sum[i][j-1] + a[i][j] - sum[i-1][j-1];
    13         }
    14     }
    15 }
    16 // 查询矩阵和## 标题
    17 int query(int x1, int y1, int x2, int y2){
    18     return sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1];
    19 }
    20 void solve(){
    21     int MAX = -1e8;
    22     fo(x1,1,n){
    23         fo(y1,1,n){
    24             fo(x2,x1,n){
    25                 fo(y2,y1,n){
    26                     int t = query(x1,y1,x2,y2);
    27                     if(t>MAX) {
    28                         MAX = t;
    29                     //    cout<<x1<<" " <<y1<<" " <<x2<<" " <<y2<<endl;
    30                     }
    31                 }
    32             }
    33         }
    34     }
    35     printf("%d
    ",MAX);
    36 }
    37 int main(){
    38     scanf("%d",&n);
    39     fo(i,1,n){
    40         fo(j,1,n){
    41             scanf("%d",&a[i][j]);
    42         }
    43     }
    44     init();
    45     solve();
    46     return 0;
    47 }
    View Code

    思考:本题还可以用贪心来优化算法

              当字段区间连续序列和sum小于0时sum归零,是上一道题的变形题,但是没有区间长度大于T的要求限制,即求一个子段,他的和最大

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #define ll long long
     5 #define fo(i,j,n) for(register int i=j; i<=n; ++i)
     6 using namespace std;
     7 const int maxn = 1e4;
     8 int mp[maxn][maxn],n;
     9 int d[maxn];
    10 void solve(){
    11     int ans = mp[1][1];
    12     fo(i,1,n){ // i行 
    13         memset(d,0,sizeof(d)); 
    14         fo(j,i,n){ // j行 
    15             fo(k,1,n){ // k列 
    16                 d[k] += mp[j][k]; // d[k] 表示第k列  第i~j行的和(区间和) 
    17             }
    18             
    19             // 接下来的做法就类似一维数组求最大连续序列和 
    20             int sum = 0;
    21             fo(k,1,n){
    22                 sum += d[k];
    23                 ans = max(ans,sum);
    24                 if(sum<0) sum=0;
    25             } 
    26         }
    27     }
    28     printf("%d
    ", ans);
    29 } 
    30 int main(){
    31     while(scanf("%d",&n)==1){ // 使程序更加具有鲁棒性 
    32         fo(i,1,n){
    33             fo(j,1,n)
    34                 scanf("%d",&mp[i][j]);
    35         }
    36         solve();
    37     }
    38     return 0;
    39 }
    View Code

    AcWing113

    倍增

    介绍:

    如果状态空间很大,可以通过成倍增长的方式,只递推状态空间中在2的整数次幂位置上的值作为代表,当需要其他位置上的值时,通过“任意整数可以表示成若干个2的次幂项的和”这一性质,使用之前的代表值拼成所需的值。

    倍增常与二进制划分两个思想互相结合,包括求解RMQ(区间最值)和ST算法(看书)。

    例题:

    ch601 genius acm(倍增 + 归并排序)

    #include <iostream>
    #include <set>
    #include <cmath>
    #include <stdio.h>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long LL;
    
    LL m, n, t, k;
    const int maxn = 5e5 + 5;
    LL s[maxn], b[maxn], c[maxn];
    
    bool check(LL l, LL r, LL lr)
    {
        LL tot = 0;
        int t = l, len = r - l + 1;
        for(LL i = lr; i <= r; i++){
            b[i] = s[i];
        }
        sort(b + lr, b + r + 1);
        if(l == lr){
            for(int i = l; l <= r; i++){
                c[i] = b[i];
            }
        }
        else{
            int i = l, j = lr;
            while(i < lr && j <= r){
                if(b[i] <= b[j]){
                    c[t++] = b[i++];
                }
                else{
                    c[t++] = b[j++];
                }
            }
            while(i < lr)c[t++] = b[i++];
            while(j <= r)c[t++] = b[j++];
        }
        LL jiaoyan = 0;
        for(LL i = l; i <= min(l + m - 1, l + (r - l + 1) / 2 - 1); i++){
            jiaoyan += (c[r - i + l] - c[i]) * (c[r - i + l] - c[i]);
        }
        if(jiaoyan <= k){
            for(int i = l; i <= r; i++){
                b[i] = c[i];
            }
            return 1;
        }
        return 0;
    }
    
    int main()
    {
        scanf("%lld", &t);
        while(t--){
            scanf("%lld%lld%lld", &n, &m, &k);
            for(LL i = 1; i <= n; i++){
                scanf("%lld", &s[i]);
            }
    
            memset(b, 0, sizeof(b));
            memset(c, 0, sizeof(c));
            LL sum = 0, l = 1, r = 1, p = 1;
            b[1] = s[1];
            while(r < n){
                if(!p){
                    sum++;
                    p = 1;r++;
                    l = r;
                    b[l] = s[l];
                    continue;
                }
                if(p){
                    if(r + p <= n && check(l, r + p, r + 1)){
                        r += p;
                        p *= 2;
                        if(r == n)break;
                    }
                    else{
                        p /= 2;
                    }
                }
                //cout<<sum<<endl;
    
            }
            if(r == n)sum++;
    
            printf("%lld
    ", sum);
        }
    }
    View Code

    贪心(策略+寻找最优解)

     

    例题:

    poj3614 Sunscreen                                                                         

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define _rep(i,a,b) for(int i=(a);i<=(b);i++)
    const int N=2510; 
    int c,l,ans;
    struct node{
        int minspf,maxspf;
        bool operator <(const node&rhs)const{
        return minspf>rhs.minspf;}
    }cow[N];
    struct NODE{
        int spf,cover;
        bool operator <(const NODE&rhs)const{
        return spf>rhs.spf;}
    }fss[N];
    int main()
    {
        //freopen("in.txt","r",stdin);
        scanf("%d%d",&c,&l);
        _rep(i,1,c)scanf("%d%d",&cow[i].minspf,&cow[i].maxspf);
        sort(cow+1,cow+1+c);
        _rep(i,1,l)scanf("%d%d",&fss[i].spf,&fss[i].cover);
        sort(fss+1,fss+l+1);
        _rep(i,1,c)
        {
            _rep(j,1,l)
            {
                if(fss[j].cover>0&&fss[j].spf<=cow[i].maxspf&&fss[j].spf>=cow[i].minspf)
                {
                    fss[j].cover--;
                    ans++;
                    break;
                }
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    poj3190 stall reservation

     

    #include"iostream"
    #include"stdio.h"
    #include"queue"
    #include"algorithm"
    using namespace std;
    const int MAXN=50005;
     
    struct Node
    {
        int s,e,pos;
        friend bool operator<(Node a,Node b)
        {
            if(a.e==b.e) return a.s<b.s;
            return a.e>b.e;
        }
    };
     
    bool Cmp(Node a,Node b)
    {
        if(a.s!=b.s)
            return a.s<b.s;
        else
            return a.e<b.e;
    }
     
    Node cows[MAXN];
    priority_queue<Node>que;
    int order[MAXN];
    int main()
    {
        int n;
        while(scanf("%d",&n)==1)
        {
            for(int i=0;i<n;i++)
            {
                scanf("%d%d",&cows[i].s,&cows[i].e);
                cows[i].pos=i;
            }
            sort(cows,cows+n,Cmp);
            que.push(cows[0]);
            order[cows[0].pos]=1;
            for(int i=1;i<n;i++)
            {
                if(cows[i].s>que.top().e)
                {
                    order[cows[i].pos]=order[que.top().pos];
                    que.pop();
                    que.push(cows[i]);
                }
                else
                {
                    que.push(cows[i]);
                    order[cows[i].pos]=que.size();
                }
            }
            cout<<que.size()<<endl;
            for(int i=0;i<n;i++)
            {
                cout<<order[i]<<endl;
            }
        }
        return 0;
    }
    View Code

    思路:区间贪心。把尽量多的牛放在一个棚子里,这样就可以使得用到的棚子数最少。只要任意两头牛的挤奶时间不重合,就可以放在一个棚子里。因为N值比较大,先标记好每一头牛的起始坐标,然后按起始时间从小到大进行排序,然后依次从前往后去遍历每一头牛。这里需要用一个优先队列来维护,以结束时间从小到大来维护,每次将一头牛与队列首的牛进行比较,如果满足它的起始时间大于队列首的那头牛的结束时间,这两头牛就可以使用同一个机器,否则就新加一个机器。这里需要注意牛的入队和出队

     

    poj1328  radar installation

    题意:地图的x轴的上方为海,下方为陆地,海中有n个小岛,坐标为(isl[i].x,isl[i].y)。有一种雷达,能探测到的范围为以d为半径的圆。问海岸线上至少造多少雷达可以把所有的小岛都包含在内。注意雷达是建在海岸线上的,也就是x轴上的。

     1     #include<iostream>  
     2     #include<math.h>  
     3     #include<algorithm>  
     4     using namespace std;  
     5     const int Max = 1005;  
     6       
     7     struct  
     8     {  
     9         int x, y;  
    10     }isl[Max];    //  小岛的数据。  
    11       
    12     struct data  
    13     {  
    14         float sta, end;  
    15     }rad[Max];    //  小岛所对应雷达的数据。  
    16       
    17     bool cmp(data a, data b)  
    18     {  
    19         if(a.end < b.end)   
    20             return true;  
    21         else   
    22             return false;  
    23     }  
    24       
    25     int main()  
    26     {  
    27         int n, d, t = 1;  
    28         while(cin >> n >> d && n != 0)  
    29         {  
    30             int i, j, max_y = 0;  
    31             for(i = 0; i < n; i ++)  
    32             {  
    33                 cin >> isl[i].x >> isl[i].y;  
    34                 if(isl[i].y > max_y)  
    35                     max_y = isl[i].y;  
    36             }  
    37             getchar();    
    38             getchar();  //  PE了两次。  
    39               
    40             cout << "Case " << t ++ << ": ";  
    41             if(max_y > d || d < 0)  
    42             {  
    43                 cout << -1 << endl;  
    44                 continue;  
    45             }  
    46             float len;  
    47             for(i = 0; i < n; i ++)  
    48             {   //  求出小岛所对应雷达的可能覆盖范围。  
    49                 len = sqrt(1.0 * d * d - isl[i].y * isl[i].y);  
    50                 rad[i].sta = isl[i].x - len;  
    51                 rad[i].end = isl[i].x + len;  
    52             }  
    53             sort(rad, rad + n, cmp);   //  根据rad的end值进行排序。  
    54               
    55             int ans = 0;  
    56             bool vis[Max];  
    57             memset(vis, false, sizeof(vis));  
    58             for(i = 0; i < n; i ++)  
    59             {   //  类似的活动选择。  
    60                 if(!vis[i])  
    61                 {  
    62                     vis[i] = true;  
    63                     for(j = 0; j < n; j ++)  
    64                         if(!vis[j] && rad[j].sta <= rad[i].end)  
    65                             vis[j] = true;  
    66                         ans ++;  
    67                 }  
    68             }  
    69             cout << ans << endl;  
    70         }  
    71         return 0;  
    72     }  
    View Code

    思路:贪心,从左到右建立雷达,要尽量多地覆盖岛屿。以岛屿为圆心,以d为半径画圆,如果画出的圆与x轴没有交点,则不能实现。存在交点的话,计算出第i个岛屿与x轴的交点坐标保存在结构体数组rad[i].sta与rad[i].end中。对结构体数组排序,按照rad[i].end进行升序排列,然后一次从左到右找雷达。对于rad[i].end为当前最右边的左坐标,对于下一个岛屿,如果rad[j].sta<ran[i].end,则不需要建立新的雷达,需要更新下一个岛屿。否则需要建立新的雷达。

    noip2012 国王游戏(洛谷P1080)

     

    poj2054  color a tree

    参考:https://www.cnblogs.com/yu-chao/archive/2012/02/19/2358565.html

     

     

     

     

     

     

     

  • 相关阅读:
    C++STL——vector
    大数常用计算模板及例题
    在线算法&离线算法
    线段树——hdu1166敌兵布阵
    C++STL——堆栈
    C++STL——优先队列
    C++STL——队列
    图的建立——图的两种存储结构
    Manacher算法——最长回文子串
    HttpClient的几个实现类
  • 原文地址:https://www.cnblogs.com/zyddd915/p/11281733.html
Copyright © 2011-2022 走看看