[Problem Discription] \color{blue}{\texttt{[Problem Discription]}} [Problem Discription]
[Solution] \color{blue}{\texttt{[Solution]}} [Solution]
这是道好题。
建图,对每一个 i → p i i \to p_{i} i→pi 都建立一个有向边,就可以得到一个 n n n 个点 n n n 条边组成的图。题目所需要满足的条件可以等价于从任何一个 S i S_{i} Si 为 1 \texttt{1} 1 的边出发走 l l l 步都能回到自身,且图中不存在自环。
显然,如果存在一个大小为 g g g 的环(点数一定等于边数,因为每个点的入度和出度均为 1 1 1),且 g g g 为 l l l 的约数,那么这个环上的任意一点均可以走 l l l 步后回到自身。
根据这个想法,我们就可以判定是否有解:把 l l l 所有的大于 1 1 1 的约数找出来,假设记为 d 1 , d 2 , d 3 , ⋯ , d m d_{1},d_{2},d_{3},\cdots,d_{m} d1,d2,d3,⋯,dm,有解等价于存在自然数(包括 0 0 0)序列 a 1 … m a_{1 \dots m} a1…m,满足:
∑ i = 1 m a i d i = β \sum\limits_{i=1}^{m} a_{i}d_{i}=\beta i=1∑maidi=β
其中 β \beta β 是 k k k 到 n n n 的任意一个整数( k k k 表示 S S S 为 1 \texttt{1} 1 的点的个数)。 β \beta β 表示我们需要将有限制的 k k k 个点和无限制的点中任意的 ( β − k ) (\beta - k) (β−k) 个一起组成若干个大小为 d i d_{i} di 的环(如果 a i a_{i} ai 为 0 0 0,则不存在大小为 d i d_{i} di 的环)。假设环的大小分别为 D 1 , D 2 , ⋯ , D M D_{1},D_{2},\cdots,D_{M} D1,D2,⋯,DM。
显然,我们用背包即可判断是否有解。同时还可以知道我们需要构造哪些大小的环。
注意: β \beta β 不能等于 n − 1 n-1 n−1。如果 β = n − 1 \beta=n-1 β=n−1,那么剩下的那一个没有限制的点需要形成自环,不合题意。
如果判断有解,剩下的事情就比较简单了:只要依次挑出 D i D_{i} Di 个点首尾相连形成大小为 D i D_{i} Di 的环即可。这部分比较简单,具体看代码。
[Notice] \color{blue}{\texttt{[Notice]}} [Notice]
- 如果 k = 0 k=0 k=0,即没有有限制的点,那么一定有解( 1 → 2 , 2 → 3 , ⋯ , n → 1 1 \to 2,2 \to 3, \cdots, n \to 1 1→2,2→3,⋯,n→1 即可)。不然会 WA on test 10 10 10。
- 只有 ( n − 1 ) (n-1) (n−1) 是不合要求的,大小为 n n n 的环是符合要求的。写代码时要注意这一点,不然会 WA on test 13 13 13。
- dp 的时候找到了解就要及时退出,减少代码运行量,不然会 TLE on test 14 14 14。
- 记得及时清空数组,毕竟是多测。
Code \color{blue}{\text{Code}} Code
const int N=5e5+100;
bool f[N],limit[N];
char s[N];long long l;
int T,n,k,p[N],lst[N];
int Div[N],dcnt,beg;
int unlimit[N];
void initdata(){
for(int i=1;i<=n;i++){
p[i]=lst[i]=Div[i]=0;
f[i]=limit[i]=false;
unlimit[i]=0;
}
k=dcnt=beg=0;
}
int main(){
scanf("%d",&T);
while (T--){
scanf("%d%lld",&n,&l);
scanf("%s",s+1);
initdata();
for(int i=1;i<=n;i++)
if (s[i]=='1'){
k++;//统计有限制的点的个数
limit[i]=true;
}
for(int i=2;i<=n;i++)
if (l%i==0&&i!=n-1){
f[i]=true;
Div[++dcnt]=i;
lst[i]=i;//别忘了这个
}
for(int i=1,flag=1;i<=dcnt&&flag;i++)
for(int j=Div[i];j<=n;j++)
if (f[j-Div[i]]&&!f[j]){
f[j]=true;
lst[j]=Div[i];
if (k<=j&&j<=n&&j!=n-1){
flag=1;break;//及时退出
}
}
for(int i=k;i<=n;i++)
if (f[i]&&i!=n-1){
beg=i;//对应解析里的 beta
break;
}
if (beg!=0){
for(int i=1,l=0;l<n-beg;i++)
if (!limit[i]) unlimit[++l]=i;
if (beg!=n){
for(int i=1;i<n-beg;i++)
p[unlimit[i]]=unlimit[i+1];
p[unlimit[n-beg]]=unlimit[1];
}//多余的无限制的点也要连成环(不过环的大小没限制啦是真的)
int l=0,r=0,cnt=0,tmp=beg;
for(int i=1;i<=n;i++)
if (!p[i]){
if ((++cnt)==lst[tmp]){
p[r]=i;
tmp-=lst[tmp];
cnt=0;
}
p[i]=l;l=i;
if (cnt==1) r=i;
}//构造解
}
if (k==0){
for(int i=1;i<n;i++)
printf("%d ",i+1);
printf("1\n");
}
else if (!beg) printf("-1\n");
else{
for(int i=1;i<=n;i++)
printf("%d%c",p[i],(i==n?'\n':' '));
}
}
return 0;
}
Wish You Good Luck!