CF2157H
xujindong_ · · 题解
下面为了方便操作,把条件改成先降后升,最后反过来。
考虑一个增量法。在排列后面加一个
这启示我们用一个增量法,先跑出较小的
若
若
#include<bits/stdc++.h>
using namespace std;
int n,m;
vector<vector<int> >ans;
void solve(int n,int m){
for(int i=0;i<1<<n&&ans.size()<2000;i+=2){
vector<int>p;
for(int j=n-1;j>=0;j--)if(i>>j&1)p.push_back(j);
for(int j=0;j<n;j++)if(~i>>j&1)p.push_back(j);
int cyc=0;
bool vis[n]={0};
for(int j=0;j<n;j++){
if(vis[j])continue;
cyc++,vis[j]=1;
for(int k=p[j];k!=j;k=p[k])vis[k]=1;
}
if(cyc==m)ans.push_back(p);
}
}
int main(){
cin>>n>>m;
if(n<=18)solve(n,m);
else if(n-m<=9){
solve(2*(n-m)+1,n-m+1);
for(int i=0;i<ans.size();i++)while(ans[i].size()<n)ans[i].push_back(ans[i].size());
}
else{
solve(19,max(19-n+m,1));
for(int i=0;i<ans.size();i++){
while(ans[i].size()<n)ans[i].push_back(ans[i].size());
if(n-m>=19){
ans[i][0]=n-m;
for(int j=19;j<=n-m;j++)ans[i][j]--;
}
}
}
cout<<ans.size()<<'\n';
for(int i=0;i<ans.size();i++)for(int j=n-1;j>=0;j--)cout<<n-ans[i][j]<<(j?' ':'\n');
return 0;
}
一个有趣的事情是,如果写一个爆搜,优先往后面放,过程中算一下最终置换环数的上下界剪枝,发现直接过了。这是因为当
#include<bits/stdc++.h>
using namespace std;
int n,m,cnt,p[105];
bool vis[105];
ostringstream ans;
void dfs(int l,int r){
if(cnt>=2000)return;
int cyc=0;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++){
if(!p[i]||vis[i])continue;
int pos=p[i];
vis[i]=i;
while(pos&&pos!=i)vis[pos]=i,pos=p[pos];
cyc+=pos==i;
}
if(cyc+min(r-l+1,1)>m||cyc+r-l+1<m)return;
if(l>r){
cnt++;
for(int i=1;i<=n;i++)ans<<p[i]<<(i==n?'\n':' ');
return;
}
p[l]=n-r+l,dfs(l+1,r),p[l]=0;
if(l<r)p[r]=n-r+l,dfs(l,r-1),p[r]=0;
}
int main(){
return cin>>n>>m,dfs(1,n),cout<<cnt<<'\n'<<ans.str()<<'\n',0;
}