2025.10.21 NOIP 模拟赛 题解

T1 码(code)

题意

给定 \(n\) 个字符串 \(s\),每个字符可以匹配任意自然数次,求出能匹配所有字符串的长度为 \(4\) 的字符串数量,字符集为 \(\text0\sim\text9\)\(n\le10^3,|s_i|\le10^4,\sum|s_i|\le10^6\)

分析

建立类似自序列自动机的结构,每个 \(s_i\) 暴力枚举所有字符串尝试匹配即可

时间复杂度 \(O(|\sum|\sum|s_i|+10^\omega\omega n)\),其中 \(\omega=4\)

代码:

#include <bits/stdc++.h>
using namespace std;
int n;
bool f[10000];
int main(){
    freopen("code.in", "r", stdin);
    freopen("code.out", "w", stdout);
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    fill(f, f + 10000, 1);
    while (n--){
        int l; string s;
        cin >> l >> s;
        vector<array<int, 10> > nx(l);
        array<int, 10> f;
        f.fill(l);
        for (int i = l - 1; ~i; --i){
            f[s[i] - '0'] = i;
            nx[i] = f;
        }
        auto mtc = [&](string s){
            int p = 0;
            for (char c : s){
                assert('0' <= c && c <= '9');
                p = nx[p][c - '0'];
                if (p >= l)return 0;
            }
            return 1;
        };
        for (int i = 0; i < 10000; ++i){
            if (::f[i] && !mtc({char(i / 1000 + '0'), char(i / 100 % 10 + '0'), char(i / 10 % 10 + '0'), char(i % 10 + '0')}))
                ::f[i] = 0;
        }
    }
    cout << count(f, f + 10000, 1) << endl;
    return 0;
}
/*
g++ code.cpp -o code.exe -std=c++14 -Wall -O0 -g -fsanitize=address
g++ code.cpp -o code.exe -std=c++14 -Wall -O2
*/

T2 书(book)

题意

给定 \(w_{1\sim n}\)\(h_{1\sim n}\),将 \([1,n]\) 划分为若干段,要求每段的 \(\sum w\) 不超过 \(l\),求出每段的 \(\max h\) 之和的最小值,\(n\le10^5,l\ge \max w\)

分析

\(f_i\) 表示 \(1\sim i\) 的答案,容易线段树优化,时间复杂度 \(O(n\log n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
int n, l, h[100010], w[100010];
long long sw[100010];
long long f[100010];

long long mnv[100010 << 2], adt[100010 << 2];
inline void ad(int k, long long v){
    mnv[k] += v;
    adt[k] += v;
}
inline void pu(int k){
    mnv[k] = min(mnv[k << 1], mnv[k << 1 | 1]);
}
inline void pd(int k){
    if (adt[k]){
        ad(k << 1, adt[k]);
        ad(k << 1 | 1, adt[k]);
        adt[k] = 0;
    }
}

inline void assg(int p, long long v, int l = 1, int r = n, int k = 1){
    if (l == r){
        mnv[k] = v;
        return;
    }
    int M = (l + r) >> 1; pd(k);
    if (p <= M)assg(p, v, l, M, k << 1);
    else assg(p, v, M + 1, r, k << 1 | 1);
    pu(k);
}

inline void add(int L, int R, long long v, int l = 1, int r = n, int k = 1){
    if (L > R)return;
    if (L <= l && r <= R)return ad(k, v);
    int M = (l + r) >> 1; pd(k);
    if (L <= M)add(L, R, v, l, M, k << 1);
    if (R > M)add(L, R, v, M + 1, r, k << 1 | 1);
    pu(k);
}

inline long long minv(int L, int R, int l = 1, int r = n, int k = 1){
    if (L <= l && r <= R)return mnv[k];
    int M = (l + r) >> 1; pd(k);
    if (R <= M)return minv(L, R, l, M, k << 1);
    if (L > M)return minv(L, R, M + 1, r, k << 1 | 1);
    return min(minv(L, R, l, M, k << 1), minv(L, R, M + 1, r, k << 1 | 1));
}

int main(){
    freopen("book.in", "r", stdin);
    freopen("book.out", "w", stdout);
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> l;
    for (int i = 1; i <= n; ++i)cin >> h[i] >> w[i], sw[i] = sw[i - 1] + w[i];
    fill_n(f, n + 1, 1e18);
    f[0] = 0;
    static int s[100010], tp;
    s[tp = 0] = 0;
    for (int i = 1; i <= n; ++i){
        while (tp && h[s[tp]] <= h[i])add(s[tp - 1] + 1, s[tp], h[i] - h[s[tp]]), --tp;
        s[++tp] = i;
        assg(i, f[i - 1] + h[i]);
        // for (int j = i, mx = 0; j; --j)assert(minv(j, j) == f[j - 1] + (mx = max(mx, h[j])));
        int p = lower_bound(sw, sw + i + 1, sw[i] - l) - sw + 1;
        f[i] = minv(p, i);
        // for (int x = i, mxv = 0; x >= p; --x){
        //     mxv = max(mxv, h[x]);
        //     f[i] = min(f[i], f[x - 1] + mxv);
        // }
    }
    cout << f[n] << endl;
    return 0;
}
/*
g++ book.cpp -o book.exe -std=c++14 -Wall -O0 -g -fsanitize=address
*/

T3 图(graph)

题意

给定一张 \(n\)\(m\) 边的简单无向图,求出一组 \(P\subset [1,n],Q\subset [1,n]\),令 \(p\)\(P\) 的导出子图中度数的最小值,\(q\)\(|Q|\)\(Q\) 为独立集,要求 \((p+1)(q+1)>n\)\(n\le10^4,m\le10^5\),多测 \(T\le32\)

分析

每次取出剩余部分中度数最小的点删去,则 \(p\) 为此过程中点被删去时的度数的最大值,构造 \(P\) 是容易的,按此顺序贪心取 \(Q\),显然每个点 \(u\) 至多导致另外 \(p\) 个点无法加入 \(Q\),因此 \(q=|Q|\ge \left\lceil\frac{n}{p+1}\right\rceil\),即 \(n\le q(p+1)<(q+1)(p+1)\)

容易用堆做到 \(O((n+m)\log n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<int> e[10010];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
void clr(){
    decltype(pq)().swap(pq);
}
int dg[10010];
void work(){
    cin >> n >> m;
    fill_n(e + 1, n, vector<int>{});
    while (m--){
        int u, v;
        cin >> u >> v;
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    int p = 0;
    vector<int> pts;
    {
        clr();
        for (int i = 1; i <= n; ++i)pq.emplace(dg[i] = e[i].size(), i);
        static bool rm[10010];
        fill_n(rm + 1, n, 0);
        while (!pq.empty()){
            auto du = pq.top();
            pq.pop();
            int d = du.first, u = du.second;
            if (dg[u] != d)continue;
            p = max(p, d);
            rm[u] = 1;
            for (int v : e[u])if (!rm[v])--dg[v], pq.emplace(dg[v], v);
        }
        clr();
        for (int i = 1; i <= n; ++i)pq.emplace(dg[i] = e[i].size(), i);
        fill_n(rm + 1, n, 0);
        while (!pq.empty()){
            auto du = pq.top();
            int d = du.first, u = du.second;
            if (d >= p)break;
            pq.pop();
            if (dg[u] != d)continue;
            rm[u] = 1;
            for (int v : e[u])if (!rm[v])--dg[v], pq.emplace(dg[v], v);   
        }
        while (!pq.empty()){
            auto du = pq.top();
            int d = du.first, u = du.second;
            pq.pop();
            if (d == dg[u])pts.emplace_back(u);
        }
    }
    cout << pts.size() << " ";
    for (int u : pts)cout << u << " ";
    cout << "\n";
    int q = 0;
    vector<int> qts;
    {
        clr();
        for (int i = 1; i <= n; ++i)pq.emplace(dg[i] = e[i].size(), i);
        static bool rm[10010], dl[10010];
        fill_n(rm + 1, n, 0);
        fill_n(dl + 1, n, 0);
        while (!pq.empty()){
            auto du = pq.top();
            int d = du.first, u = du.second;
            pq.pop();
            if (dg[u] != d)continue;
            dl[u] = 1;
            for (int v : e[u])if (!dl[v])
                --dg[v], pq.emplace(dg[v], v), !rm[u] && (rm[v] = 1);
        }
        for (int i = 1; i <= n; ++i)if (!rm[i])qts.emplace_back(i);
        q = qts.size();
    }
    cout << q << " ";
    for (int u : qts)cout << u << " ";
    cout << "\n";
    // clog << p << "*" << q << ":" << n << endl;
}
int main(){
    freopen("graph.in", "r", stdin);
    freopen("graph.out", "w", stdout);
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)work();
    return 0;
}

T4 牌(card)

题意

定义 \(F(a_{1\sim 2n})=[a_1,a_{n+1},a_2,a_{n+2},a_3,a_{n+3},\cdots,a_n,a_{2n}]\),令 \(G(a_{1\sim 2n},b_{1\sim 2n})=\sum_{i=1}^{2n}[a_i\ne b_i]\),给定 \(a_{1\sim 2n}\)\(b_{1\sim 2n}\),求出 \(\min_x G(F^x(a),b)\)\(n\le2\times10^5\),多测 \(\sum n\le5\times10^5\)

分析

假定下标从 \(0\) 开始

发现 \(F^k(a)_i=\begin{cases}a_i&i=0\lor i=2n-1\\ a_{n^i\bmod (2n-1)}&\text{otherwise} \end{cases}\)

\(ct>0\) 为满足 \(n^{ct}\equiv 1\pmod (2n-1)\) 的最小值(\(n=1\) 时特判,其它情况下一定存在),则 \(F^{ct}(a)=a\)

重排 \(a,b\),使得 \(a_i=i\)

转化为求

\[\min_{x=0}^{ct} \left([b_0\ne 0]+[b_{2n-1}\ne 2n-1]+\sum_{j=1}^{2n-2}[b_j\ne j\times n^x\bmod (2n-1)]\right) \]

\(ad_{n^x\bmod (2n-1)}=\sum_{j=1}^{2n-2}[b_j=j\times n^x\bmod (2n-1)]\),求出 \(ad\) 后容易得到答案

枚举 \(j\),则 \(b_j\) 贡献到 \(ad\) 的位置为一个等差树列,容易用扩展欧几里德求出范围,数量为 \(\gcd(j,n)\) 的,因此暴力加的复杂度为 \(O(\sum_j \gcd(j,n))=O(d(n)n)\)

总时间复杂度 \(O(\sum (d(n)+\log n)n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
int n, a[400010], b[400010];
int exgcd(int a, int b, int &x, int &y){
    if (!b){
        x = 1, y = 0;
        return a;
    }
    int e = exgcd(b, a % b, x, y);
    tie(x, y) = make_pair(y, x - a / b * y);
    return e;
}
pair<int, int> dv(int a, int b, int p){//(r,t) | kt+r
    if (a == p)return {-1, 0};
    // xb+yp=a
    // clog << a << "/" << b << "%" << p << endl;
    int x, y;
    int e = exgcd(b, p, x, y);
    // clog << b << "*" << x << "+" << p << "*" << y << "=" << e << endl;
    if (a % e)return {-1, 0};
    int ret = 1ll * (x % p + p) % p * (a / e) % p;
    // clog << "=" << ret << endl;
    assert(1ll * ret * b % p == a);
    // clog << "=" << ret << "+" << p / e << "k" << endl;
    return {ret, p / e};
}

int sn = 0;
void work(){
    cin >> n;
    for (int i = 0; i < n + n; ++i)cin >> a[i], --a[i];
    for (int i = 0; i < n + n; ++i)cin >> b[i], --b[i];
    int c = (a[0] != b[0]) + (a[n + n - 1] != b[n + n - 1]);
    if (n == 1){cout << c << "\n";return;}
    {
        static int p[400010];
        for (int i = 0; i < n + n; ++i)p[a[i]] = i;
        for (int i = 0; i < n + n; ++i)b[i] = p[b[i]];
    }
    sn += n;
    // if (n <= 2000 && (sn <= 5e4)){
    //     int ct = 0;
    //     for (int v = 1; ; ){
    //         v = (v + v) % (n + n - 1); ++ct;
    //         if (v == 1)break;
    //     }
    //     int rs = c;
    //     for (int i = 1; i < n + n - 1; ++i)rs += i != b[i];
    //     for (int i = 1, of = n; i <= ct; ++i, of = 1ll * of * n % (n + n - 1)){
    //         int s = c + i + n + n - 2;
    //         for (int j = 1; j < n + n - 1; ++j)s -= 1ll * j * of % (n + n - 1) == b[j];
    //         rs = min(rs, s);
    //     }
    //     cout << rs << "\n";
    //     return;
    // }
    int ct = 0;
    for (int v = 1; ; ){
        v = (v + v) % (n + n - 1); ++ct;
        if (v == 1)break;
    }
    int rs = c;
    for (int i = 1; i < n + n - 1; ++i)rs += i != b[i];
    static int ad[400010];
    fill_n(ad, n + n, 0);
    for (int i = 1; i < n + n - 1; ++i){
        unsigned p = n + n - 1;
        auto rt = dv(b[i], i, p);
        int r = rt.first, t = rt.second;
        if (r == -1)continue;
        for (unsigned x = r, c = p / t; c; --c){
            ++ad[x];
            x += t;
            if (x >= p)x -= p;
        }
        // for (int x = (r + t) % (n + n - 1), c = x == r; c < 1; x = (x + t) % (n + n - 1), c += x == r)++ad[x];
    }
    for (int i = 1, of = n; i <= ct; ++i, of = 1ll * of * n % (n + n - 1)){
        int s = c + i + n + n - 2 - ad[of];
        rs = min(rs, s);
    }
    cout << rs << "\n";
}
int main(){
    freopen("card.in", "r", stdin);
    freopen("card.out", "w", stdout);
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)work();
    return 0;
}

比赛结果

\(100+100+100+100\)\(\text{rk}1\)

posted @ 2025-10-21 18:15  Hstry  阅读(6)  评论(0)    收藏  举报