CF1553E Permutation Shift

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\)了,而且必有\(\sum\limits_{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;
}
posted @ 2021-07-23 21:09  mrclr  阅读(58)  评论(0编辑  收藏  举报