【Codeforces】1617C Paprika and Permutation 题解

题目大意

给定一个 n ( 1 ≤ n ≤ 1 0 5 ) n(1\le n \le 10^5) n(1n105)个数的正整数数组 a n ( 1 ≤ a i ≤ 1 0 9 ) a_n(1 \le a_i \le 10^9) an(1ai109),你需要把它变成一个 1 1 1~ n n n的排序,可选的操作只有: 对于 a i a_i ai,选择一个正整数 x ( 1 ≤ x ≤ a i ) x(1 \le x \le a_i) x(1xai),令 a i = a i % x a_i = a_i \% x ai=ai%x

问是否可以通过任意次这种操作是数组 a a a成为一个排列。
题目链接

思路

对于 a i a_i ai,它能变成的最大的数就是 ⌊ a i 2 ⌋ \lfloor \frac{a_i}{2} \rfloor 2ai
那么我们的贪心思路就是:对于新排列中的数 a i ′ a_i' ai,我们尽可能选择原序列中小的数去生成它。
但是,还是有一个隐藏条件我们没有发现,那就是假如在原序列中 1 ≤ a i ≤ n 1 \le a_i \le n 1ain,那么它本身就已经在排列中了,那么我们会选择不去操作这个数。

证明:
根据前面说的,它能生成的新数在 [ 1 , ⌊ a i 2 ⌋ [1,\lfloor \frac{a_i}{2} \rfloor [1,2ai]。中,假设原序列中有个数 a k a_k ak,它能生成 a i a_i ai,那么 [ 1 , ⌊ a i 2 ⌋ [1,\lfloor \frac{a_i}{2} \rfloor [1,2ai]之间的数它也能生成,所以这么做不会使得结果更劣。
并且,有可能 a i a_i ai这个数有可能无法被生成,比如 2 , 3 , 5 2,3,5 2,3,5这种情况,如果让 3 3 3去生成 1 1 1了,那么 3 3 3这个数就无法填充了,所以正确做法是 2 , 3 2,3 2,3不变,让 5 5 5去生成 1 1 1

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int maxN = 1e5 + 7;

int T, n, a[maxN], matched[maxN], used[maxN];
//matched[i]表示新排列中i这个数是否已经被生成,used[i]表示原序列中a[i]是否被使用过了
int main()
{
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        sort(a + 1, a + 1 + n);
        int ans  = 0;
        memset(matched, 0, sizeof matched);
        memset(used, 0, sizeof used);
        for(int i = 1; i <= n; ++i)
            if(a[i] >= 1 && a[i] <= n && matched[a[i]] == 0) {
                matched[a[i]] = i;
                used[i] = 1;
            }
        int u = 1; bool succ = true;
        for(int i = 1; i <= n; ++i) {
            while(used[u]) u++;
            if(matched[i])
                continue;
            if((a[u] + 1) / 2 <= i && a[u] != i) {
                succ = false;
                break;
            }
            used[u] = 1;
            matched[i] = u;
            ans++;
        }
        if(!succ)
            printf("-1\n");
        else
            printf("%d\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值