【二分图匹配】BZOJ1562-[NOI2009] 变换序列
【题目大意】
对于0,1,…,N-1的N个整数,给定一个距离序列D0,D1,…,DN-1,定义一个变换序列T0,T1,…,TN-1使得每个i,Ti的环上距离等于Di。一个合法的变换序列应是0,1,…,N-1的一个排列,任务是要求出字典序最小的那个变换序列。(概括by:BYVoid)
【思路】
我们意识流现象一下。平时二分图匹配我们会根据从前往后,后面的会占用前方匹配,使得前方节点需要重新匹配。所以得出结论——后面的点会连到比较小的。那么我们就可以yy出这样一个做法:把每一个点连的边从小到大排序,从后往前进行匈牙利算法。
一个剪枝:有一些节点存在唯一的匹配,可以直接预处理好这些匹配唯一的点,余下的点再跑匈牙利算法。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 const int MAXN=10000+500; 8 vector<int> E[MAXN],rE[MAXN]; 9 int n,in[MAXN],visfr[MAXN],visto[MAXN],vis[MAXN],lk[MAXN],ans[MAXN],cnt=0; 10 11 void addedge(int u,int v) 12 { 13 E[u].push_back(v); 14 rE[v].push_back(u); 15 } 16 17 int find(int u) 18 { 19 for (int j=0;j<E[u].size();j++) 20 { 21 int i=E[u][j]; 22 if (!vis[i] && !visto[i]) 23 { 24 vis[i]=1; 25 if (!lk[i] || find(lk[i])) 26 { 27 lk[i]=u; 28 ans[u]=i; 29 return 1; 30 } 31 } 32 } 33 return 0; 34 } 35 36 void init() 37 { 38 memset(visfr,0,sizeof(visfr)); 39 memset(visto,0,sizeof(visto)); 40 memset(in,0,sizeof(in)); 41 memset(lk,0,sizeof(lk)); 42 scanf("%d",&n); 43 for (int i=1;i<=n;i++) 44 { 45 int d; 46 scanf("%d",&d); 47 int a=i+d;if (a>n) a-=n; 48 int b=i-d;if (b<1) b+=n; 49 if (a<b) addedge(i,a),addedge(i,b); 50 else if (a>b) addedge(i,b),addedge(i,a); 51 else if (a==b) 52 { 53 visfr[i]=visto[a]=1; 54 ans[i]=a; 55 cnt++; 56 } 57 if (a!=b) in[a]++,in[b]++;else in[a]++; 58 } 59 for (int i=1;i<=n;i++) 60 if (in[i]==1 && !visto[i]) 61 { 62 visto[i]=visfr[rE[i][0]]=1; 63 ans[rE[i][0]]=i; 64 cnt++; 65 } 66 } 67 68 void solve() 69 { 70 for (int i=n;i>=1;i--) 71 { 72 if (visfr[i]) continue; 73 memset(vis,0,sizeof(vis)); 74 if (find(i)) cnt++;else break; 75 } 76 if (cnt==n) 77 { 78 for (int i=1;i<=n;i++) 79 { 80 printf("%d",(ans[i]+n-1)%n); 81 if (i!=n) printf(" "); 82 } 83 } 84 else puts("No Answer"); 85 } 86 87 int main() 88 { 89 freopen("transform.in","r",stdin); 90 freopen("transform.out","w",stdout); 91 init(); 92 solve(); 93 return 0; 94 }