cf传送门
题解链接
这题比赛的时候完全没有头绪,题解也是看了半天才看懂。
首先想,给定两个(0 sim n - 1)的排列(a,b),将(a)中的元素两两交换得到(b),怎么求最少的交换次数?
对于(a)中的每一个数(a_i),都可以找到在(b)中的“目标位置”(j)(即(a_i=b_j)),从(i)向(j)连边,会发现构成了一张有若干个环的图。那么最优的交换策略一定是环与环之间互不干扰,且环内的交换次数是 环大小-1,将这些加起来,就得到了最少的交换次数是(n-x)((x)是环的个数)。
所以我们能用(O(n))的时间求出两个序列最少需要多少次交换才能相互转换。
但是这道题,(O(n^2))显然不行,接下来就是这道题的独特之处了。
假设排列(a,b)中已经有(x)个满足(a_i=b_i),那么至少要交换(frac{n-x}{2})次。而题中有最多(m)次的这个限制,那么只有(frac{n-x}{2}leqslant m),即(x geqslant n - 2m)的排列才可能满足题目的条件,因此我们如果只对这些排列进行检查,时间复杂度可能会减少。
那究竟是否可行呢?
答案是可行的。因为每一个排列对应的(x)是很好求出来的:因为原序列是从(1 sim n),那么对于新序列的一个(p_i),只有让原序列移动((i-p_i)mod n)次后才能相等,所以我们可以直接扫一遍就求出所有的(x_k)了,而且必有(sumlimits_{k=0}^{n - 1} x_k=n).
结合上面的(x geqslant n - 2m)和(m leqslant frac{n}{3}),有(x geqslant n - frac{2}{3}n = frac1{3}n).因此最多只有(3)个符合条件的(x_k)!
那么我们只要找到这(3)个(x)对应的(k),再(O(n))判断就行了!
代码中之所以开了很多vector,是因为(n,m)可能很小,(数据组数)t$可能很大,memset数组会超时。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<queue>
#include<assert.h>
#include<ctime>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 3e5 + 5;
In ll read()
{
ll ans = 0;
char ch = getchar(), las = ' ';
while(!isdigit(ch)) las = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(las == '-') ans = -ans;
return ans;
}
In void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
#endif
}
int n, m, a[maxn];
In int calc_circle(vector<int> b)
{
int ret = 0;
vector<int> vis(n);
for(int i = 0; i < n; ++i) if(!vis[i])
{
int x = i;
while(!vis[x]) vis[x] = 1, x = b[x];
++ret;
}
return ret;
}
In bool judge(int K)
{
vector<int> b;
for(int i = K; i < n; ++i) b.push_back(a[i]);
for(int i = 0; i < K; ++i) b.push_back(a[i]);
return n - calc_circle(b) <= m;
}
In void solve()
{
vector<int> num(n), ans;
for(int i = 0; i < n; ++i) num[i - a[i] + (i < a[i]) * n]++;
for(int i = 0; i < n; ++i)
if(num[i] + 2 * m >= n && judge(i)) ans.push_back(i);
write(ans.size());
for(auto x : ans) space, write(x); enter;
}
int main()
{
// MYFILE();
int T = read();
while(T--)
{
n = read(), m = read();
for(int i = 0; i < n; ++i) a[i] = read() - 1;
solve();
}
return 0;
}