题目大意:给定一个二分图,记\(u\)点的匹配点为\(match(u)\),求一匹配方案使得\(match(u)\quad u \in X\)序列字典序最小
二分图匹配,匈牙利
题面有点乱七八糟(还不是我菜)刚开始没有读懂,看到一堆柿子就吓得关掉了
读懂题目以后我们就可以建出来一个二分图了.
对于二分图匹配而言,我们可以用\(Dinic\)算法求解,但是如果遇到要输出方案并且还要字典序最小就很难受了(反正我是不会)。然后跑去日报现学了下匈牙利算法。
对于匈牙利算法,它的实质实际上就是一个搜索。
inline bool find(int u){
for(int v : G[u]){
if(!vis[v]){
vis[v] = 1;
if(match[v] == -1 || find(match[v])){
match[u] = v;
match[v] = u;
return true;
}
}
}
return false;
}
在这之中我们可以看到,每次贪心的为\(u\)点寻找匹配点,如果\(v\)点没有被匹配,或者是原来匹配\(v\)点的\(u'\)点可以选择一个新点\(v'\)来匹配的话,我们就为\(u\)点找到了一个匹配点,并且新增了一条匹配边
那么要求字典序最小,我们可不可以用一个\(set\)代替\(vector\)存图,这样跑出来的就是字典序最小的匹配呢?
可以是可以,但是你必须得倒序进行匹配。
为什么?因为为后来点寻找匹配时可能会让前面的点重新寻找匹配点,导致丢失最优解
试想一下:你前面的点贪心找到了最优匹配点,但是你后面的点也想要这个匹配点,于是你前面的点只能重新寻找匹配点,导致丢失解
我们倒序枚举就可以保证不丢失最优解,前面的点拿走了后面点的匹配点,但是没有关系,这样从前往后的字典序还是最小的
然后注意如果有任何一个点找不到匹配点就是\(No\;Answer\)(所求的是完美匹配)
#include <cstdio>
#include <cstring>
#include <cctype>
#include <set>
using namespace std;
const int maxn = 20480;
inline int read(){
int x = 0;char c = getchar();
while(!isdigit(c))c = getchar();
while(isdigit(c))x = x * 10 + c - '0',c = getchar();
return x;
}
set<int> G[maxn];
inline void addedge(int from,int to){
G[from].insert(to);
}
int n,match[maxn],vis[maxn],d[maxn];
inline bool find(int u){//匈牙利
for(int v : G[u]){
if(!vis[v]){
vis[v] = 1;
if(match[v] == -1 || find(match[v])){
match[u] = v;
match[v] = u;
return true;
}
}
}
return false;
}
inline int to(int x){//得到变换之后的点
if(x < 0)return x + n;
if(x >= n)return x - n;
return x;
}
int main(){
n = read();
for(int i = 0;i < n;i++)d[i] = read(),addedge(i,to(i - d[i]) + n),addedge(i,to(i + d[i]) + n);//连边
memset(match,-1,sizeof(match));//因为存在0点,所以只能用-1来表示没有匹配点
for(int i = n - 1;i >= 0;i--){
memset(vis,0,sizeof(vis));
if(!find(i))return printf("No Answer\n"),0;
}
for(int i = 0;i <= n - 1;i++)
printf("%d%c",match[i] - n,i == n - 1 ? '\n' : ' ');
return 0;
}