【高效算法设计——滑动窗口】 UVa 12174 Shuffle

Time Limit: 3000MS Memory Limit: Unknown 64bit IO Format: %lld & %llu

 Status

Description

Download as PDF

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 ≤ sn ≤ 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, x1x2, ..., 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
7
0
The 2008 ACM Northwestern European Programming Contest

题意:给定n首歌(编号1~n)和m条播放记录,每完整播放完一轮音乐后就会随机打乱顺序,问随机排序所发生的位置有多少种可能

思路:这题看样例给出的数据发现可能的情况确实很多,那么我们先思考一个问题,最多有多少种可能呢?答案是n,这点通过样例我们应该不难发现。我们可以先枚举一组完整序列的开头是从1~n开始的,比如  5 7 3 这组样例,我们枚举到2的时候,表示从7开始是一段全新的开始,即前面的5是属于上一段序列的,这样我们可以从1到n开始枚举,依次判断每种情况是否合法

那么问题来了,如何判断序列合法呢?这里要注意有三点需要考虑(当时我由于没有考虑清楚,这道题活活wa了一天,最后通过对拍才找出错误数据)
1.当枚举一个位置i为全新的开头时,则1~i-1的序列不能出现重复,否则1~i-1就不能属于同一段了。这里还要考虑一种特殊情况,就是n>m时,当枚举到i大于m时,合法的条件应该是m个序列都不出现重复,即第二点里的sum[1]==m,在代码中我们用isPre函数实现
2.满足情况1之后如何判断序列合法呢?这里我们要先利用滑动窗口的方法(窗口长度不定长,一个窗口内的数都不同)预处理一个sum数组,sum[i]表示从i开始往后的连续序列,最多有多少个不重复的数字,有了sum之后,我们判断每次sum[i]是不是等于n,即是不是以i为开头的序列之后存在一段合法序列,如果存在则i+=n,依次判断,直到i>m,在代码中我们用check函数实现
3.以上两点是否就够了呢?答案是否的,因为如果序列i为开头,如果sum[i]不为n但是可以直接到达m的话,那么也算合法,比如最后一组样例,所以要加一个额外的判断,并且第二点的check函数也要加上这个判断

代码如下:
#include<cstdio>
#include<cstring>
#include<vector>


using namespace std;
int n,m;
int seq[100000+5];
int sum[100000+5];
bool vis[100000+5];

bool check(int x)
{
    while(x<m)
    {
        if(sum[x]!=n)
        {
           if(x+sum[x]>m)
            return true;
           else
            return false;
        }

        x+=n;
    }
    return true;
}

bool isEnd(int x)
{
    if(sum[x]+x>m)
        return true;
    else
        return false;
}


bool isPre(int x)
{
    if(x>m)
    {
        if(sum[1]>=m)
            return true;
        else
            return false;
    }
    if(sum[1]+1>=x)
    {
        return true;
    }
    return false;
}


int main()
{
    int T,i,j,l,r,s;
    int ans,cnt;
   // freopen("input.txt","r",stdin);
    //freopen("my.txt","w",stdout);
    scanf("%d",&T);

    while(T--)
    {

        scanf("%d%d",&n,&m);

        for(i=1;i<=m;i++)
        {
            scanf("%d",&seq[i]);

        }



        ans=0;
        memset(sum,0,sizeof sum);
        memset(vis,false,sizeof vis);
        l=r=1;

        while(l<=m)
        {
            while(!vis[seq[r]] && r<=m)
            {
                vis[seq[r]]=true;
                r++;
            }

            vis[seq[l]]=false;
            sum[l]=r-l;
            l++;

        }


       // for(i=1;i<=m;i++)
        //    printf("%d ",sum[i]);

        for(s=1;s<=n;s++)
        {
            if(!isPre(s))
                continue;

            if(isEnd(s) || check(s))
            {
                ans++;

            }
        }


       printf("%d\n",ans);

    }


    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值