20241101

T1

美丽的序列

dp 中记录每个数上一次出现位置和当前位置的差,和 \(7\)(或这个数)取 \(\min\)。状态数很少,直接做即可。

代码
#include <iostream>
#include <unordered_map>
#include <vector>
#include <map>
using namespace std;
const int P = 1000000007;
inline void Madd(int& x, int y) { (x += y) >= P ? (x -= P) : 0; }
int n, m;
int a[10];
bool u[10];
unordered_map<int, int> mp;
int _mp[310005];
int cnt;
int out[310005][12];
int pw[12];
void dfs(int x, int cur) {
    if (x == m + 1) {
        if (!u[0]) 
            return;
        if (!mp.count(cur)) 
            _mp[mp[cur] = ++cnt] = cur;
        return;
    }
    for (int i = 0; i < 7; i++) {
        if (!u[i]) {
            u[i] = 1;
            dfs(x + 1, cur + i * pw[x - 1]);
            u[i] = 0;
        }
    }
    dfs(x + 1, cur + 7 * pw[x - 1]);
}
int dp[105][310005];
int main() {
    freopen("seq.in", "r", stdin);
    freopen("seq.out", "w", stdout);
    pw[0] = 1;
    for (int i = 1; i <= 9; i++) pw[i] = pw[i - 1] * 10;
    cin >> n >> m;
    for (int i = 1; i <= m; i++) cin >> a[i];
    dfs(1, 0);
    int ecnt = 0;
    for (int i = 1; i <= cnt; i++) {
        int tmp = _mp[i];
        for (int j = 0; j < m; j++) {
            if ((_mp[i] % pw[j + 1]) / pw[j] != 7) 
                tmp += pw[j];
        }
        for (int j = 1; j <= m; j++) {
            int t = (_mp[i] % pw[j]) / pw[j - 1];
            if (t >= a[j] - 1) {
                t = (tmp % pw[j]) / pw[j - 1];
                out[i][j] = mp[tmp - t * pw[j - 1]];
            }
        }
    }
    for (int i = 1; i <= m; i++) {
        int x = 0;
        for (int j = 1; j <= m; j++) 
            x = x + 7 * (i != j) * pw[j - 1];
        dp[1][mp[x]] = 1;
    }
    int ans = 0;
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= cnt; j++) {
            if (!dp[i - 1][j]) 
                continue;
            for (int k = 1; k <= m; k++) 
                (out[j][k]) ? Madd(dp[i][out[j][k]], dp[i - 1][j]) : void();
        }
    }
    for (int i = 1; i <= cnt; i++) Madd(ans, dp[n][i]);
    cout << ans << "\n";
    return 0;
}

T2

开一棵线段树维护原序列,每次操作相当于合并左右儿子或交换左右儿子。只考虑把右儿子并到左儿子的操作,则另一种操作可以通过再交换左右儿子实现。考虑对每一层打标记,注意到如果发生了合并操作,则这次合并之前的操作都是没用的,所以只需要关心最后一次合并操作以及其之后的交换操作。由于每层的每个点操作的进度不一样,考虑在每一层记一个时间轴。对每个点记录其当前执行到了哪一个合并操作,然后 pushdown 一个点的时候就检查它是否还有没执行的合并操作,然后再看是否要交换左右儿子。合并都是线段树合并,容易发现复杂度是对的。

代码
#include <iostream>
#define int long long
#define lowbit(x) ((x) & (-(x)))
using namespace std;
int n, q;
int a[600005];
struct node {
    int l, r, s;
    int tg;
} T[50000005];
int lm[30], t[30]; // last merge, cur time
struct Segment_Tree {
    int ncnt;
    int New(int d) { T[++ncnt] = (node) { 0, 0, 0, t[d] }; return ncnt; }
    void pushdown(int o, int d) {
        if (T[o].tg < lm[d]) 
            T[o].l = Merge(T[o].l, T[o].r, d - 1), T[o].r = 0, T[o].tg = lm[d];
        if ((t[d] ^ T[o].tg) & 1) 
            swap(T[o].l, T[o].r);
        T[o].tg = t[d];
    }
    int Merge(int x, int y, int d) {
        if (!x || !y) 
            return x | y;
        if (!d) {
            T[x].s += T[y].s;
            return x;
        }
        pushdown(x, d);
        pushdown(y, d);
        T[x].l = Merge(T[x].l, T[y].l, d - 1);
        T[x].r = Merge(T[x].r, T[y].r, d - 1);
        T[x].s = T[T[x].l].s + T[T[x].r].s;
        return x;
    }
    void Add(int& o, int l, int r, int x, int y, int d) {
        if (!o) 
            o = New(d);
        if (l == r) {
            T[o].s += y;
            return;
        }
        pushdown(o, d);
        int mid = (l + r) >> 1;
        x <= mid ? Add(T[o].l, l, mid, x, y, d - 1) : Add(T[o].r, mid + 1, r, x, y, d - 1);
        T[o].s = T[T[o].l].s + T[T[o].r].s;
    }
    int Query(int o, int l, int r, int L, int R, int d) {
        if (!o) 
            return 0;
        if (L <= l && r <= R) 
            return T[o].s;
        pushdown(o, d);
        int mid = (l + r) >> 1;
        if (R <= mid) 
            return Query(T[o].l, l, mid, L, R, d - 1);
        if (L > mid) 
            return Query(T[o].r, mid + 1, r, L, R, d - 1);
        return Query(T[o].l, l, mid, L, R, d - 1) + Query(T[o].r, mid + 1, r, L, R, d - 1);
    }
} seg;
int rt;
signed main() {
    freopen("jeden.in", "r", stdin);
    freopen("jeden.out", "w", stdout);
    cin >> n >> q;
    int d = -1;
    for (int x = n; x; x /= 2) ++d;
    // cout << __lg(n) << "\n";
    for (int i = 0; i < n; i++) cin >> a[i], seg.Add(rt, 0, n - 1, i, a[i], d);
    while (q--) {
        string s;
        int x, y;
        cin >> s >> x;
        if (s == "add") {
            cin >> y;
            seg.Add(rt, 0, n - 1, x, y, d);
        } else if (s == "sum") {
            cin >> y;
            cout << seg.Query(rt, 0, n - 1, x, y, d) << "\n";
        } else if (s == "and") {
            for (int i = 1; i <= d; i++) {
                if (!(x & (1 << (i - 1)))) 
                    lm[i] = ++t[i];
            }
        } else if (s == "or") {
            for (int i = 1; i <= d; i++) {
                if (x & (1 << (i - 1))) 
                    lm[i] = ++t[i], ++t[i];
            }
        } else 
            for (int i = 1; i <= d; i++) t[i] += (x >> (i - 1)) & 1;
    }
    return 0;
}

T3

朋友

首先每条鱼的行动范围是一段区间。然后会发现在钦定了最大的聚集位置之后,其两边都不会互相产生任何影响,也就是两边独立。因此直接考虑区间 dp,转移枚举当前区间中最大的聚集地,然后令这个区间中所有能走到这个聚集地的都走到这个聚集地,算一下贡献即可。

代码
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
int n, m, w;
int h[10005];
int p[205], J[205];
int mx[17][10005];
int lg2[10005];
int L[10005], R[10005];
int d[405], dcnt;
inline int Qmax(int l, int r) {
    int k = lg2[r - l + 1];
    return max(mx[k][l], mx[k][r - (1 << k) + 1]);
}
int S[405][405][405];
int dp[405][405];
inline int C2(int x) { return x * (x - 1) / 2; }
signed main() {
    freopen("friend.in", "r", stdin);
    freopen("friend.out", "w", stdout);
    cin >> n >> m >> w;
    lg2[0] = -1;
    for (int i = 1; i <= n; i++) cin >> h[i], h[i] = w - h[i], mx[0][i] = h[i], lg2[i] = lg2[i - 1] + ((i & (i - 1)) == 0);
    for (int i = 1; (1 << i) <= n; i++) {
        for (int j = 1; j + (1 << i) - 1 <= n; j++) 
            mx[i][j] = max(mx[i - 1][j], mx[i - 1][j + (1 << (i - 1))]);
    }
    for (int i = 1; i <= m; i++) {
        cin >> p[i] >> J[i];
        int l = 1, r = p[i], mid;
        L[i] = p[i] + 1;
        R[i] = p[i] - 1;
        while (l <= r) {
            mid = (l + r) >> 1;
            if (Qmax(mid, p[i]) < J[i]) 
                L[i] = mid, r = mid - 1;
            else 
                l = mid + 1;
        }
        l = p[i], r = n;
        while (l <= r) {
            mid = (l + r) >> 1;
            if (Qmax(p[i], mid) < J[i]) 
                R[i] = mid, l = mid + 1;
            else 
                r = mid - 1;
        }
        L[i] = max(1ll, L[i] - 1);
        R[i] = min(n, R[i] + 1);
        d[++dcnt] = L[i];
        d[++dcnt] = R[i];
    }
    sort(d + 1, d + dcnt + 1);
    dcnt = unique(d + 1, d + dcnt + 1) - d - 1;
    for (int i = 1; i <= m; i++) {
        L[i] = lower_bound(d + 1, d + dcnt + 1, L[i]) - d;
        R[i] = lower_bound(d + 1, d + dcnt + 1, R[i]) - d;
    }
    for (int i = 1; i <= dcnt; i++) {
        for (int j = 1; j <= m; j++) {
            if (L[j] <= i && i <= R[j]) 
                ++S[i][L[j]][R[j]];
        }
        for (int j = dcnt; j; j--) {
            for (int k = 1; k <= dcnt; k++)     
                S[i][j][k] += S[i][j + 1][k] + S[i][j][k - 1] - S[i][j + 1][k - 1];
        }
    }
    for (int i = 1; i <= dcnt; i++) dp[i][i] = C2(S[i][i][i]);
    for (int i = 2; i <= dcnt; i++) {
        for (int l = 1; l + i - 1 <= dcnt; l++) {
            int r = l + i - 1;
            for (int k = l; k <= r; k++) dp[l][r] = max(dp[l][r], dp[l][k - 1] + dp[k + 1][r] + C2(S[k][l][r]));
        }
    }
    cout << dp[1][dcnt] << "\n";
    return 0;
}

T4

环覆盖

容易发现合法的等价条件是所有点度数均为偶数。因此有显然的 \(\mathcal{O}(2^nm^2)\) dp,记录每个点的奇偶性以及当前选了几条边。然后考虑用集合幂级数刻画这个 dp。我们要求的是 \([x^{\varnothing}]F\),其中 \(F = \prod\limits_{E}(1 + x^{\{ u, v \}}y)\)。我们直接 FWT。有

\[[x^S]fwt(F) = \sum\limits_{T \subset U} (-1)^{S \cap T}[x^T]F \]

以及

\[[x^S]F = \frac{1}{2^n}\sum\limits_{T \subset U} (-1)^{S \cap T}[x^T]fwt(F) \]

那么

\[\begin {equation} \begin {split} [x^{\varnothing}]F &= \frac{1}{2^n}\sum\limits_{S}[x^S]fwt(F) \\ &= \frac{1}{2^n}\sum\limits_{S}\prod\limits_{E}[x^S]fwt(1 + x^{\{ u, v \}}y) \\ & 注意到 [x^S]fwt(1 + x^{\{ u, v \}}y) 必然是 1 \pm y 的形式(代入最前面的两个式子和 fwt 的线性性容易得到),因此设 a_S 表示有多少条边 (u, v) 使得 [x^S]fwt(1 + x^{\{ u, v \}}y) = 1 - y,可得 \\ &= \frac{1}{2^n}\sum\limits_{S}(1 + y)^{m - a_S}(1 - y)^{a_S} \\ & 不难发现 a_S 的实际意义就是只有一个端点在 S 中的边数,值域很小,于是考虑设 f_v = \sum\limits_{S}[a_S = v],则有 \\ &= \frac{1}{2^n}\sum\limits_vf_v(1 + y)^{m - v}(1 - y)^v \end {split} \end {equation} \]

到这里,如果能算出 \(f_v\),则这个题就可以在 \(\mathcal{O}(m^3)\) 的复杂度完成。接下来考虑求 \(f_v\)。我们考虑动态维护一个点集 \(S\) 及其 \(a_S\)。每次往里加入一个点,加入点 \(x\)\(a_S\) 增加与 \(x\) 相邻且不在 \(S\) 中点的个数,减掉与 \(x\) 相邻且在 \(S\) 中点的个数。这个事情容易用 bitset 维护,每次拿当前集合的 bitset 与一下 \(x\) 的邻接矩阵即可。总复杂度 \(\mathcal{O}(\frac{n2^n}{\omega} + m^3)\)

代码
#include <iostream>
#include <string.h>
#include <bitset>
#define int long long
using namespace std;
const int P = 1000000007, i2 = (P + 1) / 2;
inline void Madd(int& x, int y) { (x += y) >= P ? (x -= P) : 0; }
int n, m;
bitset<30> cur, g[30];
int f[305];
void dfs(int x, int cnt) {
    if (x == n + 1) 
        return ++f[cnt], void();
    dfs(x + 1, cnt);
    cur[x] = 1;
    dfs(x + 1, cnt - (cur & g[x]).count() + ((~cur) & g[x]).count());
    cur[x] = 0;
}
int F[305], G[305], H[305];
int C[305][305];
int ans[305];
signed main() {
    freopen("even.in", "r", stdin);
    freopen("even.out", "w", stdout);
    cin >> n >> m;
    for (int i = C[0][0] = 1; i <= m; i++) {
        for (int j = C[i][0] = 1; j <= i; j++) 
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
    }
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        g[u][v] = g[v][u] = 1;
    }
    dfs(1, 0);
    for (int v = 0; v <= m; v++) {
        for (int i = 0; i <= m - v; i++) {
            for (int j = 0; j <= v; j++) 
                Madd(H[i + j], C[m - v][i] * (j & 1 ? P - C[v][j] : C[v][j]) % P);
        }
        for (int i = 0; i <= m; i++) Madd(ans[i], H[i] * f[v] % P), H[i] = 0;
    }
    int x = 1;
    for (int i = 1; i <= n; i++) x = x * i2 % P;
    for (int i = 0; i <= m; i++) cout << ans[i] * x % P << " ";
    cout << "\n";
    return 0;
}

在层上维护懒标记,层内不同点执行过的标记可以用时间轴描述。

考察特殊位置的性质。

posted @ 2024-11-04 21:51  forgotmyhandle  阅读(14)  评论(0)    收藏  举报