UVA 12174 Shuffle(滑动窗口优化) 紫书训练

UVA 12174 Shuffle

题意:有个音乐播放器有乱序功能,可以随机播放1 ~ s首歌曲,每次随机播放完后再次打乱顺序随机播放(在播放完这一组的歌曲之前,不会再次乱序,直到全部播完)。现截取一段记录,给定播放的歌曲数 s 以及这段记录的长度 n ,然后输入这段记录,根据这段记录,求随机排序发生的时刻有几种可能。(1 <= s, n <= 100000)
个人理解:我是真fw,先看了紫书的分析用滑动窗口,写了好几个小时,也没写出来,盯着lrj的代码看,看了半天最后才差不多理解了,利用了滑动窗口,最后将滑动窗口组合起来进行判断的方法很巧妙,这道题细节也很多,可以学到很多。
例子:4 10
3 4 4 1 3 2 1 2 3 4
1 空集合
2 3
3 3 4
4 3 4 4
5 3 4 4 1
6 4 4 1 3
7 4 1 3 2
8 …

11 1 2 3 4
12 2 3 4
13 3 4
14 4
15 空集合
集合总共10+4+1
1空集合+4正是从3 4 4 1之后开始划分,是有必要留着的,同理可得最后的15的空集合也要保留
特判例子(s>n):7 5
5 7 3 1 4
1 空集合
2 5
3 57
4 573
5 5731
6 57314
7 57314
8 57314
9 7314
10 314
11 14
12 4
13 空集合
联系下面的代码,剩下只有ok[s-1]值是0,其他都是1,所以得出特判的条件是n+1。怎么说,这个特判需要自己举例模拟领会,并不好理解。

下面写一下我对lrj代码的理解:

#include<iostream>
#include<vector>
using namespace std;
const int maxn = 100000 + 5;
int s, n, x[maxn * 3], cnt[maxn], ok[maxn * 2];
//cnt用来储存s个数字,每个数字出现的次数,x用来输入,OK用来判定该滑动窗口是否可行
int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> s >> n;
        // 令x的初始状态为-1,补充2*s个数字,方便滑动窗口滑动(应该是这样,具体我还不太清楚,还需理解)
        fill(x, x + n + 2 * s, -1);
        for (int i = 0; i < n; i++) cin >> x[i + s];//从i+s的位置开始补充
        int tot = 0; // 当前的滑动窗口有多少数字
        fill(cnt + 1, cnt + s + 1, 0); //一共有s个数字,从cnt[1]-cnt[s]赋值0
        fill(ok, ok + n + s + 1, 0);   // 一共n+s+1个滑动窗口,ok用来检测第i个滑动窗口是否可行,ok[0]和ok[n+s]其实滑动窗口里面没有数字,但是需要,具体可以看上面的例子
        //开始完成ok数组
        for (int i = 0; i < n + s + 1; i++) {
            if (tot == s) ok[i] = 1;              //当前的滑动窗口个数刚好等于s,说明一个滑动窗口完成,ok=1
            if (i < s && tot == i) ok[i] = 1;     // 从左边开始滑动窗口中数字的个数逐渐增加,也算是一个滑动窗口,假如个数跟i是一样的,说明这个滑动窗口符合条件
            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++) {//只需要从0到s-1就能判断所有的可能性
            int valid = 1;
            for (int j = i; j < n + s + 1; j += s)//j+的是s而不是1,因为每个滑动窗口之后再过s个数字才能接上上一个滑动窗口
                if (!ok[j]) valid = 0;//valid=0,说明这种情况不行
            if (valid) ans++;
        }
        if (ans == n + 1) ans = s; //特判
        cout << ans << "\n";
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值