原题 : POI2009
题意 :
对于一个1-N的数组,每次你可以交换两个数ax与ay,代价为W(ax)+W(ay)。请问将(ai)变为(目标顺序bi)所需的最小代价是多少。
解析 :
将交换用有向线段连起来,得到的会是多个环,我们只要对这些环进行考虑即可
- 自环就不说了,无需考虑
- 如果是两个节点的环,那么直接交换即可
- 如果两个节点以上(设环节点数为n),就要考虑实际的交换问题了,因为每次交换必须使一个点到预期位置才能最小化代价,所以我们需要用代价最小的那个点作为媒介去交换其他点
- 若用环内的点作为媒介,那么应该是最小代价点n-1次,其他点1次
- 如果用环外的点,那么首先会选全局的最小点,用环内的最小点交换进来,在环内排序成功后再交换出去,所以,环内最小点2次,环外最小点n+1次,环内其他点1次
那么代码思路就是先判断环的大小,如果是2以上,那么先处理出所有点一次的代价,然后在(环内最小点n-2次,环内最小点1次+环外最小点n+1次)里选个小的就可以了
当然,不用考虑全局最小点在环内还是环外,因为在比较的时候一定会选最优的
代码:
#include<iostream>
#include<stdio.h>
using namespace std;
#define LL long long
const int MAXN=1e6+9;
LL v[MAXN];
int a[MAXN];//当前数组
int to[MAXN];
bool vis[MAXN];
int main(){
int n;
LL minn=1e9;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",&v[i]),minn=min(minn,v[i]);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++){int tmp;scanf("%d",&tmp);to[tmp]=i;}
LL ans=0;
for(int i=1;i<=n;i++){//遍历位置
int now=a[i];LL mi=v[now];
if(vis[now])continue;
int loop=1;
vis[now]=1;//标记大象
ans+=v[now];
while(1){
now=to[now];
now=a[now];
if(now==a[i])break;
loop++;
vis[now]=1;
mi=min(mi,v[now]);
ans+=v[now];
}
if(loop==1){
ans-=v[a[i]];
}
else if(loop==2){
;
}
else{
ans+=min(mi*(loop-2),mi+minn*(loop+1));
}
}
printf("%lld\n",ans);
}