Problem Description
You are listening to your music collection using the shuffle function to keep the music surprising. You assume that the shuffle algorithm of your music player makes a random permutation of the songs in the playlist and plays the songs in that order until all songs have been played. Then it reshuffles and starts playing the list again.
You have a history of the songs that have been played. However, your record of the history of played songs is not complete, as you started recording songs at a certain point in time and a number of songs might already have been played. From this history, you want to know at how many different points in the future the next reshuffle might occur.
A potential future reshuffle position is valid if it divides the recorded history into intervals of length s (the number of songs in the playlist) with the first and last interval possibly containing less than s songs and no interval contains a specific song more than once.
Input
On the first line one positive number: the number of testcases, at most 100. After that per testcase:
*One line with two integers s and n (1 ≤ s, n ≤ 100000): the number of different songs in the playlist and the number of songs in the recorded playlist history.
*One line with n space separated integers, x1, x2, …, xn (1 ≤ xi ≤ s): the recorded playlist history.
Output
Per testcase:
*One line with the number of future positions the next reshuffle can be at. If the history could not be generated by the above mentioned algorithm, output 0.
Sample Input
4
4 10
3 4 4 1 3 2 1 2 3 4
6 6
6 5 4 3 2 1
3 5
3 3 1 1 1
7 3
5 7 3
Sample Output
1
6
0
7
题意:
假设一种音乐播放器有一个乱序的功能,设定每播放S首歌为一个周期,随机播放编号为1~S的歌曲。现在给一个长度为N的部分播放记录,请统计下次随机排序所发生的时间的可能性种数。(或者计算开头第一首歌可以在某些播放序列的哪些位置)(1≤S,N≤100000)
所给的播放记录不全,即可以在开头和结尾添加歌。
题解:
滑动窗口法。我们可以想象一个长度为s的窗口,在长度为n的序列上滑动,那么我们应该可以获得n+s-1个窗口(可以手动模拟一下)。很明显前s个窗口会一直添加元素,后s个窗口会一直删除元素。
我们要求的是所给的序列第一个歌,可以放在哪些位置,那么前s个窗口就已经把他所有可能的位置给包括了。一个窗口如果可以成为一种播放序列,那么它里面的所有歌都应该不一样,或者说在这个窗口中出现次数为1的总数是窗口的长度。当一个播放序列确定了,其他的序列也就确定了,位置也就确定了。
判断一个窗口是否满足条件我们只要在滑动的过程中维护一个计数器和一个数组。计数器记录这个窗口中出现次数是1的歌的数目,数组记录每首歌出现次数。只要窗口长度等于计数器的值,那么这个窗口就是符合的。
这里要注意n<=s的情况,如果序列n的每个值都不一样(或者是判断第n个窗口是否可行),那么我们最终结果应该就是s(因为我可以通过在前后添加任意长度的序列,使它构成不同位置的播放序列)。
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int N=110000;
int x[2*N];
int vis[2*N];//判断窗口是否可行
int cnt[2*N];//记录窗口滑动过程中每首歌的出现次数
int t,s,n;
int main()
{
scanf("%d",&t);
while(t--)
{
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
scanf("%d%d",&s,&n);
for(int i=0;i<n;i++)
{
scanf("%d",&x[i]);
}
int tot=0;//计数器
for(int i=0;i<n+s-1;i++)//一共可以得到n+s-1个窗口
{
if(i<n)//后s个只删除
{
if(++cnt[x[i]]==1) tot++;//判断是否更新计数器
}
if(i>=s)//前s个只添加
{
if(--cnt[x[i-s]]==0) tot--;
}
//printf("%d :%d\n",i,tot);
if(i<s&&tot!=(i+1))//前s个的窗口长度为i+1
{
vis[i]=1;
continue;
}
if(i<n&&i>=s&&tot!=s)//中间的窗口长度均为s
{
vis[i]=1;
vis[i%s]=1;
continue;
}
if(i>=n&&tot!=(n+s-i-1))//后s个的窗口长度每次滑动会减少
{
vis[i]=1;
vis[i%s]=1;//对应前s个的哪个位置
continue;
}
}
int ans=0;
for(int i=0;i<s;i++)
{
if(!vis[i]) ans++;
}
if(n<=s&&!vis[n-1])//特判
{
ans=s;
}
printf("%d\n",ans);
}
return 0;
}