洛谷P1963 [NOI2009] 变换序列 题解
题目链接:P1963 [NOI2009] 变换序列
题意:
对于 $N$ 个整数 $0, 1, \cdots, N-1$ ,一个变换序列 $T$ 为这 $N$ 个整数的一个置换。
定义 $x$ 和 $y$ 之间的距离($x,y = 0,1,\cdots,n-1$)
给定每个 $i$ 和 $T_i$ 之间的距离 $D(i,T_i)$
你需要求出一个满足要求的且字典序最小的变换序列 $T$ 。
说明:对于两个变换序列 $S$ 和 $T$ ,如果存在 $p<N$ ,满足对于$i=0,1,\cdots,p-1$, $S_i=T_i$ 且 $S_p<T_p$ ,我们称 $S$ 比 $T$ 字典序小。
原题真的废话连篇还特别丑,已经删减过了。输入格式:
第一行包含一个整数 $N$ ,表示序列的长度。接下来的一行包含 $N$ 个整数 $D_i$ ,其中 $D_i$ 表示 $i$ 和 $T_i$ 之间的距离。
输出格式:
如果至少存在一个满足要求的变换序列 $T$ ,则输出文件中包含一行 $N$ 个整数,表示你计算得到的字典序最小的 $T$ ;否则输出
No Answer
(不含引号)。注意:输出文件中相邻两个数之间用一个空格分开,行末不包含多余空格。数据范围:
对于 $100\%$ 的数据,满足:$N\le 10^4$。
这道题很好的考察了对匈牙利算法的理解。
注意到这是一个置换,即满足双射的关系,考虑建立二分图。
对于每个左侧 $x$ ,我们可以将它与右侧 $x+D_x,~x-D_x,~x-n+D_x,~x+n-D_x$ 连边
实际上,$x+D_x \le n$ 时,$x-n+D_x \le 0$ ,因此这四条边里面只会有两条存在
不难发现他们就是 $(x+D_x) \bmod n,~(x+n-D_x) \bmod n$
因此如果这张图存在二分图完美匹配,就一定有解。
但是题目要求的是字典序最小的解,这个有些麻烦。
考虑匈牙利算法的过程。
对于顺序枚举的每个点 $u$ ,它会遍历它所有的边 $(u,v_i)$ (按照存储顺序)并尝试匹配。
如果 $v_i$ 没有被匹配过,那么它会被直接匹配。
如果 $v_i$ 存在匹配的左部点,那就递归地“强行”让之前的点换匹配。
直到 $v_i$ 可以被匹配,或者发现之前的点改不了匹配(也就是 $v_i$ 无论如何也匹配不了)
不难发现匈牙利算法会使每个后匹配的点 $u$ ,优先匹配它最前的一条边 $(u,v_i)$ 的 $v_i$ 。
如果把 $u$ 的每条边 $(u,v)$ ,按 $v$ 的大小顺序排序后存储
那么对于每个后匹配的点 $u$ ,它会优先匹配最小的一个 $v_i$ 。
注意到字典序是第一个值优先,而匈牙利算法是最后一个匹配的优先
因此我们倒着匹配就可以得到字典序最小的解
时间复杂度 $O(nm)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e5+15))
int n,to[N][2],p[N],g[N],used[N];
bool dfs(int u,int tim)
{
for(int t=0; t<2; t++)
{
int v=to[u][t];
if(used[v] != tim) // !used[v];
{
used[v] = tim; // used[v]=1;
if(p[v] == -1 || dfs(p[v],tim))
{ p[v] = u; g[u] = v; return 1; }
}
}
return 0;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n;
for(int i=0,x; i<n; i++)
{
cin >> x;
to[i][0] = (i + x) % n;
to[i][1] = (i - x + n) % n;
if(to[i][0] > to[i][1]) swap(to[i][0], to[i][1]); // v1 < v2
}
memset(p,-1,sizeof(p));
int tim=0,i;
for(i=n-1; i>=0; i--) if(!dfs(i,++tim)) break;
if(~i) cout << "No Answer\n";
else for(int i=0; i<n; i++) cout << g[i] << " \n"[i==n-1];
return 0;
}