Coins Exhibition 题解

CF930E

很水的 2900,连我都想出来了。


第一步,离散化,把所有区间离散化了,因为我们只关心每个硬币对每个区间的包含关系,并不在乎它具体是哪个。

先打暴力:设 \(f_{i,j,0/1}\) 表示考虑到了第 \(i\) 个段,上个与当前硬币不同面的硬币的位置在哪个段里,还有当前段最后一个硬币的正反。

正为 \(1\),反为 \(0\)

则易得出转移方程。

在这段里全部填 \(0\)

\[f_{i,j,0} \to f_{i + 1,j, 0} \]

\[f_{i,j,1}\to f_{i+1,i,0} \]

全部添 \(1\)

\[f_{i,j,1}\to f_{i+1,j,1} \]

\[f_{i,j,0} \to f_{i+1,i,1} \]

\(0,1\) 混杂,此处之所以要减一是因为你要固定最后一个的正反:

\[(2^{len-1}-1)f_{i,j,0/1}\to f_{i+1,i+1,1} \]

\[(2^{len-1} - 1)f_{i,j,0/1} \to f_{i+1,i+1, 0} \]

注意由于要考虑限制:如果不考虑离散化则,在 \([l,r]\) 内至少有一个 \(1\),此时考虑到第 \(i\) 个硬币,则 \(f_{r,i,0}\space(i<l)\) 是不合法的,应置为 \(0\)

而离散化之后小于不好刻画,故我们考虑把 \(l-1\) 扔进去离散化而不是 \(l\)

这样条件就能刻画为:对于每个状态 \(f_{r,i,0} \space (i \le l-1)\) 算完之后我们将其清零。

注意到由于只有在 \(f_i\)\(f_{i+1}\) 之间转移,我们滚动数组。

滚完之后发现,除了清零之外,改变的位置个数是 \(O(1)\) 的,即 \(f_{i+1,0/1}\)\(f_{i,0/1}\)

而在这些的转移式中所有的 \(j\) 它们都会产生固定 \(1\)\((2^{len-1} - 1)\) 的贡献。

这启发我们维护当前 \(f_{*,0}\) 的和与 \(f_{*,1}\) 的和。

每次将对应的贡献乘上总和加上去集合。

于是除了清零之外的操作我们都能在 \(O((n+m)\log k+(n+m)\log (n+m))\) 的时间内解决了。

那清零怎么办呢?

由于只有 \(f\) 的后边会发生变化,前边的变化只有清零,因此一个格子如果被清零它就彻底没戏了。

于是我们记录 \(f_{*,0/1}\) 的两个指针指向当前数组中第一个非零,如果要清空更多零暴力移指针就行了。

因为每清一个零指针就向后一格,而指针单调向后走所以均摊复杂度 \(O(n+m)\)

于是我们就做完了。

小细节:

  • 初始条件是 \(f_{0,0/1}=1\)
  • 离散化的时候要把 \(0,k\) 加入,因为他们是 DP 的起点与终点。
  • 输出答案的时候要除以 \(2\),因为你多算了 \(0\) 号硬币的正反但压根就不存在 \(0\) 号硬币。
#include <bits/stdc++.h>
using namespace std;

#define int long long
#define endl '\n'
#define debug(x) cerr << #x << " = " << x << endl
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define gn(u, v) for (int v : G.G[u])
#define pb emplace_back
#define mp make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define pii pair<int, int>
#define vi vector<int>
#define vpii vector<pii>
#define vvi vector<vi>
#define no cout << "NO" << endl
#define yes cout << "YES" << endl
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define tomin(x, y) ((x) = min((x), (y)))
#define tomax(x, y) ((x) = max((x), (y)))
#define ck(mask, i) (((mask) >> (i)) & 1)
#define pq priority_queue
#define umap unordered_map
#define FLG (cerr << "Alive!" << endl);

constexpr int MAXN = 5e5 + 5;
constexpr int MOD = 1e9 + 7;
int k, n, m;
int l[MAXN], r[MAXN], sum[2], f[MAXN][2];
int c[MAXN], tot;

int chk[2][MAXN], tic[2] = { -1, -1 };

int qpow(int x, int y) {
    if (y < 0) return 0;
    int ans = 1;
    while (y) {
        if (y & 1)
            ans = ans * x % MOD;
        x = x * x % MOD;
        y >>= 1;
    }
    return ans;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> k >> n >> m;
    rep (i, 1, n + m) {
        cin >> l[i] >> r[i];
        l[i]--;
        c[++tot] = l[i];
        c[++tot] = r[i];
    }
    c[++tot] = 0;
    c[++tot] = k;
    sort(c + 1, c + tot + 1);
    tot = unique(c + 1, c + tot + 1) - c - 1;
    rep (i, 1, n + m) {
        l[i] = lower_bound(c + 1, c + tot + 1, l[i]) - c;
        r[i] = lower_bound(c + 1, c + tot + 1, r[i]) - c;
    }

    memset(chk, 0xff, sizeof chk);

    rep (i, 1, n)
        tomax(chk[0][r[i]], l[i]);

    rep (i, n + 1, n + m)
        tomax(chk[1][r[i]], l[i]);

    f[0][0] = f[0][1] = 1;
    int sum[2] = { 1, 1 };
    rep (i, 1, tot - 1) {
        (f[i][0] += sum[1]) %= MOD;
        (f[i][1] += sum[0]) %= MOD;
        int cur;
        if (c[i + 1] - c[i] == 1) cur = 0;
        else (cur = qpow(2, c[i + 1] - c[i] - 1) + MOD - 1) %= MOD;
        (cur *= sum[0] + sum[1]) %= MOD;
        (f[i + 1][0] += cur) %= MOD;
        (f[i + 1][1] += cur) %= MOD;
        sum[0] = sum[1] = (sum[0] + sum[1]) % MOD;
        (sum[0] += cur) %= MOD;
        (sum[1] += cur) %= MOD;
        while (tic[0] < chk[0][i + 1]) {
            tic[0]++;
            (sum[0] += MOD - f[tic[0]][0]) %= MOD;
        }
        while (tic[1] < chk[1][i + 1]) {
            tic[1]++;
            (sum[1] += MOD - f[tic[1]][1]) %= MOD;
        }
    }
    cout << (sum[0] + sum[1]) * (MOD + 1) / 2 % MOD << endl;

    return 0;
}
posted @ 2025-02-15 16:10  LightningCreeper  阅读(3)  评论(0编辑  收藏  举报