Day 2 (前缀和 差分 二分 快速幂 逆元)

一.前缀和(一维,二维,三维)

模板

/*
一维前缀和
s[i] = s[i-1] + a[i];
查询 l - r 的和
s[r] - s[l-1] 
*/
/*
二维前缀和
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
查询 (x1,y1) - (x2,y2) 的和
s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
*/

/*
三维前缀和
s[i][j][k] = s[i-1][j][k] + s[i][j-1][k] + s[i][j][k-1]
           - s[i-1][j-1][k] - s[i-1][j][k-1] - s[i][j-1][k-1]
           + s[i-1][j-1][k-1] + a[i][j][k];

查询(x1,y1,z1) - (x2,y2,z2) 的和
s[x2][y2][z2] - s[x1-1][y1-1][z1-1] - s[x1-1][y2][z2] - s[x2][y1-1][z2] - s[x2][y2][z1-1] 
              + s[x1-1][y1-1][z2] + s[x1-1][y2][z1-1] + s[x2][y1-1][z1-1];
*/

例题

Kobolds and Catacombs

思路:能够分开的点 前面的所有数一定小于后面所有数 可以利用前缀和来判断 s表示原数组的前缀和 presum 表示 排成升序的前缀和 当s[i] == presum[i] 时 ans++

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 1000010;
LL a[N];
LL presum[N];
LL s[N];
int ans;

int main()
{
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++)
    {
        cin >> a[i];
        s[i] += s[i-1] + (LL)a[i];
    }
    sort(a + 1,a + n + 1);
    for(int i = 1;i <= n;i++) presum[i] += presum[i-1] + (LL)a[i];
    bool flag = true;
    for(int i = 1;i <= n;i++)
    {
        if(s[i] == presum[i]) ans++;
        else flag = false;
    }
    
    cout << ans << endl;
}

Cuboid Sum Query(三维前缀和 模板)

#include<bits/stdc++.h>
using namespace std;

const int N = 110;
typedef long long LL;
int a[N][N][N];
LL s[N][N][N];

int main()
{
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= n;j++)
            for(int k = 1;k <= n;k++)
                cin >> a[i][j][k];

    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= n;j++)
            for(int k = 1;k <= n;k++)
                s[i][j][k] = s[i-1][j][k] + s[i][j-1][k] + s[i][j][k-1] 
                - s[i-1][j-1][k] - s[i-1][j][k-1] - s[i][j-1][k-1] 
                + s[i-1][j-1][k-1] + (LL)a[i][j][k];
                
        
    int Q;
    cin >> Q;
    while(Q--)
    {
        int x1,x2,y1,y2,z1,z2;
        cin >> x1 >> x2 >> y1 >> y2 >> z1 >> z2;
        LL sum = s[x2][y2][z2] - s[x1-1][y1-1][z1-1] 
        - s[x1-1][y2][z2] - s[x2][y1-1][z2] - s[x2][y2][z1-1] 
        + s[x1-1][y1-1][z2] + s[x1-1][y2][z1-1] + s[x2][y1-1][z1-1] ;
        cout << sum << endl;
    } 
}

二.二分(二分查找,二分答案)

模板

/*
二分模板一

int l = 1,r = le9;
while(l <= r)
{
    int mid = l + r >> 1;
    if(check(mid)) r = mid;
    else l = mid + 1;
}
return r;
*/

/*
二分模板二

int l = 1,r = le9;
while(l <= r)
{
    int mid = l + r + 1 >> 1;
    if(check(mid)) l = mid;
    else r = mid - 1;
}
return l;
*/

例题

MaratonIME gets candies(交互题 猜数字)

#include<bits/stdc++.h>

using namespace std;

char ask(int x)
{
    cout << "Q" << " " << x << endl;
    char res;
    cin >> res;
    return res;
}

int main()
{
    int l = 1,r = 1e9;
    while(l <= r)
    {
        int mid = l + r >> 1;
        char op = ask(mid);
        if(op == '=') break;
        else if( op == '<') r = mid - 1;
        else l = mid + 1;
    }
}

Counting Pairs(二分边界范围)

 

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL N = 200010;
 
LL a[N];
LL n;
LL findl(LL x)//找到大于等于x的最左边的数的下标
{
    LL l = 0,r = n-1;
    while(l < r)
    {
        LL mid = l + r >> 1;
        if(a[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r;
}
 
LL findr(LL x)//找到小于等于x的最右边的数的下标
{
    LL l = 0,r = n - 1;
    while(l < r)
    {
        LL mid = l + r + 1>> 1;
        if(a[mid] <= x) l = mid;
        else r = mid - 1;
    }
    return l;
}
 
void solve()
{
    memset(a,0,sizeof a);//每次初始化一下a数组
    LL x,y;
    cin >> n >> x >> y;
    LL sum = 0;
    for(LL i = 0;i < n;i++)
    {
        cin >> a[i];
        sum += a[i];//求一下总和
    }
    /*
        要求的是 x <= sum - a[i] - a[j] <= y
        那么我们可以枚举每一个a[i] 那么对应的a[j] 就有范围
        sum - a[i] - y <= a[j] <= sum - a[i] - x
        因为我们对元素组排序 所以可以二分数俩个端点
    */
 
 
    sort(a,a+n);//可以发现选的数其实跟位置没关系 因为俩个数 总是前后关系
    LL ans = 0;
    for(LL i = 0;i < n;i++)
    {
        LL res = 0;
        LL left = sum - (LL)a[i] - y;//求出左端点大小
        LL right = sum - (LL)a[i] - x;//求出右端点大小
        LL l = findl(left);//找一下大于等于左端点的最右端的位置
        if(a[l] < left) continue;//如果左端点(也就是a[l] 已经为 数组当中最大值)找不到 跳过
        LL r = findr(right);
 
        if(r <= i) break;//如果右端点下标是小于i的 那么不用再找了 因为右端点值只会越来越小
                        //后面的条件越苛刻 当前条件都满足不了 就不用再继续了
 
        if(l <= i) l = i + 1;//我们只计算在i右边的区间 这样避免了重复计算
        res = r - l + 1;
        ans += res;//累加一下答案
    }
    
    cout << ans << endl;
}
 
int main()
{
    LL T;
    cin >> T;
    while(T--)
    {
        solve();
    }
    return 0;
}

 Largest Allowed Area(二分答案 + 二维前缀和)

#include<bits/stdc++.h>
using namespace std;

const int N = 2020;
int g[N][N];
int s[N][N];
int n,m;
// 不使用快读会TLE
#define Temp template<typename T>
Temp inline void read(T &x)
{
    x=0;T w=1,ch=getchar();
    while(!std::isdigit(ch)&&ch!='-')ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
    x=x*w;
}

bool check(int x)
{
    //枚举左上横坐标 对应得到右下横坐标
    for(int x1 = 1;x1 + x - 1 <= n;x1 ++)
    {
        int x2 = x1 + x - 1;
        //枚举左上纵坐标 对应得到右下纵坐标
        for(int y1 = 1;y1 + x - 1 <= m ;y1 ++)
        {
            int y2 = y1 + x - 1;
            int cnt = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1 - 1][y1 - 1];
            if(cnt <= 1) return true;
        }
    }
    return false;
}

void solve()
{
    cin >> n >> m;
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
        {
            read(g[i][j]);
        }

    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + g[i][j];

    int l = 1,r = min(n,m);//边长最长为min(n,m);
    int ans = 1;
    while(l <= r)
    {
        int mid = l + r >> 1;
        if(check(mid))
        {
            ans = mid;
            l = mid + 1;
        }
        else r = mid - 1;
    }
    cout << ans << endl;

    
}

int main()
{
    int T;
    read(T);
    while(T--)
    {
        memset(g,0,sizeof g);
        memset(s,0,sizeof s);
        solve();
    }
    return 0;
}

Max Median(二分答案)

#include<bits/stdc++.h>
using namespace std;

const int N = 200010;
int a[N],tmp[N];
int n,k;
int cnt[N];


bool check(int mid)
{
    for(int i = 1;i <= n;i++)
    {
        if(a[i] >= mid) cnt[i] = 1;
        else cnt[i] = -1;
    }

    for(int i = 1;i <= n;i++) cnt[i] += cnt[i-1];
    int minv = 0;
    for(int i = k;i <= n;i++)
    {
        if(cnt[i] - minv > 0) return true;
        minv = min(cnt[i - k + 1],minv);
    }
    return false;
}


int main()
{
    cin >> n >> k;
    for(int i = 1;i <= n;i++)
        cin >> a[i],tmp[i] = a[i];
    //将元素组升序排序 二分最大中位数
    sort(tmp + 1,tmp + n + 1);

    int l = 1,r = n;
    while(l < r)
    {
        int mid = l + r + 1 >> 1;
        if(check(tmp[mid])) l = mid;
        else r = mid - 1;
    }
    cout << tmp[l] << endl;
    return 0;
}

三.快速读写

模板

#include<bits/stdc++.h>
#define Temp template<typename T>
//避免为不同的数据类型重复编写相同逻辑的函数或类
using namespace std;
//这样,对于任何类型 T,只要支持拷贝构造和赋值操作,该函数都可以使用,无需为每种类型单独编写函数。
Temp inline int read(T & x)
{
    x = 0;
    T w = 1,ch = getchar();
    while(ch <= '0' && ch >= '9')
    {
        if(ch == '-') w *= -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x = x * w;
}

四.差分

例题

 Karen and Coffee

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 200010;
int d[N];
int cnt[N];
int n,q,k;
int main()
{
    cin >> n >> k >> q;
    for(int i = 0;i < n;i++)
    {
        int l,r;
        cin >> l >> r;
        d[l] ++;
        d[r+1] --;
    }
    for(int i = 1;i <= N;i++) d[i] += d[i-1];
    for(int i = 1;i <= N;i++)
    {
        if(d[i] >= k) cnt[i] = 1;
        cnt[i] += cnt[i-1];
    }
    while(q--)
    {
        int l,r;
        cin >> l >> r;
        cout << cnt[r] - cnt[l-1] << endl;
    }
}

Landscape Generator

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
LL d[N];//对于前俩个操作 的差分数组
LL tmpd[N];//对于后俩个操作的差分数组 (二次差分)
int n,k;

int main()
{
    cin >> n >> k;
    while(k--)
    {
        char op;
        int l,r;
        cin >> op >> l >> r;
        if(op == 'R') d[l] ++,d[r + 1] --;
        else if(op == 'D') d[l] --,d[r + 1] ++;
        else if(op == 'H')
        {
            int mid = l + r >> 1;
            int len = r - l + 1;
            if(len % 2 == 0)//分奇偶讨论
            {
                tmpd[l] ++,tmpd[mid + 1] --;
                tmpd[mid + 2] --,tmpd[r + 2] ++;
            }
            else
            {
                tmpd[l] ++,tmpd[mid + 1] -= 2;
                tmpd[r + 2] ++;
            }
        }
        else if(op == 'V')
        {
            int mid = l + r >> 1;
            int len = r - l + 1;
            if(len % 2 == 0)
            {
                tmpd[l] --,tmpd[mid + 1] ++;
                tmpd[mid + 2] ++,tmpd[r + 2] --;
            }
            else
            {
                tmpd[l] --,tmpd[mid + 1] += 2;
                tmpd[r + 2] --;
            }
        }
    }
    for(int i = 1;i <= n;i++)
    {
        tmpd[i] += tmpd[i-1];
        d[i] += d[i-1] + tmpd[i];
    }

    for(int i = 1;i <= n;i++) cout << d[i] << endl;

}

五.快速幂(模板以及求逆元)

模板

LL qmi(LL a,LL k,LL p)
{
    LL res = 1;
    while(k)
    {
        if(k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % mod;
    }
    retrun res;
}

//求逆元
LL vni(LL x)
{
    return qmi(x,p-2);
}

例题

Plant(快速幂+数学推导)

up为an down 为bn

可以发现 an - bn = 2^(n-1)

an + bn = 4^(n-1)

可以求出an 注意题目的第一项跟这里第一项不太一样

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long LL;

const int mod =  1000000007;
LL qmi(LL a,LL k)
{
    a %= mod;
    LL res = 1;
    while(k)
    {
        if(k & 1)
        {
            res = (res * a) % mod;
        }
        k >>= 1;
        a = (a * a) % mod;
    }
    return res;
}
LL up,down;
int main()
{
    LL n;
    cin >> n;
    if(n == 0) puts("1");
    else cout << (qmi(2,n * 2 - 1) + qmi(2,n-1)) % mod << endl;
    
}

Hamsters Training(逆元求组合数)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010, mod = 1e9+7;

LL fa[N],fb[N];

LL qmi(LL a,LL b)
{
    LL res = 1;
    while(b)
    {
        if(b & 1) res = res * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return res;
}

LL C(LL n,LL k)
{
    return fa[n] * fb[k] % mod * fb[n - k] % mod;
}

void init()
{
    fa[0] = fb[0] = 1;
    for(int i = 1;i < N;i++)
    {
        fa[i] = fa[i-1] * i % mod;
        fb[i] = qmi(fa[i],mod - 2) % mod;
    }
}

void solve()
{
    int n;
    cin >> n;
    cout << C(2 * n - 1,n)   << endl;
}

int main()
{
    init();
    int T;
    cin >> T;
    while(T--)
    {
        solve();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值