群论题,涨姿势啊涨姿势,牛掰的群论。先确定循环有几个,再按照遍历找出循环,记录每个循环的最小值和所有数的和。
最后判断每个循环节所用的最小代价时有两种情况:
第一种是在当前循环里把最小数作为调动的工具。
第二种是把整个数列里最小的数字和循环里最小数字交换,把数列里最小的数字作为工具,最后两个数要换回来。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int n;
int mino;
bool labelperm[1010];
struct node
{
int pos,w;
}box[1100];
struct loop
{
int mina,sum,num,head;
}perm[1100];
int findpos()
{
for(int i=0;i<n;i++)
{
box[i].pos=0;
for(int j=0;j<n;j++)
{
if(box[i].w>box[j].w)
{
box[i].pos++;
}
}
}
}
void minn()
{
mino=0x3f3f3f3f;
for(int i=0;i<n;i++)
{
mino=min(mino,box[i].w);
}
}
int getans(int k)
{
if(perm[k].num==1) return 0;
int r1=perm[k].sum+(perm[k].num-2)*perm[k].mina;
int r2=perm[k].sum+(perm[k].num-1)*mino-perm[k].mina+2*(mino+perm[k].mina);
return min(r1,r2);
}
int countg()
{
int sum=0;
for(int i=0;i<n;i++)
labelperm[i]=true;
for(int i=0;i<n;i++)
{
if(labelperm[i])
{
int k=0;
for(k=i;box[k].pos!=i;k=box[k].pos)
{
labelperm[k]=false;
}
labelperm[k]=false;
labelperm[i]=true;
}
}
for(int i=0;i<n;i++)
if(labelperm[i])
{
perm[sum].head=i;
sum++;
}
for(int i=0;i<sum;i++)
{
perm[i].mina=0x3f3f3f3f;
perm[i].sum=perm[i].num=0;
for(int j=perm[i].head;j!=perm[i].head || perm[i].num==0 ;j=box[j].pos)
{
perm[i].mina=min(perm[i].mina,box[j].w);
perm[i].sum+=box[j].w;
perm[i].num++;
}
}
return sum;
}
int main()
{
int tot=0;
while(scanf("%d",&n)!=EOF)
{
tot++;
for(int i=0;i<n;i++)
{
scanf("%d",&box[i].w);
}
findpos();
int permnum=countg();
minn();
int ans=0;
for(int i=0;i<permnum;i++)
{
ans+=getans(i);
}
if(ans==0)
{
return 0;
}
printf("Case %d: %d\n",tot,ans);
}
return 0;
}