[牛客IOI周赛16-提高组]补题 暴力美学

https://ac.nowcoder.com/acm/contest/5389

B

参考大佬题解 https://ac.nowcoder.com/acm/problem/blogs/201976
题目:给定一个序列,每次在其左端点或者右端点加入一个元素。
每次加入都求一次这个序列的最长子区间---满足这个子区间的 'W' 元素的个数等于 'D' 元素的个数

比赛时考虑把W看成1 R看成0 然后看个数是否相等----考虑复杂了,不过也想到了用区间和来表示
把 'W' 看成 1,把 'D' 看成 -1,若一个子区间满足条件,则这个子区间的和为 0。
所以先用一个表示前缀和;若满足条件则
但是这个题是动态在左右增加数的,且每次都要查询;考虑维护一个前缀和第一次和最后一次出现的位置 ,即
比如在r处前缀和为5,找map[5](存的是前面出现过的前缀和),如果map[5]存在则pre[r]=pre[map[5]] 也就是这个子区间的和为0。
前面添加数不影响前缀和之间的差(虽然影响了整个前缀和数组),所以可以离线处理前缀和数组。
这个题难点在于前面加数怎么求---转换思维,把数组倒过来看就是和上面一样的"前缀和"

#include <bits/stdc++.h>
#define N 2000005

using namespace std;
unordered_map<int, int> l, r;
int n;
char s[N]; int num[N<<1];
int opr[N],presum[N<<1],sufsum[N<<1];
int get(char ch=0) {
    while(ch != 'R' && ch != 'W' && ch != 'D') ch = getchar();
    if(ch == 'R') return 0;
    return ch == 'D' ? 1 : -1;
}
int main() {

    int ans = 0;
    int lsum = 0, rsum = 0;
    int lp=N+1,rp=N;
    scanf("%d", &n);
    scanf("%s", s + 1);
    int len = strlen(s + 1);
    for(int i = 1; i <= len; ++i)
        num[++rp] = get(s[i]);

    //记录左右增加
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &opr[i]);
        if(opr[i] == 1) {
            num[--lp] = get();
        } else {
            num[++rp] = get();
        }
    }

    for(int i = lp; i <= rp; ++i) presum[i] = presum[i - 1] + num[i];
    for(int i = rp; i >= lp; --i) sufsum[i] = sufsum[i + 1] + num[i];

    lp = N+1, rp = N;
    r[presum[rp]] = rp;
    for(int i = 1; i <= len; ++i) {//起初的序列
        ++rp;
        if(!r.count(presum[rp])) {
            r[presum[rp]] = rp;
        } else {
            ans = max(ans, rp - r[presum[rp]]);
        }
    }
    for(int i = N+1; i <= N+len; ++i) l[sufsum[i]] = i;
    l[sufsum[N+len + 1]] = N+len + 1;
    printf("%d\n", ans);

    for(int i = 1; i <= n; ++i) {
        if(opr[i] == 1) {
            --lp, r[presum[lp - 1]] = lp - 1;//更新前缀和出现的位置
            if(!l.count(sufsum[lp])) {
                l[sufsum[lp]] = lp;
            } else {
                ans = max(ans, l[sufsum[lp]] - lp);
            }
        } else {
            ++rp, l[sufsum[rp + 1]] = rp + 1;//更新前缀和出现的位置
            if(!r.count(presum[rp])) {
                r[presum[rp]] = rp;
            } else {
                ans = max(ans, rp - r[presum[rp]]);
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

C

对于每次查询暴力+各种剪枝可过,代码有注释。

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

#define ll long long
#define ull unsigned long long
#define rint register int

const int N = 600005;
int a[N];
int n, m;

int solve(int x)
{
    int mmax = 0, orv = 0, len = 1e9;
    for (rint i = x; i >= 1; i--) {
        mmax = max(mmax, a[i]);
        orv |= a[i];
        if (orv > mmax) {
            len = min(len, x - i + 1);
            break;
        }
    }
    mmax = 0, orv = 0;
    for (rint i = x; i <= n; i++) {
        mmax = max(mmax, a[i]);
        orv |= a[i];
        if (orv > mmax) {
            len = min(len, i - x + 1);
            break;
        }
    }
    mmax = a[x], orv = a[x];
    for (rint i = x - 1; i >= 1; i--) {
        mmax = max(mmax, a[i]);
        orv |= a[i];
        if (x - i + 1 >= len) {
            break; //剪枝
        }
        if (orv > mmax) {
            len = x - i + 1;
            break; //剪枝
        }
        int Max2 = mmax, Or2 = orv;
        for (rint j = x + 1; j <= n; j++) {
            Max2 = max(Max2, a[j]);
            Or2 |= a[j];
            if (j - i + 1 >= len) {
                break;
            }
            if (Or2 > Max2) {
                len = j - i + 1;
                break;
            }
        }
    }
    return len;
}
int main()
{
    cin>>n>>m;
    for (rint i = 1; i <= n; i++) {
        cin>>a[i];
    }
    while (m--) {
        int x;cin>>x;
        int ans = solve(x);//对于每次查询
        if (ans == 1e9)
            ans = -1;
        cout<<ans<<endl;
    }
    return 0;
}
全部评论
虽然我的数据很水但补题写暴力是不对的呜呜呜!
点赞 回复 分享
发布于 2021-09-21 12:23

相关推荐

ArisRobert:统一解释一下,第4点的意思是,公司按需通知员工,没被通知到的员工是没法去上班的,所以只要没被通知到,就自动离职。就是一种比较抽象的裁员。
点赞 评论 收藏
分享
点赞 评论 收藏
分享
1 收藏 评论
分享
牛客网
牛客企业服务