洛谷 P2127

版权声明:博主是个蒟蒻,希望大家支持,如果要转发就转发吧,把我链接挂上即可。 https://blog.csdn.net/LightningUZ/article/details/88981105

题意简述

现要对一个序列a进行排序,交换i,j的花费是a[i]+a[j],求对a排好序的最小花费。

数据

输入:
6
8 4 5 3 2 7
输出:
34

思路

我们来想土匪正解。

我们先把原数组(代码中是a)和排好序的数组(代码中是b)都写好,拿样例举例子:

然后你会发现我染了颜色。。。

那么,同样颜色的数有什么关系呢?

你会发现,在同一个颜色块里面的数字,只有内部排列错了, 不会影响外面 \color{red}\text{不会影响外面}

就比如红色的,

上面是8 2 7
下面是2 7 8

那么,在一个颜色块里面,我们有哪些方案呢?

有两种方案,一个是拿整个序列里面最小的数去一个一个交换归位(方案1),一个是拿该颜色块里面的最小的数去一个一个交换归位(方案2)。

比如:对于蓝色块,我们有两种方法:
法1 用块里面最小的和整个块换
交换3和5,然后交换3和4
也就是拿最小的那个去这个和整个序列换
当然我们珂以用DFS求出这个具体的方案,但实际上我们不必要知道的那么具体,只要知道总花费是(最小的+所有不是最小的),拆开也就是
(C-1)×最小+(所有不是最小的和),
即(C-2)×最小+块里面的总和
其中C表示块大小

法2 用整个序列最小的和整个块换
交换2和3,交换2和5,交换2和4,再把2和3换回来
一般来讲这种方法不太珂能更优,但也不是没有珂能,如果数据毒瘤的话不考虑就炸了
我不考虑这个只有10分
不难想到这种方法的总花费是
(总最小+块最小)+(总最小+块里面所有数),
即(总最小)×(C+1)+块里面的和+块最小

我们用C表示块大小,Sm(s&m)表示块里面的和,Mn表示块里面的最小,M表示总最小。

那么,枚举每个块,初始设置ans=0,显然(如果不显然请看上面)应该这么写:
a n s + = m i n ( S m + ( C 2 ) × M n , M × ( C + 1 ) + S m + M n ) ans+=min(Sm+(C-2)\times Mn,M\times(C+1)+Sm+Mn)

现在问题来了:怎么维护+寻找块?

维护:写并查集(看到块就想到了并查集)

寻找:写一个map(C++福利)

m a p < i n t , i n t > a p p e a r map<int,int> appear

这个appear[i]表示 数字i \color{red}\text{数字i} 出现的位置(注意到给定的a数组两两不同,所以不会出现重复)

那么,我们只要在找的时候,枚举i,记录j,j不断= a p p e a r [ b [ i ] ] appear[b[i]] ,直到j回到了i。

好了。理一下思路:

读入数据,记录b数组(排好序的数组),找出总最小;
寻找块;
维护出每个块的和,最小,有多少个;
枚举每个块,做贪心;
输出答案,华丽结束;
交到洛谷,直接AC;

代码(有很多注释):

#include<bits/stdc++.h>
#define N 1001000
#define INF 0x3f3f3f3f3f3f3f3f
//正无穷
#define int long long
//小蒟蒻开long long的习惯
using namespace std;
class DSU//并查集
{
    private:
        int Father[N];
    public:
        void Init()
        {
            for(int i=0;i<N;i++)
            {
                Father[i]=i;
            }
        }
        int Find(int index)
        {
            return index==Father[index]?index:Father[index]=Find(Father[index]);
        }
        void Merge(int x,int y)
        {
            Father[Find(x)]=Find(y);
        }
}D;
map<int,int>appear;//那个appear数组
int n;
int a[N];//给定数列
int b[N];//排好序的数列
int M=INF;//总最小
void Input()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        b[i]=a[i];
        M=min(M,a[i]);
        //求最小
        appear[a[i]]=i;
        //记录出现位置
    }
    sort(b+1,b+n+1);
    //排序
}
bool vis[N];
//这个一定要判!在寻找块的时候,这个避免了重复找已找的块。
//不写这个会被卡到O(n^2)
int Cnt[N];//块大小
int Sum[N];//块总和
int Min[N];//块最小
void Build()
{
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
    {
        if (!vis[i])
        //优化
        {
            vis[i]=1;//记得打标记
            int j=appear[b[i]];//不断找
            while(j!=i)
            {
                vis[j]=1;//记得打标记
                D.Merge(i,j);//合并
                j=appear[b[j]];//下一个
            }
        }
    }

    memset(Min,0x3f,sizeof(Min));//记录最小
    for(int i=1;i<=n;i++)
    {
        int an=D.Find(i);//an保存祖先
        Cnt[an]++;
        Sum[an]+=a[i];
        Min[an]=min(Min[an],a[i]);
        //众所周知,并查集里的所有信息全部由祖先承担
        //就像你什么事都交给班长一样。。。
        //然后你就珂以吃着辣条看着番剧看你的班长去搞事情了
        //2333
    }
}
void Solve()
{
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if (i==D.Find(i))//i是否是祖先
        //枚举块的方法:枚举祖先
        {
            int C=Cnt[i];
            int Mn=Min[i];
            int Sm=Sum[i];//s&m
            ans+=min(Sm+(C-2)*Mn,M*(C+1)+Sm+Mn);
            //两种情况分别讨论
        }
    }
    printf("%lld\n",ans);
}
main()
{
    D.Init();//记得初始化
    Input();
    Build();
    Solve();
    //我们的主题思路2333
    //强迫症的main()函数不会超过20行。。。
    return 0;
}

洛谷上直接搬运的。。。

猜你喜欢

转载自blog.csdn.net/LightningUZ/article/details/88981105
今日推荐