Description
给定 n , k n,k n,k 以及位置集合 S 1 , S 2 , ⋯ , S k S_1,S_2,\cdots,S_k S1,S2,⋯,Sk。
定义第 i i i 轮排序如下: 将 S i S_i Si 中的值从小到大排序。
你需要判断,若依次执行第 1 , 2 , 3 , ⋯ , k 1,2,3,\cdots,k 1,2,3,⋯,k 轮排序,是否任意数列都能最终变成单调不下降的。
1 ≤ n ≤ 40 , 1 ≤ k ≤ 10 1 \le n \le 40,1 \le k \le 10 1≤n≤40,1≤k≤10
Solution
下面,为方便叙述,令一个序列可以被复原,当且仅当对其依次执行第 1 , 2 , 3 , ⋯ , k 1,2,3,\cdots,k 1,2,3,⋯,k 轮排序后,该排列不降。
Lemma
答案为 ACCEPTED
,当且仅当所有 01 01 01 序列可被复原。
考虑求出所有可被复原的 01 01 01 串。若其仅包含形如 0000...0111...11
的串,那么答案为 ACCEPTED
,否则为 REJECTED
。
考虑扫描每轮排序,并维护若干个当前可达的 01 01 01 串。其中,若在第 i i i 轮排序后串 S 0 ′ S_0' S0′ 可达,当且仅当存在某个初始串 S 0 S_0 S0 使得其经历前 i i i 轮排序后变为 S 0 ′ S_0' S0′。于是,我们得到了算法流程: 维护若干个当前可达的 01 01 01 串,每次向上向下扩展。可以理解为 BFS。
这样我们就得到了一个朴素的做法。然而,其复杂度很劣:尤其是在第 1 1 1 轮排序前,可达的 01 01 01 串有 2 n 2^n 2n 个,无法直接维护。
考虑做一个剪枝——只考虑那些有意义的位置。具体的,我们仅考虑所有在之前的排序中出现过至少一次的位置。例如, n = 40 n=40 n=40,且前两轮排序分别对 { 3 , 5 , 7 , 8 } \{3,5,7,8\} { 3,5,7,8} 和 { 2 , 5 , 6 , 7 } \{2,5,6,7\} { 2,5,6,7} 进行了排序,那么我们在第 2 2 2 轮排序结束前仅考虑第 2 , 3 , 5 , 6 , 7 , 8 2,3,5,6,7,8 2,3,5,6,7,8 位。
剪枝后,我们该如何进行扩展呢?令 S i S_i Si 为第 i i i 轮排序的位集, S i ′ S'_i Si′ 为在 S i S_i Si 中出现然而没有在 S 1 , S 2 , ⋯ , S i − 1 S_1,S_2,\cdots,S_{i-1} S1,S2,⋯,Si−1 中出现的位集。显然,只需要确定了序列的 S i ′ S'_i Si′ 中 0 0 0 的数量,我们就能唯一确定的扩展后的 01 01 01 序列。于是我们从 0 0 0 枚举到 ∣ S i ′ ∣ |S'_i| ∣Si′∣,计算出新的 01 01 01 序列扩展出去即可。
考虑时间复杂度。注意到,在第 i i i 轮排序中, P P P 的大小翻了 ∣ S i + 1 ′ ∣ + 1 |S'_{i+1}|+1 ∣Si+1′∣+1 倍,所以时间复杂度为
O ( ( ∣ S 1 ′ ∣ + 1 ) ( ∣ S 2 ′ ∣ + 1 ) ( ∣ S 3 ′ ∣ + 1 ) ⋯ ( ∣ S k ′ ∣ + 1 ) ) O((|S'_1|+1)(|S'_2|+1)(|S'_3|+1)\cdots(|S'_k|+1)) O((∣S1′∣+1)(∣S2′∣+1)(∣S3′∣+1)⋯(∣Sk′∣+1))
由于所有 ∣ S i ′ ∣ + 1 |S'_i|+1 ∣Si′∣+1 的和不超过 n + k n+k n+k,而 S i ′ S'_i Si′ 共有 k k k 个,所以复杂度等价于——选定 k k k 个和为 n + k n+k n+k 的数使得它们的乘积最大。可以发现,其为 O ( ( n + k k ) k ) O((\frac {n+k} k)^k) O((kn+k)k)。
综上所述,我们采用二进制数+位运算加速转移,单次 O ( 1 ) O(1) O(1),总复杂度 O ( ( n + k k ) k ) O((\frac {n+k} k)^k) O((kn+k)k)。本题被解决。
Code
注意要特判 n = 1 n=1 n=1,答案总为 ACCEPTED
。否则(即 n ≠ 1 n \neq 1 n=1 的情况),若没能覆盖到所有的位置,那么答案总为 REJECTED
。
yysy,下面的代码非常难懂而且我不想解释,所以不建议阅读。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxl=45;
int read(){
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') w=-w;ch=getchar();}
while (ch>='0'&&ch<='9'){
s=s*10+(ch^'0');ch=getchar();}
return s*w;
}
int n,k,cover_tot,flag=1,lb;
int p[maxl],s[maxl],bc_s[maxl],set01[maxl][maxl],good_list[maxl];
bool getbit(int x,int y){
return x&(1ll<<(y-1));}
bool judge_state(int val){
int p=lower_bound(good_list,good_list+n+1,val)-good_list;
return (good_list[p]==val);
}
void dfs(int now,int state){
if (now==k+1){
if (!judge_state(state)) flag=0;
return;
}
int occ=(p[now]^s[now]),cnt0,new_state=state;
cnt0=__builtin_popcountll(((1ll<<n)-1-state)&occ);
state=(state&((1ll<<n)-1-p[now]));
for (int i=0;i<=bc_s[now];i++){
new_state=(state|set01[now][cnt0]);
dfs(now+1,new_state),cnt0++;
}
}
signed main(){
n=read(),k=read();
if (n==1) return puts("ACCEPTED"),0;
for (int i=1;i<=n;i++) good_list[i]=good_list[i-1]+(1ll<<(i-1));
for (int i=0;i<=n;i++) good_list[i]=(1ll<<n)-1-good_list[i];
sort(good_list,good_list+n+1);
for (int i=1;i<=k;i++){
int len=read();
for (int j=1;j<=len;j++){
int x=read();
p[i]+=(1ll<<(x-1));
}
}
for (int i=1;i<=k;i++){
int len=0,all=0;
set01[i][len]=0;
for (int j=1;j<=n;j++){
if (getbit(p[i],j)){
len++;
set01[i][len]=set01[i][len-1]+(1ll<<(j-1));
all+=(1ll<<(j-1));
}
}
for (int j=0;j<=len;j++) set01[i][j]=all-set01[i][j];
}
for (int i=1;i<=k;i++){
for (int j=1;j<=n;j++){
if (getbit(p[i],j)==1&&getbit(cover_tot,j)==0)
s[i]+=(1ll<<(j-1));
}
for (int j=1;j<=n;j++) bc_s[i]+=getbit(s[i],j);
cover_tot|=p[i];
}
if (cover_tot!=((1ll<<n)-1)) return puts("REJECTED"),0;
dfs(1,0);
if (flag==0) puts("REJECTED");
else puts("ACCEPTED");
return 0;
}