1. 程式人生 > >【bzoj1697】牛排序 置換群

【bzoj1697】牛排序 置換群

【題解】

將所給值離散之後,就是一個置換群問題。

初始狀態為離散值,目標狀態為1~n,交換這一行為映射了置換,我們把這一置換迴圈分解而得到一個置換群。

對於群內的每個環,有一個很貪心的辦法就是每次都讓權值最小的那個元素去和其他的元素交換,

這樣交換的代價為sum-min+(len-1)*min

其中sum為環內所有元素的權重之和,min為環內最小的元素,len為這個環的迴圈節。

經過len-1次交換,我們讓這個環變的有序了。

不過這裡還有一個策略,引進外援,就是從全域性中找一個最小的small,將其引入到這個環中,參與置換。

舉例(1) (8 6 9 7) 我們可以先把1和6交換,讓1去參與環的置換,結束後再將6交換進來。

如此的代價為sum+min+(len+1)*small 取這兩種策略裡代價小的那種即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
using namespace std;
#define FILE "read"
#define MAXN 100010
#define INF 1000000000
#define up(i,j,n) for(int i=j;i<=n;++i)
#define dn(i,j,n) for(int i=j;i>=n;--i)
namespace INIT{
	char buf[1<<15],*fs,*ft;
	inline char getc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
	inline int read(){
		int x=0,f=1;  char ch=getc();
		while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getc();}
		while(isdigit(ch))  {x=x*10+ch-'0';  ch=getc();}
		return x*f;
	}
}using namespace INIT;
int n,ans,len,Minn,a[MAXN],b[MAXN],v[MAXN],vis[MAXN],size[MAXN],sum[MAXN],minn[MAXN];
int find(int x){
	int l=1,r=n,temp;
	while(l<=r){
		int mid=(l+r)>>1;
		if(v[mid]>=x)  {temp=mid;  r=mid-1;}
		else l=mid+1;
	}
	return temp;
}
void solve(int x){
	vis[x]=1;  size[++len]=1;  sum[len]+=a[x];  minn[len]=min(minn[len],a[x]);
	for(int i=b[x];a[i]!=a[x];i=b[i]){
		vis[i]=1;  size[len]++;  sum[len]+=a[i];  minn[len]=min(minn[len],a[i]);
	}
}
int main(){
	freopen(FILE".in","r",stdin);
	freopen(FILE".out","w",stdout);
	n=read();  memset(minn,127/3,sizeof(minn));  Minn=INF;
	up(i,1,n)  a[i]=read(),v[i]=a[i],Minn=min(Minn,a[i]);
	sort(v+1,v+n+1);
	up(i,1,n)  b[i]=find(a[i]);
	up(i,1,n)  if(i!=b[i]&&!vis[i])  solve(i);
	up(i,1,len){
		ans+=min(sum[i]+minn[i]*(size[i]-2),sum[i]+minn[i]+(size[i]+1)*Minn);
	}
	printf("%d\n",ans);
	return 0;
}