[滑动窗口] UVa12174 Shuffle的播放记录(滑窗做预处理)(较复杂区间处理)

题目

这里写图片描述

思路

1.暴力:枚举一个播放记录的起点,然后进行是否重复的判断。
2.预处理+暴力枚举:先用滑窗滑动整个序列,判断出每个地方能否作为一个播放记录的开头。(即在接下来的s长度的序列里无重复)最后枚举时,依次判断此种情况的每个窗口是否都能作为开头即可。
3.需要注意本题特殊情况比较多,所以含有很多区间操作。拿题目所给样例7 3 5 7 3来说。由于s>n,所以就要把整个序列补成这样的:

下标012345678910111213141516
数据-1-1-1-1-1-1-1573-1-1-1-1-1-1-1

即前后各加s个-1。
这时候就可以理解
(1).对于一个播放记录的起点,从0到n+s(10)。
(2).当窗口起点小于n时,只要满足tot == i即可以构成合法播放记录。
(3).当窗口起点大于n时,只要满足tot == n+s-i即可以构成合法播放记录。
(4).窗口向右移时,删除的最左边的那个元素,当其为-1或失去后cnt不等于0,就不减tot;
(5).窗口向右移时,新增的最右边的那个元素,当其为-1或cnt加1钱不等于0,就不加tot;

代码

#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;

const int maxn = 100000 + 1000;
int s, n, x[maxn*3], cnt[maxn], ok[maxn*2];

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &s, &n);

        // 向序列的左右两边,分别加-1.这样就避免了负下标和越界的问题。
        fill(x, x + n + 2 * s, -1);
        for (int i = 0; i < n; i++) scanf("%d", &x[i + s]);

        int tot = 0; // 窗口中只出现一次的元素的个数
        fill(cnt + 1, cnt + s + 1, 0); // cnt是窗口中某个元素出现的次数
        fill(ok, ok + n + s + 1, 0);  // ok[i]=1表示以i开头的滑动窗口,没有重复元素

        // 计算ok数组
        for (int i = 0; i < n + s + 1; i++) {
            if (tot == s) ok[i] = 1;
            if (i < s && tot == i) ok[i] = 1;       // 左边越界
            if (i > n && tot == n + s - i) ok[i] = 1;       // 右边越界

            if (i == n + s) break;
            if (x[i] != -1 && --cnt[x[i]] == 0) tot--;
            if (x[i + s] != -1 && cnt[x[i + s]]++ == 0) tot++;
        }

        // 枚举每种答案
        int ans = 0;
        for (int i = 0; i < s; i++) {
            int vaild = 1;
            for (int j = i; j < n + s + 1; j += s)
                if (!ok[j]) vaild = 0;
            if (vaild) ans++;
        }
        if (ans == n + 1) ans = s; //特殊情况
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值