Codeforces Round #733 (Div. 1 + Div. 2) D. Secret Santa

D. Secret Santa

题意

给t组样例
每组样例给n个数
a[1] , a[2] , a[3] ...... a[n]
(t组样例n的总和<=2e5,a[i] <= n)
并且保证a[i] != i
求一个数组p
并且这个数组p为1到n的全排列中的一种方式
求 p[i] == a[i] 的个数最大并且p[i] != i

输出这个个数的最大值和p数组

思路

首先先把可以匹配的匹配了
举个例子
a 6 4 6 2 4 5 1
从头到尾扫一遍
p 6 4 _ 2 _ 5 1

还差3 7没填
然后在考虑怎么安排3 7的位置 使得p[i] != i 
很明显应该
p 6 4 7 2 3 5 1

也就是说题目转换成为了
怎么安排未匹配的数
使得p[i] != i 

这里有个性质
自闭了1h才想出来

假设现在没有匹配的数是 1 2 3 4 5 6 7 
有7个位置要填
_ _ _ _ _ _ _ 

那我1 2 3 4 5 6 7 正序填
7个都冲突出现了p[i] = i 的情况

如果倒叙填
是 7 6 5 4 3 2 1 
只有p[4] 冲突了

那是不是说明了正序填和倒叙填有一种情况
最多只冲突一次
(可以自己多找几组样例看看是不是对的)

如果冲突一次
找到这个数交换一下即可

时间复杂度:O n

#include<bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define re register int
#define pll pair<int,int> 
#define x first 
#define y second 
#define sf(x) scanf("%d",&x)
#define sfl(x) scanf("%lld",&x)
typedef long long ll ;
using namespace std;
const int N = 2e5 + 10 , M = 2010 , inf = 0x3f3f3f3f , mod = 1e9 + 7 ;
int t ;
int n ;
int a[N] ; // a数组
bool st[N] ; // 判断这个数有没有出现过
int ans[N] ;  
int s[N] ; // s , ans最后的答案数组
int main()
{
    cin >> t ;
    while(t--)
    {
        cin >> n ;
        
        fer(i,1,n) sf(a[i]) ;
        
        fer(i,1,n) st[i] = ans[i] = 0 ; // 初始化
        
        int cnt = 0 ;
        
        fer(i,1,n) // 把可以匹配的先匹配了
        {
            if(!st[a[i]])
            {
                cnt ++ ;
                st[a[i]] = 1 ;
                ans[i] = a[i] ;
            }
        }
        
        vector<int> q ; // 找到未匹配的数
        fer(i,1,n)
        {
            if(!st[i])
                q.push_back(i) ;
        }
        //fer(i,1,n) cout << a[i] << " " ;
        
        sort(q.begin(),q.end()) ;
        reverse(q.begin(),q.end()) ;
        // 倒叙填一遍
        
        int cnt1 = 0 , cnt2 = 0 ;
        // cnt1为倒叙的冲突个数 , cnt2反之
        
        fer(i,1,n)
        {
            s[i] = ans[i] ;
        }
        // 先让s数组和ans数组相等
        
        // 倒叙填ans数组
        int k = 0 ;
        fer(i,1,n)
        {
            if(!ans[i])
            {
                ans[i] = q[k ++] ;
            }
        }
        
        fer(i,1,n)
        {
            if(ans[i] == i) cnt1 ++ ;
        }
        
        // 正序填s数组
        reverse(q.begin(),q.end()) ;
        k = 0 ;
        fer(i,1,n)
        {
            if(!s[i])
            {
                s[i] = q[k ++] ;
            }
        }
        
        fer(i,1,n)
        {
            if(s[i] == i) cnt2 ++ ;
        }
        
        if(cnt1 > cnt2)
        {
       	// 如果ans数组冲突值大了,把s数组给ans
            fer(i,1,n)
            {
                ans[i] = s[i] ;
            }
        }
        
        // 找到这个冲突值的下标
        int xia = 0 ;
        fer(i,1,n)
        {
            if(ans[i] == i)
            {
                xia = i ;
                break ;
            }
        }
        
        // 然后交换
        fer(i,1,n)
        {
            if(ans[i] == a[xia] && i != xia)
            {
                swap(ans[i],ans[xia]) ;
                break ;
            }
        }
        
        // 最后输出答案
        cout << cnt << "\n" ;
            
        fer(i,1,n) cout << ans[i] << " " ;
        
        cout << "\n" ;
    }
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值