欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UVa12174 Shuffle(滑动窗口)

程序员文章站 2022-03-04 21:02:22
...

题目链接
UVa12174 Shuffle(滑动窗口)
1.题目大意:一共有s首歌,音乐播放器有一个乱序的功能,一开始给s首歌随机排列,全部播放完后再随机排列,继续播放以此类推,当且仅当s首歌全部播放完成才随机。现在给出一个长度为n的播放序列(不一定从最开始记录),统计下次排序发生时间有多少种可能性

2.题意实际上就是最后一首或几首歌的种类,决定了有多少种答案。首先需要注意,当n<=s且n个数互不相同,那么答案就是s,因为可以以任意一首歌结束当前序列并开始下次排序。这个题写了很长时间,一开始一直看不懂题解,没办法只好自己慢慢敲,学到了不少东西,尽管最后自己写的RE且找不到错误,但是过了debug,也没有遗憾了。自己尝试后再去看LRJ的方法也很好懂了

3.当n>s时,我们只需要考虑前面s个数那么只要每次的分段都符合互不相同这一要求,就可以作为一个答案。如果暴力的话肯定超时,在这里我们预处理前缀的1-s-1长度的窗口以及整个区间s长度的窗口,那么可以得到以每个数结尾的长度为[1-s]的窗口是否符合答案,接着枚举每一个区间尾,接下来每次都加上s的长度,那么判断到最后。但是我们还跳过了一段区间,即最后的[s-1,1]长度的区间没有判断,因此我们还需要处理这s个后缀窗口是否符合互不相同。至于判断方法,使用cnt记录区间内互不相同数的个数,尺取的思想,首先将窗口由1逐渐扩大到s,mp记录当前区间内每个数的个数,当窗口达到s后,每次移动对应mp[a[L]]–,mp[a[R]]++。同样的方法处理后缀
UVa12174 Shuffle(滑动窗口)
4.LRJ的思路是,首先将整个序列扩展为n+2s长度,这样就只需要从一开始使用长度为s的窗口,不断向右滑动,每次更新cnt,这样就得到了每个数结尾的长度为s的窗口是否符合要求
UVa12174 Shuffle(滑动窗口)
标答

#include<iostream>
#include<vector>
using namespace std;

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

int main() {
  int T;
  cin >> T;
  while(T--) {
    cin >> s >> n;
    fill(x, x+n+2*s, -1);
    for(int i = 0; i < n; i++) cin >> x[i+s];
    int tot = 0; // 统计在滑动窗口中的不一样的数字的个数
    fill(cnt+1, cnt+s+1, 0); // cnt[i]保存数字i在窗口中出现的次数
    fill(ok, ok+n+s+1, 0);   // ok[i] = 1 当如果窗口中没有相同的数,那么一定是一个排列

    //更新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 valid = 1;
      for (int j = i; j < n+s+1; j += s)
        if(!ok[j]) valid = 0;;
      if(valid) ans++;
    }
    if(ans == n+1) ans = s;
    cout << ans << "\n";
  }
  return 0;
}

RE的代码

#include <iostream>
#include <cstring>
#include <unordered_map>
using namespace std;
const int maxn=3e5+10;
unordered_map<int,int> mp;
int a[maxn];
bool ok[maxn],sub[maxn];
int t,n,s;

bool check(){
    mp.clear();
    for(int i=1;i<=n;i++){
        if(mp[a[i]]) return false;
        else mp[a[i]]=1;
    }
    return true;
}

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>t;
    while(t--){
        cin>>s>>n;
        memset(a,0,sizeof a);
        for(int i=1;i<=n;i++) cin>>a[i];
        if(n<=s && check()){  //特判
            cout<<s<<endl;
            continue;
        }
        int l=1,r=1;
        int cnt=0;
        mp.clear();
        while(r<=n){  //尺取,首先由1逐渐增加到s,接着保持s向右滑动
            while(r-l+1<=s && r<=n){
                if(!mp[a[r]]) cnt++;
                mp[a[r]]++;
                if(cnt==(r-l+1)) ok[r]=1;
                else ok[r]=0;
                r++;
            }
            if(--mp[a[l]]==0) cnt--;
            l++;
        }
        l=n-s+1,r=n,cnt=0;
        int tot=0;
        mp.clear();
        while(l<r){  //处理后缀,由1增加到s即可,无需在向左滑动
            tot++;
            if(!mp[a[r]]) cnt++;
            mp[a[r]]++;
            if(cnt==tot) sub[r]=1;
            else sub[r]=0;
            r--;
        }
        //验证两个数组都是对的
        //for(int i=1;i<=n;i++) cout<<ok[i]<<endl;
        //for(int i=n-s+2;i<=n;i++) cout<<sub[i]<<endl;
        int ans=0;
        for(int i=1,j;i<=s;i++){
            if(ok[i]){
                int flag=1;
                for(j=i;j<=n;j+=s)  
                    if(!ok[j]){
                        flag=0;
                        break;
                    }
                //cout<<flag<<"---"<<j-s+1<<endl;
                if(flag && j-s+1<=n){  //如果前面都符合,将后缀是否符合也判断
                    if(!sub[j-s+1]) flag=0;
                }
                if(flag) ans++;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}