[NOIP2024] 遗失的赋值

GitHub跳转原题关系图返回列表

用一元固定点切分变量链,相邻固定点区间用总方案减唯一强制失败链计数。

OJ: luogu

题目 ID: P11362

难度:普及+/提高

标签:组合计数数学快速幂

日期: 2026-06-22 19:11

题意

n 个变量,每个变量可以取 1..v。相邻变量之间原本有 n-1 条二元限制:

text
如果 x_i = a_i,则要求 x_{i+1} = b_i

现在所有 a_i,b_i 都忘了,只记得 m 条一元限制 x_c = d。要求统计有多少种 (a_i,b_i) 的取值,使得至少存在一种变量赋值同时满足所有限制。

思路

先看一个小数据暴力:枚举所有边上的 (a_i,b_i),再枚举所有变量赋值,判断是否存在合法赋值。

cpp
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

struct Constraint {
    int pos;
    int value;
};

int T, n, m, v;
Constraint cons[15];
int edge_a[15], edge_b[15];
long long ans;

bool check_assignment(int pos, int x[]) {
    if (pos > n) {
        for (int i = 1; i <= m; i++) {
            if (x[cons[i].pos] != cons[i].value) {
                return false;
            }
        }

        for (int i = 1; i < n; i++) {
            if (x[i] == edge_a[i] && x[i + 1] != edge_b[i]) {
                return false;
            }
        }
        return true;
    }

    for (int value = 1; value <= v; value++) {
        x[pos] = value;
        if (check_assignment(pos + 1, x)) {
            return true;
        }
    }
    return false;
}

void enumerate_edges(int id) {
    if (id == n) {
        int x[15];
        if (check_assignment(1, x)) {
            ans++;
        }
        return;
    }

    for (int a = 1; a <= v; a++) {
        for (int b = 1; b <= v; b++) {
            edge_a[id] = a;
            edge_b[id] = b;
            enumerate_edges(id + 1);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> T;
    while (T--) {
        cin >> n >> m >> v;
        for (int i = 1; i <= m; i++) {
            cin >> cons[i].pos >> cons[i].value;
        }

        ans = 0;
        enumerate_edges(1);
        cout << ans << '\n';
    }

    return 0;
}

暴力无法处理 n,v10^9 的数据。

先处理一元限制。如果同一个位置被限制成两个不同的值,那么无论二元限制怎么选,都不可能存在变量赋值,答案为 0

否则,把所有固定点按位置排序。一元固定点会把整条变量链切成若干段。

没有被两个固定点夹住的边是自由的。因为至少一侧没有终点固定要求,总可以选择变量值绕开触发条件,所以每条这样的边有 v^2(a,b)

真正需要计数的是两个相邻固定点之间的区间。设左端固定为 x_p=A,右端固定为 x_q=B,距离:

text
L = q - p

这段有 L 条边,总参数方案数为:

text
v^(2L)

什么时候这段不合法?只有一种结构:从 A 出发,每一步都被强制触发,形成唯一的一条值链,最后还没有走到 B

L-1 条边可以任意决定下一步被强制成什么值,共:

text
v^(L-1)

最后一条边强制出的值不能等于 B,有:

text
v - 1

种。因此不合法方案数为:

text
v^(L-1)(v-1)

所以这个区间的合法贡献是:

text
v^(2L) - v^(L-1)(v-1)

把所有相邻固定点区间的贡献乘起来,再乘上自由边的 v^2 次幂,就是答案。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const long long MOD = 1000000007LL;
const int MAXM = 100005;

struct Constraint {
    long long pos;
    long long value;
};

int T, m;
long long n, v;
Constraint cons[MAXM];

bool cmp_constraint(Constraint a, Constraint b) {
    if (a.pos != b.pos) {
        return a.pos < b.pos;
    }
    return a.value < b.value;
}

long long power_mod(long long a, long long b) {
    long long res = 1;
    a %= MOD;
    while (b > 0) {
        if (b & 1LL) {
            res = res * a % MOD;
        }
        a = a * a % MOD;
        b >>= 1LL;
    }
    return res;
}

long long interval_count(long long len) {
    long long total = power_mod(v * v % MOD, len);
    long long bad = power_mod(v, len - 1) * ((v - 1) % MOD) % MOD;
    return (total - bad + MOD) % MOD;
}

void solve_one() {
    cin >> n >> m >> v;
    for (int i = 1; i <= m; i++) {
        cin >> cons[i].pos >> cons[i].value;
    }

    sort(cons + 1, cons + m + 1, cmp_constraint);

    vector<Constraint> fixed_point;
    bool ok = true;

    for (int i = 1; i <= m; i++) {
        if (!fixed_point.empty() && fixed_point.back().pos == cons[i].pos) {
            if (fixed_point.back().value != cons[i].value) {
                ok = false;
            }
        } else {
            fixed_point.push_back(cons[i]);
        }
    }

    if (!ok) {
        cout << 0 << '\n';
        return;
    }

    long long all_edge = v * v % MOD;
    long long free_edges = 0;
    long long ans = 1;

    if (fixed_point.empty()) {
        free_edges = n - 1;
    } else {
        free_edges += fixed_point[0].pos - 1;
        free_edges += n - fixed_point.back().pos;

        for (int i = 0; i + 1 < (int)fixed_point.size(); i++) {
            long long len = fixed_point[i + 1].pos - fixed_point[i].pos;
            ans = ans * interval_count(len) % MOD;
        }
    }

    ans = ans * power_mod(all_edge, free_edges) % MOD;
    cout << ans << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> T;
    while (T--) {
        solve_one();
    }

    return 0;
}

复杂度

排序一元限制复杂度为 O(mlogm)O(m log m),每个固定点区间做快速幂,总复杂度为 O(mlogm+mlogn)O(m log m + m log n)

空间复杂度为 O(m)O(m)

总结

这题的关键是分清楚统计对象:不是变量赋值数量,而是遗失的二元限制参数数量。

一元限制把链切成段。自由段每条边随便选;两个固定点之间的段,用“总方案数减去唯一强制失败链”即可得到贡献。