【每日训练】2020/11/8(规律 + 二进制、单调栈 + 前缀和,后缀和、bitset + 枚举)

整理的算法模板合集: ACM模板


1. NC 打铁的箱子(规律 + 二进制)

在这里插入图片描述

  • 首先分析数据范围

n<1e9,所以哪怕 O ( n ) O(n) O(n)的算法都不可能通过,就更别想用01背包来完成了(看上去像是01背包的模型)。只能用一些结论 O ( 1 ) O(1) O(1)输出、二进制优化 O ( l o g n ) O(logn) O(logn)、折半查找等极为迅速的算法。

  • 然后来分析题意

我们发现给出的能用的数为: 2 , 3 , 5 , 9 2,3,5,9 2359,题目干扰地说是前一项 ∗ 2 − 1 *2-1 21,实际上我们可以很轻松地看出来这个数列应该是 2 0 + 1 、 2 1 + 1 、 2 2 + 1..... 2^0+1、2^1+1、2^2+1..... 20+121+122+1..... 。我们发现本题的关键是将题目所给的备选数据转换为二进制的数,而不是题目中给出的格式,然后就可以发现规律。我们发现这些数能够凑出来的数一定是 2 k 1 + k 2 . . . + k x + x 2^{k_1 + k_2...+k_x} + x 2k1+k2...+kx+x ,所以我们可以看看输入的数是否满足这个条件

  • 最后计算答案

我们可以枚举用了几个数,也就是 x x x ,然后按照上面的能够凑出来的公式来判断。那么我们可以先将 n n n 减去 x x x,然后检查 n − x n−x nx 的二进制中是否恰好是 x x x 1 1 1 即可。

  • 时间复杂度 O ( l o g n ) O(logn) O(logn)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define int long long 
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;
using namespace std;

int lowbit(int x)
{
    return x & (-x);
}

int t, n, m;

signed main()
{
    scanf("%lld", &t);
    while(t -- ){
        bool flag = 0;
        scanf("%lld", &n);
        for(int i = 1; i <= 30; ++ i){
            int x = n - i;
            int num = 0;//二进制中x有几个1
            while(x){
                num ++ ;
                x -= lowbit(x);
            }
            if(num == i){
                flag = 1;
                break;
            }
        }
        if(flag){
            puts("YES");
        }
        else puts("NO");
    }
    return 0;
}

2. NC 最优屏障(单调栈 + 前缀和,后缀和)

在这里插入图片描述
如图:
在这里插入图片描述

  • 首先分析数据范围

n < 5 e 4 n<5e4 n<5e4,所以只能用 O ( n l o g n ) O(nlogn) O(nlogn)或者 O ( n ) O(n) O(n)的算法来做。

  • 然后再分析题意

因为我们如果在中间放一个屏障的话,那么左右的山峰就被隔断了,也就是屏障左边只算左边的,屏障右边的只算右边的,按照这个思路我们要找的是一个最优的位置,使得只算左边和只算右边的比全部都算差的最大,所以大致的思路就是我们枚举每一个位置,看这个位置的差是否最大,这个过程是 O ( n ) O(n) O(n)。而这个差就是总的防御力 - (屏障左边的防御力+屏障右边的防御力)。我们思考如何求得左边,右边的防御力。

我们画图发现,首先,左右的山峰一定都可以互相看到,如果对于一个山峰而言,先看左边,所有高度小于他自己的山峰都可以算作自己的防御力,也就是从左往右单调递减,(如果是1高2低3更低,那么1只能和2算,2只能和3算)我们发现可以用一个单调栈维护,右边同理。那么现在的问题是如何在 O ( 1 ) O(1) O(1)或者 O ( l o g n ) O(logn) O(logn)的时间复杂度内求得左边的防御力和以及右边的防御力和,我们发现这不就是前缀和嘛,同理后面的我们可以维护一个后缀和,这样就可以 O ( 1 ) O(1) O(1)求得每个点防止屏障时该点左右的防御力和。

  • 最后计算答案

答案就是总的防御力 - (屏障左边的防御力+屏障右边的防御力) = m a x v = m a x { p r e [ n ] − ( p r e [ i − 1 ] + s u f [ i ] ) } maxv = max\{ pre[n] - (pre[i - 1] + suf[i])\} maxv=max{pre[n](pre[i1]+suf[i])}

  • 时间复杂度 O ( n ) O(n) O(n)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 50007, M = 5000007, INF = 0x3f3f3f3f;
using namespace std;

int n, m, t, kcase;
int pre[N];//前缀和
int suf[N];//后缀和
int a[N];
stack<int>stk;

int main()
{
    scanf("%d", &t);
    while(t -- ){
        scanf("%d", &n);
        for(int i = 1; i <= n; ++ i){
            scanf("%d", &a[i]);
        }
        memset(pre, 0, sizeof pre);
        memset(suf, 0, sizeof suf);
        while(stk.size())stk.pop();
        for(int i = 1; i <= n; ++ i){
            int now = 0;
            pre[i] = pre[i - 1];
            while(stk.size() && stk.top() < a[i]){
                stk.pop();
                now ++ ;
            }
            if(stk.size())pre[i] += now + 1;
            else pre[i] += now;
            stk.push(a[i]);
        }
        while(stk.size())stk.pop();
        for(int i = n; i >= 1; -- i){
            int now = 0;
            suf[i] = suf[i + 1];
            while(stk.size() && stk.top() < a[i]){
                stk.pop();
                now ++ ;
            }
            if(stk.size())suf[i] += now + 1;
            else suf[i] += now;
            stk.push(a[i]);
        }

        int maxv = -INF, id = -1;
        for(int i = 1; i <= n; ++ i){
            if(maxv < (pre[n] - (pre[i - 1] + suf[i]))){
                maxv = pre[n] - (pre[i - 1] + suf[i]);
                id = i;
            }
        }
        printf("Case #%d: %d %d", ++ kcase, id, maxv);
    }
    return 0;
}

3. CF993C Careful Maneuvering(bitset优化 + 枚举,模拟)

在这里插入图片描述

  • 首先分析数据范围

n , m < 60 n,m<60 nm<60,爆搜!不至于,至少我们可以很轻松地 O ( n 2 ) O(n^2) O(n2)枚举。

  • 然后来分析题意

我们实际上可以把题意简化为:有两条平行于y轴的直线,他们是关于y轴对称的。每条直线上有一些点。每条直线上的点都与另外一条直线上的点连一条线,这条线与y轴有一个交点。选两个点,使经过这两个点的直线的两个端点数量最多。

某个点能摧毁的飞船一定是因为有两个飞船关于这个点对称。即若(-100,y1)和(100,y2)能互相摧毁,说明他们关于(0,(y1+y2)/2)对称。这里100是没有用的,我们可以离散化一下。

我们n*m枚举所有的左右点,找到他们的对称点,然后标记一下这个点可以将i,j这两个点删除,这里可以直接使用bitset标记,因为最后我们有两架飞机,需要合并,使用bitset可以很轻松地完成合并(使用 | )最后取最大值即可。

  • 时间复杂度 O ( n ∗ m + c n t 2 ∗ ( n + m ) 64 ) O(n * m + \frac{cnt^2 * (n + m)}{64}) O(nm+64cnt2(n+m))

需要注意的是我们使用get_mid的时候,由于 ab 都是 int,所以直接相加除以 2 会下取整成 int

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<bitset>
#include<map>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 40007, M = 100007, INF = 0x3f3f3f3f;
using namespace std;

int n, m;
bitset<200>Glight[N], Gright[N];
map<double, int>id;
int l[M], r[M];
int cnt;

inline double get_mid(int a, int b)
{
    return (double)(a + b) / 2;//啊这,这里要加(double)不然他会取整然后就WA at 3了
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++ i)
        scanf("%d", &l[i]);
    for(int i = 1; i <= m; ++ i)
        scanf("%d", &r[i]);

    for(int i = 1; i <= n; ++ i){
        for(int j = 1; j <= m; ++ j){
            double x = get_mid(l[i], r[j]);
            if(id[x] == 0)//可能存在同一个对称点
                id[x] = ++ cnt;
            Glight[id[x]].set(i, 1);
            Gright[id[x]].set(j, 1);
        }
    }
    int ans = 0;
    for(int i = 1; i <= cnt; ++ i){
        for(int j = 1; j <= cnt; ++ j){
            bitset<200>A, B;
            A = Glight[i] | Glight[j];
            B = Gright[i] | Gright[j];
            int tmp = A.count() + B.count();
            ans = max(ans, tmp);
        }
    }
    printf("%d\n", ans);
    return 0;
}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页