【题解】 [NOI2009]变换序列 (二分图匹配)

懒得复制,戳我戳我

Solution:

  • 这个题面出的很毒瘤,读懂了其实是个板子题qwq
  • 题面意思:有个\(0\)\(N-1\)的数列是由另一个数列通过加减得到的,相当于将\(A_i\)变成\(i\),每一步的代价计算就是\(min(A_i-i,N-(A_i-i))\),并且\(A_i\left(0<=i<N\right)\)互不相同,读入代价,要求字典序最小的满足要求的数列
  • 我们设读入的为\(w[i]\)
  • 思路其实很简单,\(i\)只可能是由\(i-w[i]\) 或者 \(i+w[i]\) 或者 \(i+N-w[i]\) 或者 \(i-N+w[i]\),然后我们把符合范围\(0\)\(N-1\)的对应点从大到小建图,这样可以保证搜的时候是从小的点开始
  • 然后就从\(N-1\)\(0\)进行二分图匹配,如果无法匹配就输出\(No Answer\),这样从后到前匈牙利算法去做,保证越前面的匹配的数是最小的。
  • 因为\(be[i]\)中存的是\(i\)数字对应变成的数字是什么,所以反过来存一下输出就好啦

    主要是想字典序最小的地方有点emmm神奇,其他的地方还是比较显然的


Code:

//It is coded by Ning_Mew on 3.17
#include<bits/stdc++.h>

using namespace std;

const int maxn=1e5+7;

bool vis[maxn];
int n,w[maxn],be[maxn],ans[maxn];
int head[maxn],cnt=0;
struct Edge{
  int nxt,to;
}edge[maxn*4];
priority_queue<int>q;

void add(int from,int to){
  edge[++cnt].nxt=head[from];
  edge[cnt].to=to;
  head[from]=cnt;
}
bool find(int k){
  for(int i=head[k];i!=0;i=edge[i].nxt){
    int v=edge[i].to;
    if(!vis[v]){
      vis[v]=true;
      if(be[v]==-1||find(be[v])){be[v]=k;return true;}
    }
  }return false;
}
int main(){
  scanf("%d",&n);
  while(!q.empty())q.pop();
  for(int i=0;i<n;i++){
    scanf("%d",&w[i]);
    q.push(i-w[i]);  q.push(i+w[i]);
    q.push(i+n-w[i]);q.push(i-n+w[i]);
    while(!q.empty()){
      int box=q.top();q.pop();
      //cout<<box<<endl;
      if(box>=0&&box<n)add(i,box);//cout<<i<<' '<<box<<endl;
    }
  }
  memset(be,-1,sizeof(be));
  for(int i=n-1;i>=0;i--){
    memset(vis,false,sizeof(vis));
    if(find(i));
    else{printf("No Answer\n");return 0;}
  }
  for(int i=0;i<n;i++)ans[be[i]]=i;
  for(int i=0;i<n;i++)printf("%d ",ans[i]);printf("\n");
  return 0;
}

转载于:https://www.cnblogs.com/Ning-Mew/p/8590580.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值