【NOIP2016提高A组模拟7.21】Double-row

37 篇文章 0 订阅

Description

科学家温斯顿在一张超长的白纸上写下了两行数,每一行数有N个。
但他写完后觉得看起来有点不和谐。他希望重新编排,使得每一行数中没有相同的数。
他每次可以调换同一列的两个数。
请帮他找到操作次数最少的方案。

Input

第一行一个正整数N,代表每一行数的个数。
第二第三行每行N个数,代表第一行与第二行的数值。

Output

第一行一个整数,表示最少的操作次数。数据保证合法的操作是存在的。

Sample Input

9
2 5 5 2 7 4 7 3 9
1 6 8 4 6 3 9 1 8

Sample Output

3

Data Constraint

设字符串的长度为N。
Subtask1[30pts]:N<=500
Subtask2[30pts]:N<=5000
Subtask3[40pts]:N<=50000
数值Xi满足1<=X<=100000

Solution

这题的方法比较巧妙
可以发现,每个数字最多只能出现两次,否则就会无解
如果两个数字出在同一行,那么这两列必须且仅能交换一列
如果两个数字出现在不同行,那么这两列要么同时换,要么同时不换
把每一列的两个数字看成一个点,分别以上述两种方式连两种边,这样会形成一堆联通块,每个联通块之间相互独立互不影响。
对于每个联通块,选定一个点,让它交换或不交换,那么可以通过一个点得知整个联通块需要交换的点的个数,这不是答案,答案还可能是在这种情况下交换的点不交换,不交换的点交换,所以还要取个min

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 101000
using namespace std;
int t[2][N][2],ans=0,n,last[N],next[N*10],to[N*10],date[N*10],tot=0,bz[N],an,na;
void put(int x,int y,int z)
{
    next[++tot]=last[x];last[x]=tot;to[tot]=y;date[tot]=z;
    next[++tot]=last[y];last[y]=tot;to[tot]=x;date[tot]=z;
}
void dg(int x,int z)
{
    na++;bz[x]=z;if(z==1) an++;
    for(int i=last[x];i;i=next[i])
    if(bz[to[i]]==-1)
    {
        if(date[i]==0) dg(to[i],1-z);
        else dg(to[i],z);
    }
}
int main()
{
    scanf("%d",&n);
    memset(bz,255,sizeof(bz));
    fo(i,1,n)
    {
        int x;scanf("%d",&x);
        if(t[0][x][0]==0) t[0][x][0]=i;else t[0][x][1]=i;
    }
    fo(i,1,n)
    {
        int x;scanf("%d",&x);
        if(t[1][x][0]==0) t[1][x][0]=i;else t[1][x][1]=i;
    }
    fo(x,1,100000)
    if(t[0][x][0]||t[1][x][0])
    {
        if(t[0][x][0])
        {
            if(t[0][x][1]) put(t[0][x][0],t[0][x][1],0);
            else if(t[1][x][0]) put(t[0][x][0],t[1][x][0],1);
        }
        else
        if(t[1][x][0])
        {
            if(t[1][x][1]) put(t[1][x][0],t[1][x][1],0);
        }
    }
    fo(i,1,n)
    if(bz[i]==-1)
    {
        an=na=0;
        dg(i,0);
        ans+=min(an,na-an);
    }
    printf("%d",ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值