前言
本博客偏 math
。
Valera and Swaps
构造一个操作序列,每次 swap(p[i],p[j])
,使得最终序列经过最少 k
次操作后能使每个 p[i]=i
。输出字典序最小。
结论1.
连边 i->p[i]
,则 操作数=n-环的个数
。
正确性显然。
分类讨论:
- 若
cnt<k
则输出k-cnt
,有如下合并环操作:选取pos[i]!=pos[j]
,执行swap(p[i],p[j])
。 - 若
cnt>k
则输出cnt-k
,有如下拆分环操作:选取pos[i]==pos[j],i!=j
,执行swap(p[i],p[j])
。
方便起见,对于一个环,我们把首位打一个标记。
时间复杂度 O(n^2)
。
#include<bits/stdc++.h>
#define ll long long
const int mx=3005;
using namespace std;
int n,m,k,cnt,cnt2,p[mx],prt[mx],pos[mx],cord[mx];
vector<int> vec[mx];
//方便起见,对于一个环,我们把首位打一个标记。
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
scanf("%d",&k);
//所需次数 = n - 环的个数
for(int i=1;i<=n;i++) {
if(pos[i]) continue;
int j=i; m=0; cnt++; cord[j]=1; //首位打标记
while(!pos[j]) prt[m++]=j,pos[j]=cnt,j=p[j];
}
cnt=n-cnt; //细节问题
if(k<cnt) {
printf("%d\n",cnt-k);
// cout<<"YES"<<endl;
for(int i=1;i<=cnt-k;i++) {
for(int j=1;j<=n;j++) vec[j].clear(),pos[j]=0;
cnt2=0;
for(int j=1;j<=n;j++) {
if(cord[j]) {
cnt2++;
for(int k=j;!pos[k];k=p[k]) pos[k]=cnt2;
}
}
for(int j=1;j<=n;j++) {
vec[pos[j]].push_back(j);
// cout<<pos[j]<<" ";
}
// cout<<endl;
for(int j=1;j<=n;j++) cord[j]=0; //重新标号
//相同环分离
for(int j=1;j<=n;j++) {
if(vec[pos[j]].size()>1) {
int k=vec[pos[j]][1];
swap(p[j],p[k]);
cord[j]=cord[k]=1;
vec[pos[j]].clear();
printf("%d %d ",j,k);
break;
}
}
for(int j=1;j<=n;j++) {
if(vec[j].size()) {
cord[vec[j][0]]=1;
vec[j].clear();
}
}
}
}
else if(k==cnt) {
printf("0");
}
else {
printf("%d\n",k-cnt);
//不同环合并
for(int i=1;i<=k-cnt;i++) {
for(int j=1;j<=n;j++) vec[j].clear(),pos[j]=0;
cnt2=0;
for(int j=1;j<=n;j++) {
if(cord[j]) {
cnt2++;
for(int k=j;!pos[k];k=p[k]) pos[k]=cnt2;
}
}
for(int j=2;j<=n;j++) {
//不在同一个环
if(pos[1]!=pos[j]) {
cord[pos[j]]=0; //
swap(p[1],p[j]);
printf("1 %d ",j);
cord[1]=1;
break;
}
}
}
}
}
[NOIP2005 提高组] 篝火晚会
题意看错了,以为只能交换连续的 m
个位置。。。
说白了你有两个环,问你经过最少几次操作后一模一样。
显然 a->b
和 b->a
等价,假设样例给的环是这样:
显然也可以逆时针构造,所以要跑两次:
环上问题考虑破成 2n
链: 1 4 2 3 1 4 2 3
结论1:
最小操作次数 n-k
,其中 k
表示相对位相同的个数。
举例 1 4 2 3 , k=1
; 1 3 2 4 , k=2
。所以样例答案为 2
。
暴力枚举初始点 i
,然后暴力匹配,时间复杂度 O(n^2)
。
显然可以用桶优化,如下图:
时间复杂度 O(n)
。显然吧 qwq
。
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
const int mx = 1e5 + 5;
int n, m, res(INF), prt[mx], l[mx], r[mx], dig[mx], pos[mx], a[mx], b[mx], c[mx], tong[mx];
void dfs(int x, int nxt) {
prt[++m] = x;
pos[x] = 1;
if (pos[nxt])
return;
dfs(nxt, (x == l[nxt]) ? r[nxt] : l[nxt]);
}
//似乎答案就是逆序对
void solve() {
// for(int i=1;i<=n;i++) printf("%d ",prt[i]);
// printf("\n");
memset(tong,0,sizeof(tong));
for(int j = 1; j <= n; j ++) {
tong[(j-prt[j]+n)%n]++;
}
for(int j = 0; j < n; j++)
res=min(res,n-tong[j]);
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) a[i] = a[i+n] = i;
for (int i = 1; i <= n; i++) {
scanf("%d%d", &l[i], &r[i]);
}
for (int i = 1; i <= n; i++) {
if (l[l[i]] != i && r[l[i]] != i || l[r[i]] != i && r[r[i]] != i) {
printf("-1");
return 0;
}
}
//向左走
dfs(1, l[1]);
solve();
m = 0;
memset(pos, 0, sizeof(pos));
//向右走
dfs(1, r[1]);
solve();
printf("%d", res);
}
Moodular Arithmetic
首先观察,样例 9=3*3
且 25=5*5
,猜测答案就是 n^k
。
再仔细观察,发现 k
就是环的个数。但是要特判 k=1
的情况,此时答案就是 n^n
。
至于连边,你把 f
函数的自变量 x->k*x%p
连边,很容易看出是完全剩余系吧。
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
const int mx = 1e5 + 5;
int n, m, res(INF), prt[mx], l[mx], r[mx], dig[mx], pos[mx], a[mx], b[mx], c[mx], tong[mx];
void dfs(int x, int nxt) {
prt[++m] = x;
pos[x] = 1;
if (pos[nxt])
return;
dfs(nxt, (x == l[nxt]) ? r[nxt] : l[nxt]);
}
//似乎答案就是逆序对
void solve() {
// for(int i=1;i<=n;i++) printf("%d ",prt[i]);
// printf("\n");
memset(tong,0,sizeof(tong));
for(int j = 1; j <= n; j ++) {
tong[(j-prt[j]+n)%n]++;
}
for(int j = 0; j < n; j++)
res=min(res,n-tong[j]);
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) a[i] = a[i+n] = i;
for (int i = 1; i <= n; i++) {
scanf("%d%d", &l[i], &r[i]);
}
for (int i = 1; i <= n; i++) {
if (l[l[i]] != i && r[l[i]] != i || l[r[i]] != i && r[r[i]] != i) {
printf("-1");
return 0;
}
}
//向左走
dfs(1, l[1]);
solve();
m = 0;
memset(pos, 0, sizeof(pos));
//向右走
dfs(1, r[1]);
solve();
printf("%d", res);
}