2020牛客暑期多校第六场 K-Bag(贪心写法+滑动窗口枚举写法)

题目链接
题意:
定义k-bag为若干长度为k的全排列组成的串,问题目给出的串是否可能为一个k-bag的子串。
贪心思路
设定当前全排列的左右指针,从左向右加点,大致情况分以下几种:
1.尾指针遇到可加入的数,直接加入。
2.当前段满足全排列,进入下一段匹配。
3.尾指针遇到重复的数,此时无法继续向右添加,考虑向左边加数,同时将之前匹配成功的每段全排列向左移动。考虑以下两种情况:
3.1.头指针是否能前移?若左边需添加的数与当前段中的数有重复,则说明该数与当前段重复的数必不在同一段中,则让尾指针回退,直到可添加为止。
3.2.之前匹配成功的每一段是否能够前移?每一段前移会失去一个数同时得到一个数,那么这两个相距k的数必须相等才能前移,也就是说若位置m要前移,位置m-k,m-2k…都需相等。预处理即可。
贪心代码

#include
#define ll long long
#define LL long long
#define PB push_back
#define MP make_pair
using namespace std;
const int maxn=5e5+100;
const ll inf=1e18+10;
int a[maxn],back[maxn],vis[maxn];
map<int,int>mp;
int main()
{
    int t,n,k;
    scanf("%d",&t);
    while(t--){
        mp.clear();
        scanf("%d %d",&n,&k);
        int okk=1;
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            if(a[i]>k)okk=0;
            back[i]=vis[i]=0;
        }
        if(!okk){
            puts("NO");
            continue;
        }
        int st=1,ed=1,ok=1;
        back
        for(int i=1;i<=n&&i<=k;i++){
            back[i]=1;
        }
        for(int i=k+1;i<=n;i++){
            if(back[i-k]&&a[i]==a[i-k])
                back[i]=1;
        }
         
        mp.clear();
        for(int i=1;i<=n;i++){
            if(mp[a[i]]){
                st=ed=i;
                break;
            }
            mp[a[i]]++;
        }
        int sz=0;
        mp.clear();
        ok=1;
        while(ed<=n){
            if(!ok)break;
            if(mp[a[ed]]){
                ed--;
                while(sz<k){
                	while(mp[a[st-1]]){
                		if(vis[ed])goto ed;
                		vis[ed]=1;
                		mp[a[ed--]]--;
                		sz--;
					}
                    if(st-1>0&&back[st-1]){
                        mp[a[st-1]]++;
                        sz++;
                        st--;
                    }
                    else{
                    	ed:
                        ok=0;
                        break;
                    }
                }
                if(sz<k){
                	ok=0;
                    break;
				}
            }
            else{
                mp[a[ed]]++;
                sz++;
            }
            ed++;
            if(sz==k){
                mp.clear();
                sz=0;
                st=ed;
            }
        }
        if(ok)puts("YES");
        else puts("NO");
    }
}

枚举思路
首先用滑动窗口计算长度为k的连续区间是否为全排列,枚举左右分割点判断即可。
枚举代码

#include
#define ll long long
#define LL long long
#define PB push_back
#define MP make_pair
using namespace std;
const int maxn=5e5+100;
const ll inf=1e18+10;
unordered_map<int,int>mp;
int n,k,a[maxn],f[maxn],pre[maxn],lst[maxn];
inline void init(){
	mp.clear();
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(i>k){
			mp[a[i-k]]--;
			if(!mp[a[i-k]])cnt--;
		}
		if(!mp[a[i]])cnt++;
		mp[a[i]]++;
		if(cnt==k)f[i]=1;
	}
	mp.clear();
	for(int i=1;i<=n;i++){
		if(!mp[a[i]])pre[i]=1;
		else break;
		mp[a[i]]++;
	}
	mp.clear();
	for(int i=n;i>=1;i--){
		if(!mp[a[i]])lst[i]=1;
		else break;
		mp[a[i]]++;
	}
}
inline void slove(){
	scanf("%d%d",&n,&k);
	int p1=1;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		if(a[i]>k)p1=0;
		f[i]=pre[i]=lst[i]=0;
	}
	if(!p1){
		puts("NO");
		return;
	}
	init();
	int num=(n-1)%k+1,ok=0;
	for(int i=0;i<=num;i++){
		int l=0,r=0;
		if(i==0||pre[i])l=1;
		if(n-num+1+i==n+1||lst[n-num+1+i])r=1;
		if(l&&r){
			int okk=1;
			for(int j=i+k;j<n-num+1+i;j+=k){
				if(!f[j])okk=0;
			}
			if(okk){
				ok=1;
				break;
			}
		}
	}
	for(int i=1;i<n;i++){
		if(pre[i]&&lst[i+1])ok=1;
	}
	if(!ok){
		num+=k;
		if(num<=n){
			for(int i=0;i<=num;i++){
				int l=0,r=0;
				if(i==0||pre[i])l=1;
				if(n-num+1+i==n+1||lst[n-num+1+i])r=1;
				if(l&&r){
					int okk=1;
					for(int j=i+k;j<n-num+1+i;j+=k){
						if(!f[j])okk=0;
					}
					if(okk){
						ok=1;
						break;
					}
				}
			}
		}
	}
	puts(ok?"YES":"NO");
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		slove();
	}
}

你可能感兴趣的:(贪心,枚举)