篝火晚会

题目:

题目描述太狗,以至于我第一次理解题意时认为b1,b2,...bm-1,bm必须是连续的几个人要把我能惨了...

那现在重新看这个题:它要求最小代价,那我们想想将一个位置错误的人回复正常的位置最小代价不就是一吗?可那是理想条件,因为它会影响其他人的位置,那我们能不能把这个理想条件尽可能的多用?假设1要到5,5要到4,那我们就设定这样的命令(1,5,4)这样1,5都到了正确位置,命令之后也可以加上4要到的位置,我们就是理想条件尽可能的多用。那命令的形式已经确定,最后的结果是什么呢?是环。若干个环。比如说上面的例子:假如4要到1,那就是1,5,4的轮换,三个数构成一个环,假如4要到的不是一,那我们就在4的后面加上4要到的位置,这样依次加下去,迟早会加到正确位置为1的数(因为位置1被5占领,所以肯定有数位置是1)。这样就是一个环。也有可能是若干个环,例如:1到5,5到1,2到6,6到2,这就是两个环也就是说,我们下的命令都是让所有位置错误的人一步到位,也就是说,这个命令的代价就是这个命令中的元素个数,推到全部命令,可以得到结论一:最小代价就是所有位置错误的最小个数。

根据这个我们可以把目标链搞出来以后,枚举每个点以起始点将初始链1,2,3,4,...n与其匹配,找出最小的不匹配数,即可,可是复杂度是O(N^2)的...

那我就要想优化了,能不能不一个一个枚举起始点,就随便找一个目标链,得出最小不匹配数,这样就是O(N)的,我们先想假如已经得到了最小不匹配数的目标链,它与起始链的不匹配数最小。然后我们将它顺时针旋转,把与1匹配的起始点变来,那个就是我们随机找的目标链,根据这个怎么得到最有链呢?我们知道这个链逆时针旋转一些数就会匹配,并且这些数是所有目标链中最多的(不匹配数最少,匹配数就最多)。并且这些在最有链中匹配的数需要旋转的方向与个数是一样的。这是我们就有思路了,我们可以统计每一个数到正确位置需要向右转的个数,然后用桶排,向右转的次数相同的最多的个数就是最大匹配数,例如2个向右转2,4个向右转1,那4就是最大匹配数,因为设想一下我们将这个目标链向左转1,那四个数就可以匹配上了。这就是结论2.

拿这两个结论就可以O(N)解决这个问题了。注意细节。注意正反两边跑。

网上的正解都把环破成链了,我不喜欢搞链,就存的环做,还行,也不是太复杂。

代码:

#include<bits/stdc++.h>
const int maxn=51000;
using namespace std;
int n,tot,link[maxn],deep1[maxn],ru[maxn],deep2[maxn];
int son1,son2,cnt1[maxn],cnt2[maxn],b[maxn],c[maxn],maxx;
struct bian
{
    int y,next;
};
bian a[maxn*4];
inline void add(int x,int y)
{
    a[++tot].y=y;
    a[tot].next=link[x];
    link[x]=tot;
}
inline void dfs1(int x)
{
    for(int i=link[x];i;i=a[i].next)
    {
        int y=a[i].y;
        if(deep1[y]) continue;
        deep1[y]=deep1[x]+1;
        dfs1(y);
    }
}
inline void dfs2(int x)
{
    for(int i=link[x];i;i=a[i].next)
    {
        int y=a[i].y;
        if(deep2[y]) continue;
        deep2[y]=deep2[x]+1;
        dfs2(y);
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int x,y;
        cin>>x>>y;
        b[i]=x;c[i]=y; 
        add(i,x);add(x,i);
        add(i,y);add(y,i);
        if(i==1) son1=x,son2=y;
    }
    for(int i=1;i<=n;i++)
    {
        if((b[b[i]]!=i&&c[b[i]]!=i)||(b[c[i]]!=i&&c[c[i]]!=i))
        {
            cout<<-1<<endl;
            return 0;
        }
    }
    deep1[1]=deep2[1]=1;
    deep1[son1]=deep2[son2]=2;
    dfs1(son1);
    dfs2(son2);
    for(int i=1;i<=n;i++) 
    {
        if(i-deep1[i]>=0) cnt1[i-deep1[i]]++;
        else              cnt1[n-abs(i-deep1[i])]++;
    }
    for(int i=1;i<=n;i++) 
    {
        if(i-deep2[i]>=0) cnt2[i-deep2[i]]++;
        else              cnt2[n-abs(i-deep2[i])]++;
    }
    for(int i=0;i<=n;i++) maxx=max(maxx,max(cnt1[i],cnt2[i]));
    cout<<n-maxx<<endl;
    return 0; 
} 

最后贴上让我感触很深的一句话:

这提醒我碰到一个没有头绪的题就跳过是不对的,也不能光打搜索与模板题,这样思维能力得不到锻炼。

还有O(N)要大胆找结论!!!

 

转载于:https://www.cnblogs.com/gcfer/p/11214686.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值