说明:对于两个变换序列S和T,如果存在p<N,满足对于i=0,1,……p-1,Si=Ti且Sp<Tp,我们称S比T字典序小。
输入文件transform.in的第一行包含一个整数N,表示序列的长度。接下来的一行包含N个整数Di,其中Di表示i和Ti之间的距离。
输出文件为transform.out。
如果至少存在一个满足要求的变换序列T,则输出文件中包含一行N个整数,表示你计算得到的字典序最小的T;否则输出”No Answer”(不含引号)。注意:输出文件中相邻两个数之间用一个空格分开,行末不包含多余空格。
5
11 2 2 1
12 4 0 3
20%的数据中N≤50;
60%的数据中N≤500;
100%的数据中N≤10000。
解法1:
可以非常巧妙地运用二分图逆序匹配,即从最后一个点逆着匹配到第一个点,其中枚举的时候保证从小到大,那么这样匹配出来一定是字典序最小的匹配。
Accode:
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <string> const int maxN = 20010; struct Edge { int v; Edge *next; Edge() {} Edge(int v, Edge *next): v(v), next(next) {} } *edge[maxN]; bool marked[maxN]; int Link[maxN], d[maxN], sta[maxN], top, n; bool Find(int u) { for (Edge *p = edge[u]; p; p = p -> next) if (!marked[p -> v]) { marked[sta[top++] = p -> v] = 1; if (Link[p -> v] == -1 || Find(Link[p -> v])) {Link[p -> v] = u, Link[u] = p -> v; return 1;} } return 0; } int main() { freopen("transform.in", "r", stdin); freopen("transform.out", "w", stdout); scanf("%d", &n); for (int i = 0; i < n; ++i) { scanf("%d", d + i); d[i] %= n; if (d[i] > n >> 1) d[i] = n - d[i]; if (!d[i]) edge[i] = new Edge(i + n, edge[i]); else if (i + d[i] == i - d[i] + n) edge[i] = new Edge((i + d[i]) % n + n, edge[i]); else { if (i - d[i] < 0) edge[i] = new Edge(i - d[i] + (n << 1), edge[i]); if (i + d[i] < n) edge[i] = new Edge(i + d[i] + n, edge[i]); if (i - d[i] > -1) edge[i] = new Edge(i - d[i] + n, edge[i]); if (i + d[i] >= n) edge[i] = new Edge(i + d[i], edge[i]); } } //建图时先建较大边,再建较小边(链表插入过后会反过来)。 memset(Link, 0xff, sizeof Link); for (int i = n - 1; i > -1; --i) { top = 0; if (!Find(i)) {printf("No Answer\n"); return 0;} for (int i = 0; i < top; ++i) marked[sta[i]] = 0; } for (int i = 0; i < n; ++i) printf("%d ", Link[i] - n); return 0; }
解法2:先找出一个可行的匹配,再将其修正为字典序最小的匹配。
若从小到大修正,则在修正的时候加一个限制,即不能修改之前匹配过的点。
Accode:
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <string> const int maxN = 20010; struct Edge { int v; Edge *next; Edge() {} Edge(int v, Edge *next): v(v), next(next) {} } *edge[maxN]; bool marked[maxN]; int Link[maxN], d[maxN], sta[maxN], Lim = -1, top, n; bool Find(int u) { if (u <= Lim) return 0; for (Edge *p = edge[u]; p; p = p -> next) if (!marked[p -> v]) { marked[sta[top++] = p -> v] = 1; if (Link[p -> v] == -1 || Find(Link[p -> v])) {Link[p -> v] = u, Link[u] = p -> v; return 1;} } return 0; } int main() { freopen("transform.in", "r", stdin); freopen("transform.out", "w", stdout); scanf("%d", &n); for (int i = 0; i < n; ++i) { scanf("%d", d + i); d[i] %= n; if (d[i] > n >> 1) d[i] = n - d[i]; if (!d[i]) edge[i] = new Edge(i + n, edge[i]); else if (i + d[i] == i - d[i] + n) edge[i] = new Edge((i + d[i]) % n + n, edge[i]); else { if (i - d[i] < 0) edge[i] = new Edge(i - d[i] + (n << 1), edge[i]); if (i + d[i] < n) edge[i] = new Edge(i + d[i] + n, edge[i]); if (i - d[i] > -1) edge[i] = new Edge(i - d[i] + n, edge[i]); if (i + d[i] >= n) edge[i] = new Edge(i + d[i], edge[i]); } } memset(Link, 0xff, sizeof Link); for (int i = 0; i < n; ++i) { top = 0; if (!Find(i)) {printf("No Answer\n"); return 0;} for (int i = 0; i < top; ++i) marked[sta[i]] = 0; } for (int i = 0; i < n; ++i) if (Link[i] - edge[i] -> v) //若当前匹配i的并不是较小的边,则尝试修正。 { top = 0; Lim = i; int tmp = Link[edge[i] -> v]; Link[i] = edge[i] -> v; Link[edge[i] -> v] = i; Link[edge[i] -> next -> v] = Link[tmp] = -1; if (!Find(tmp)) { Link[i] = edge[i] -> next -> v; Link[edge[i] -> next -> v] = i; Link[edge[i] -> v] = tmp; Link[tmp] = edge[i] -> v; } //修正不成功则还原。 for (int i = 0; i < top; ++i) marked[sta[i]] = 0; } for (int i = 0; i < n; ++i) printf("%d ", Link[i] - n); return 0; }