UVa 12174

链接:

https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3326

题意:

你正在使用的音乐播放器有一个所谓的乱序功能,即随机打乱歌曲的播放顺序。
假设一共有s首歌,则一开始会给这s首歌随机排序,全部播放完毕后再重新随机排序、继续播放,依此类推。
注意,当s首歌播放完毕之前不会重新排序。这样,播放记录里的每s首歌都是1~s的一个排列。
给出一个长度为n(1≤s,n≤100000)的播放记录(不一定是从最开始记录的),你的任务是统计下次随机排序所发生的时间有多少种可能性。
例如,s=4,播放记录是3, 4, 4, 1, 3, 2, 1, 2, 3, 4,不难发现只有一种可能性:前两首是一个段的最后两首歌,后面是两个完整的段,因此答案是1;
当s=3时,播放记录1, 2, 1有两种可能:第一首是一个段,后两首是另一段;前两首是一段,最后一首是另一段。答案为2。

分析1:

使用滑动窗口,窗口大小是“基本”固定的(因为还需要考虑不完整的段),因此只需要一个指针;
而且所有数都是1~s的整数,只需要一个数组即可保存每个数在窗口中出现的次数。
再用一个变量记录在窗口中恰好出现一次的数的个数,则可以在O(n)时间内判断出每个窗口是否满足要求(每个整数最多出现一次)。
这样,就可以枚举所有可能的答案,判断它对应的所有窗口,当且仅当所有窗口均满足要求时这个答案是可行的。
可以把所有窗口都映射成窗口0 ~ s-1,使用求模即可。

代码1:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int UP = 100000 + 5;
 7 
 8 int a[UP], num[UP]; //num[x]记录当前窗口中x值的个数
 9 bool can[UP]; //can[i]表示以位置i结尾的一个序列是否可能是1~s的一个排列
10 
11 int main(){
12     int T;
13     scanf("%d", &T);
14     while(T--){
15         int s, n;
16         scanf("%d%d", &s, &n);
17         for(int i = 0; i < n; i++) scanf("%d", &a[i]);
18 
19         memset(num, 0, sizeof(num));
20         memset(can, true, sizeof(can));
21 
22         int tot = 0; //tot记录当前窗口中只出现一次的数的个数
23         for(int i = 0; i < n + s; i++){
24             if(i < n && ++num[a[i]] == 1) tot++;
25             if(i >= s && --num[a[i-s]] == 0) tot--;
26             if(min(i, n-1) - max(i-s, -1) != tot) can[i%s] = false;
27         }
28 
29         int ans = 0;
30         for(int i = 0; i < s; i++) if(can[i]) ans++;
31         printf("%d
", ans);
32     }
33     return 0;
34 }

分析2:

这个比较直观。对于1 2 1这样的播放列表,两个1之间必然存在一个窗口的交界位置。类似地,对于同一个数字的两次相邻的出现,
都能排除一些不可能的位置,当把所有不可能的位置排除后,剩下的位置个数便是答案。
可以把所有位置都映射成位置0 ~ s-1,使用求模即可。

代码2:

 1 #include <cstdio>
 2 #include <cstring>
 3 
 4 const int UP = 100000 + 5;
 5 
 6 int a[UP], f[UP]; //f[a[i]] 记录与 a[i] 的值相同的前一个位置
 7 bool can[UP]; //can[i]表示以位置i结尾的一个序列是否可能是1~s的一个排列
 8 
 9 int main(){
10     int T;
11     scanf("%d", &T);
12     while(T--){
13         int s, n;
14         scanf("%d%d", &s, &n);
15         for(int i = 0; i < n; i++) scanf("%d", &a[i]);
16 
17         memset(f, -1, sizeof(f));
18         memset(can, true, sizeof(can));
19 
20         for(int x, i = 0; i < n; f[x] = i++){
21             x = a[i];
22             if(f[x] != -1){ //判断x之前是否出现过
23                 if(i - f[x] >= s) continue; //相邻的两个x的距离不小于s,则位置f[x]~i-1都可能是结尾
24                 int L = f[x] % s, R = i % s; //把位置L和R都映射成位置0 ~ s-1
25                 if(L < R){
26                     for(int t = 0; t < L; t++) can[t] = false;
27                     for(int t = R; t < s; t++) can[t] = false;
28                 }
29                 else for(int t = R; t < L; t++) can[t] = false;
30             }
31         }
32 
33         int ans = 0;
34         for(int i = 0; i < s; i++) if(can[i]) ans++;
35         printf("%d
", ans);
36     }
37     return 0;
38 }

分析3:

这是我自己想到的做法,跟分析2基本相同,只是没有使用求模来映射位置,速度要慢一点。在这里也把代码贴一下吧。

代码3:

 1 #include <cstdio>
 2 #include <cstring>
 3 
 4 const int UP = 100000 + 5;
 5 
 6 int a[UP], f[UP]; //f[a[i]] 记录与 a[i] 的值相同的前一个位置
 7 bool no[UP*2]; //no[i]表示以位置i结尾的一个序列是否可能是1~s的一个排列
 8 
 9 int main(){
10     int T;
11     scanf("%d", &T);
12     while(T--){
13         int s, n;
14         scanf("%d%d", &s, &n);
15         for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
16 
17         memset(f, 0, sizeof(f));
18         memset(no, false, sizeof(no));
19 
20         for(int x, i = 1; i <= n; f[x] = i++){
21             x = a[i];
22             if(f[x]){ //判断x之前是否出现过
23                 if(i - f[x] >= s) continue; //相邻的两个x的距离不小于s,则位置f[x]~i-1都可能是结尾
24                 for(int t = i; t - f[x] < s; t++) no[t] = true; //排除不可能是结尾的位置
25                 for(int t = f[x] - 1; t && i - t <= s; t--) no[t] = true;
26             }
27         }
28 
29         int ans = 0;
30         for(int t, i = 1; i <= s; i++){
31             for(t = i; t <= n + s; t += s) if(no[t]) break; //判断以位置i为结尾是否可行
32             if(t > n + s) ans++;
33         }
34         printf("%d
", ans);
35     }
36     return 0;
37 }
原文地址:https://www.cnblogs.com/hkxy125/p/8152287.html