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

     

     

     

     

     

     

     

  • 相关阅读:
    51nod 1087 1 10 100 1000(找规律+递推+stl)
    51nod 1082 与7无关的数 (打表预处理)
    51 nod 1080 两个数的平方和
    1015 水仙花数(水题)
    51 nod 1003 阶乘后面0的数量
    51nod 1002 数塔取数问题
    51 nod 1001 数组中和等于K的数对
    51 nod 1081 子段求和
    51nod 1134 最长递增子序列 (O(nlogn)算法)
    51nod 1174 区间中最大的数(RMQ)
  • 原文地址:https://www.cnblogs.com/zyddd915/p/11281733.html
Copyright © 2011-2022 走看看