前缀和&差分&位运算&Hash函数

——最大数

题意:给定一个由整数数组 A 表示的环形数组 C,求 C 的非空子数组的最大可能和。在此处,环形数组意味着数组的末端将会与开头相连呈环状。此外,子数组最多只能包含固定缓冲区 A 中的每个元素一次。

思路:其实这道题和一般的题最大的区别就是:环状,第一种方式是把其模拟成环状,即是说把原数组a1 a2 a3 a4 ……an → a1 a2 a3 a4 …… an a1 a2 ……a(n - 1),然后使用前缀和,但是时间复杂度和空间复杂度趋势不太,一旦数据大起来就要wa;第二种方式是转换思维:分两种情况,一种为没有跨越边界的情况,一种为跨越边界的情况 没有跨越边界的情况直接求子数组的最大和即可; 跨越边界的情况可以对数组求和再减去无环的子数组的最小和,即可得到跨越边界情况下的子数组最大和; 求以上两种情况的大值即为结果,另外需要考虑全部为负数的情况。

class Solution {
    public int maxSubarraySumCircular(int[] A) { 
        if (A == null || A.length < 1) {
            return 0;
        }
        int curMax, max, curMin, min, sum;
        curMax  = max = curMin = min = sum = A[0];
        for (int i = 1; i < A.length; i++) {
            sum += A[i];// 计算整个序列的长度
            curMax = curMax > 0 ? curMax + A[i] : A[i]; 
            //有点类似与dp,遍历整个串找出最大的值
            //如果当前的加上后面的数更大,那就更新,否则当前最大的数就是后面这个数
            //反正就是很巧妙!!!
            max = curMax > max ? curMax : max;
            curMin = curMin < 0 ? curMin + A[i] : A[i];
            min = curMin < min ? curMin : min;
        }
        if (max < 0)
            return max;
        return Math.max(sum - min, max);
    }
}

A - Stripe

题意:输入一个长度为n的数组,问有多少种可能让整个数组分为非空的两部分?

思路:很明显前缀和。这道题有一个很巧妙的点就是没有多开数组,但是要注意在做前缀和的时候一般下标从1开始,然后下标为0的点记得赋值为0,直接甩代码~

#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 1e5 + 10;
typedef long long ll;
using namespace std;
ll n, a[N];
int main()
{
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> n;
    ll sum = 0;
    for( ll i = 1; i < n + 1; i++ ){
        cin >> a[i];
        sum += a[i];
        a[i] += a[i - 1];//很巧妙,没有多开数组,前提是我使用了全局变量,让a[0] == 0
    }
    ll cnt = 0;
    for( int i = 1; i < n; i++ ){
        if( a[i] == sum - a[i] ){
            ++cnt;
        }
    }
    cout << cnt << endl;
    return 0;
}

B - 校门外的树

题意:某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。 马路上有一些区域要用来建地铁,这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。 输入的第一行有两个整数L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。

输出包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。

思路:这道题本质是其实是对差分的考察,但是由于题目本身的数据量就很小,所以可以直接暴力处理。但是要注意细节就是位置 0 和 L都有点。

#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 1e4 + 10;
using namespace std;
int L, M, l, r, a[N];
int main()//小小的思维吧~
{
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> L >> M;
    while( M-- ){
        cin >> l >> r;
        for( int i = l; i < r + 1; i++ ){
            --a[i];
        }
    }
    int cnt = 0;
    for( int i = 0; i < L + 1; i++ ){
        if( !a[i] ){
            ++cnt;
        }
    }
    cout << cnt << endl;
    return 0;
}

C - Love Song

 题意:输入一段长度为n的字符串,有q次询问,每次询问给出左右边下标(含),问这段下标内的字符串实际长度是多少?规则是:a:代表有(a - 'a' + 1)个a, b:代表有(b - 'a' + 1)个b

 思路:最开始的思路就是直接暴力,可是那必定T啊,1e5 * 1e5,所以就想到前缀和,其实每种字母就是代表一种数字而已。

#include <iostream>
#include <cstring>
#include <algorithm>
typedef long long ll;
const int N = 1e5 + 10;
using namespace std;
ll n, q, l, r, a[N];
string s;
int main()
{
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> n >> q;
    cin >> s;
    for( int i = 1; i < n + 1; i++ ){
        a[i] = a[i - 1] + ( s[i- 1] - 'a' + 1 );  
    }
    while( q-- ){
        cin >> l >> r;
        cout << a[r] - a[l - 1] << endl;//注意 l- 1的细节
    }

    return 0;
}

D - Color the ball

 题意:N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <= N)。 
当N = 0,输入结束。每个测试实例输出一行,包括N个整数,第I个数代表第I个气球总共被涂色的次数。

思路:这道题暴力铁定超时,这是一道很明显的差分,板题

关于差分:

1、给定一个长度为n的数列a,要求支持操作add(L,R,k)表示对a[L]~a[R]的每个数都加上k。并求修改后的序列a。

2、对于一个数列A,他的差分数列B定义为:B[1] = A[1] ,B[i] = A[i] - A[i-1] ( 2<=i<=n)

3、性质1:差分序列B的前缀和序列就是原序列A,前缀和序列S的差分序列也是原序列A。

4、性质2:把序列A的区间 [l,r] 加上 d(即把 Al,Al+1……Ar 都加上 d),其差分序列 B 的变化为 Bl 加 d,Br+1 减 d,其余位置不变。

 

#include <iostream>
#include <cstring>
#include <algorithm>
typedef long long ll;
const int N = 1e6 + 10;
using namespace std;
ll n, l, r, a[N];
int main()
{
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    while( cin >> n ){
        if( !n ){
            break;
        }
        memset( a, 0, sizeof( a ) );
        ll nt = n;
        while( nt-- ){
            cin >> l >> r;
            ++a[l];//每个序列刚开始都是0,只是不断进行+1操作,所以原数组和差分数组都是0
            --a[r + 1];
        }
        for( int i = 1; i < n + 1; i++ ){
            a[i] += a[i - 1];//应用性质:差分数组的前缀和就是原数组,但是要注意下标从哪里开始
        }
        int y = 1;
        for( int i = 1; i < n + 1; i++ ){
            if( y == 1 ){
                cout << a[i];
            }
            else{
                cout << " " << a[i];
            }
            ++y;
        }
        cout << endl;
    }
    return 0;
}

 

E - Bound Found(到现在还是没有debug出来,后面后时间再看看)

题意:输入一个长度为n的数组,k次询问,每次询问给出一个数num,找出数组中一段连续的数让这些数的和与num的绝对值相差最小。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
typedef long long ll;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
using namespace std;
ll n, q, target;

struct node{
  ll sum = 0, p = 0;  
}a[N];

bool cmp( node a, node b ){
    return a.sum < b.sum;
}

int main()
{
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    while( cin >> n >> q ){
        if( !n && !q ){
            break;
        }
        memset( a, 0, sizeof( a ) );
        for( ll i = 1; i < n + 1; i++ ){
            cin >> a[i].sum;
            a[i].sum += a[i - 1].sum;
            a[i].p = i;
        }
        sort( a + 1, a + 1 + n, cmp );
        while( q-- ){
            cin >> target;
            ll l = 0, r = 1, len = INF, ansl, ansr, ans;
            while( r < n + 1 && l < n + 1 ){
                while( abs( abs( a[r].sum - a[l].sum ) - target ) <  len && r < n + 1 ){
                    ansl = a[l].p;
                    ansr = a[r].p;
                    len = abs( ans - target );
                    ans = abs( a[r].sum - a[l].sum );
                    ++r;
                }
                if( abs( a[r].sum - a[l].sum ) - target == 0 ){
                    break;
                }
                ++l;
                if( l == r ){
                    ++r;
                }
            }
            if( ansl > ansr ){
                swap( ansl, ansr );
            }
            cout << ans << " " << ansl << " " << ansr << endl;
        }
        
    }
    return 0;
}

总结:

其实还学习了位运算和hash,只是这需要其他专项练习但此次没有太涉及到,位运算是个好东西啊简单但功能强大,只是要对底层原理很熟悉。hash确实很难,但是大概了解了一下其本质就是函数映射,而且要避免hash冲突。前缀和以前就学过了,差分确实有收获~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值