15 收心赛3 T2 美食节 题解

美食节

原题:KOI 2022 食事计划

题面

\(n\) 个摊位,每个摊位提供一种类型的美食 \(a_1, a_2, a_3 ... a_n\) ,现需要你求出一个字典序最小的排列 \(p_1,p_2,p_3...p_n\) 使得 \(\forall 1 \le i < n, a_{p_i} \not= a_{p_{i + 1}}\)

如果无解,输出 -1

\(1 \le n \le 3 \times 10^5\)

\(1 \le a_i \le n\)

题解

这道题有点思维的,考虑什么时候无解?

当且仅当众数 \(> \lceil \frac n 2 \rceil\) 时,无解

那我们也可以扩展到已经填完 \(k\) 个数的情况,那么也就是剩下数的众数 \(> \lceil \frac {n - k} 2 \rceil\) 时无解

那么我们只需要从前向后遍历没有出现过的数,判断其是否可以填在这个位置,如果可以,那么就填,否则就判断下一个

考虑进行这个流程的过程中,其实两个不是众数的数对于是否无解的判断是等价的,所以我们可以直接去判断选众数还是最靠前的数即可

那么我们可以用两个 set 来维护,一个来按照出现次数排序,另一个按照出现位置排序,然后每次判断一下是否能填即可

时间复杂度 \(O(n \log n)\)

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>

using namespace std;


const int N = 3e5 + 10;

int n;
int a[N], now[N], nt[N], lst[N];
int cnt[N], la;
bool vis[N];

struct node {
    int c, pos, val;
    bool operator < (const node &o) const {
        return c > o.c || (c == o.c && pos < o.pos);
    }
};

set <node> s1;              //按照出现次数排序,从大到小
set <pair <int, int> > s2;  //按照出现位置排序,从小到大


void Do (int x) {
    cout << now[x] << ' ';
    la = x;
    s1.erase ({cnt[x], now[x], x});
    s2.erase ({now[x], x});
    cnt[x] --;
    if (!cnt[x]) return;
    now[x] = nt[now[x]];
    s1.insert ({cnt[x], now[x], x});
    s2.insert ({now[x], x});
    
}


int main () {

    freopen ("food.in", "r", stdin);
    freopen ("food.out", "w", stdout);


    cin >> n;

    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        cnt[a[i]] ++;
    }

    for (int i = 1; i <= n; i ++) {
        if (!lst[a[i]]) now[a[i]] = i;
        else nt[lst[a[i]]] = i;
        lst[a[i]] = i;
    }

    for (int i = 1; i <= n; i ++) {
        if (cnt[i]) {
            //s1中存 pos 的意义是如果有两个众数,那么我们应该优先选择出现位置靠前的那一个
            s1.insert ({cnt[i], now[i], i});
            s2.insert ({now[i], i});
        }
    }
    //如果一开始就不合法,那就无解
    if ((*s1.begin ()).c > (n + 1) / 2) {
        cout << -1 << endl;
        return 0;
    }

    //上一个位置的数,初始化为 -1
    la = -1;
    for (int i = 1; i <= n; i ++) {
        node tmp = *s1.begin ();
        if (tmp.c > (n - i + 1) / 2) {
            //因为有解,而如果不这么填一定无解,所以一定要这么填
            //因此不需要考虑是否和前一个位置相等的情况
            Do (tmp.val);
        } else {
            auto it = s2.begin ();
            //如果当前数可以填
            if ((*it).second != la) {
                Do ((*it).second);
            } else {
                it ++;
                Do ((*it).second);
            }
        }
    }

    return 0;
}
posted @ 2025-08-31 21:00  michaele  阅读(45)  评论(0)    收藏  举报