HAOI2018 简要题解

这套题是 dy, wearry 出的。学长好强啊,可惜都 \(wc\) 退役了。。

话说 wearry 真的是一个计数神仙。。就没看到他计不出来的题。。。每次考他模拟赛总有一两道毒瘤计数TAT

上午的官方题解可以看 dy0607 的博客,写的挺详细的。

「HAOI2018」奇怪的背包

题意

小C非常擅长背包问题,他有一个奇怪的背包,这个背包有一个参数 \(P\) ,当他向这个背包内放入若干个物品后,背包的重量是物品总体积对 \(P\) 取模后的结果.

现在小C有 \(n\) 种体积不同的物品,第 \(i\) 种占用体积为 \(V_i\) ,每种物品都有无限个.他会进行 \(q\) 次询问,每次询问给出重量 \(w_i\) ,你需要回答有多少种放入物品的方案,能将一个初始为空的背包的重量变为 \(w_i\) .注意,两种方案被认为是不同的,当且仅当放入物品的种类不同,而与每种物品放入的个数无关.不难发现总的方案数为 \(2^n\) .

由于答案可能很大,你只需要输出答案对 \(10^9 + 7\) 取模的结果.

\(1 \le n, q \le 10^6, 3 \le P \le 10^9, 0 < V_i, w_i < P\)

题解

题目就是求,有多少种选择物品的方式,使得下面的同余方程有解,(假设选择的物品的体积为 \(a_1, a_2, \dots, a_k\) )

\[
\sum_{i = 1}^k x_ia_i \equiv w_j \pmod{P}\\Rightarrow \sum_{i = 1}^k x_ia_i + x_0P = w_j
\]

由于裴蜀定理,有解当且仅当 \(\gcd(a_1, a_2, …, a_k, P) \mid w_j\) 。

然后令 \(f_{i, x}\) 为前 \(i\) 种物品,选择物品的体积与 \(P\) 做 \(\gcd\) 结果为 \(x\) 的方案数。

直接转移是 \(\mathcal O(n \sigma(P) \log P)\) 的,\(\sigma(P)\) 为 \(P\) 的约数个数,大概是 \(P^{\frac 13}\) 的级别。

这样显然还不够优秀,不难发现对于 \(\gcd(a_i, P)\) 相同的 \(i\) 的转移其实是一样的,我们把这些合并一起转移就好啦。

此时复杂度优化到了 \(\mathcal O((\sigma^2(P) + n + q) \log P)\) 其中 \(\log P\) 为取 \(\gcd\) 的复杂度。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2523.in", "r", stdin);
    freopen ("2523.out", "w", stdout);
#endif
}

const int N = 5100, Mod = 1e9 + 7;

int n, q, P, d[N], cnt;

inline int id(int x) {
    return lower_bound(d + 1, d + cnt + 1, x) - d;
}

int dp[N][N], tot[N], Pow[1001000], ans[N];

int main () {

    File();

    n = read(); q = read(); P = read();

    For (i, 1, sqrt(P + .5)) if (!(P % i)) {
        d[++ cnt] = i;
        if (i * i != P) d[++ cnt] = P / i;
    }
    sort(d + 1, d + cnt + 1);

    For (i, 1, n)
        ++ tot[id(__gcd(P, read()))];

    Pow[0] = 1;
    For (i, 1, n) Pow[i] = Pow[i - 1] * 2 % Mod;

    dp[0][cnt] = 1;
    For (i, 1, cnt) For (j, 1, cnt) {
        int x = id(__gcd(d[i], d[j]));
        dp[i][x] = (dp[i][x] + 1ll * dp[i - 1][j] * (Pow[tot[i]] - 1)) % Mod;
        dp[i][j] = (dp[i][j] + dp[i - 1][j]) % Mod;
    }

    For (i, 1, cnt) For (j, 1, cnt)
        if (!(d[i] % d[j])) (ans[i] += dp[cnt][j]) %= Mod;

    while (q --) printf ("%d\n", ans[id(__gcd(read(), P))]);

    return 0;

}

「HAOI2018」反色游戏

题意

小C和小G经常在一起研究搏弈论问题,有一天他们想到了这样一个游戏.

有一个 \(n\) 个点 \(m\) 条边的无向图,初始时每个节点有一个颜色,要么是黑色,要么是白色.现在他们对于每条边做出一次抉择:要么将这条边连接的两个节点都反色(黑变白,白变黑),要么不作处理.他们想把所有节点都变为白色,他们想知道在 \(2^m\) 种决策中,有多少种方案能达成这个目标.

小G认为这个问题太水了,于是他还想知道,对于第 \(i\) 个点,在删去这个点及与它相连的边后,新的答案是多少.

由于答案可能很大,你只需要输出答案对 \(10^9 + 7\) 取模后的结果.

对于所有数据,有 \(1 \le T \le 5, 1 \le n, m \le 10^5, 1 \le u, v \le n\) ,没有重边和自环。

题解

一道有意思的 找规律 结论题 qwq

首先我们不管删点操作,思考一下什么时候是无解的。一个显然的必要条件是,一个连通块内必须要有偶数个黑点,而这个条件也是充分的。为什么呢?

我们将黑点任意两两配对,对一对黑点考虑它们之间的任意一条路径,将路径上的边的状态(操作or不操作)取反,立即可以得到一种合法方案。

同时我们发现,对于任意一种偶数个黑点的方案,如果进行上面的配对,都可以唯一对应到一种全为白色的方案。

也就是说,只要有解,解的个数都是相同的。对于一个连通块,假设边数为 \(m\) ,点数为 \(n\) ,有 \(2^{n ? 1}\) 种对点染色的方案是有解的且解的个数相同,总方案数为 \(2^m\),那么对于一个有解的局面这个连通块的方案数就是 \(2^{m ? n + 1}\) ! 对于整张图,总的方案数就是 \(2^{m ? n + p}\) ,其中 \(p\) 是连通块个数。

看起来不太好想,规律其实 很好找

有一种用线性基理解的方式更加优秀,不难发现每条边对应一个长度为 \(n\) 的位向量,其中只有两个点为 \(1\) 。对于一个联通块来说,如果有解就是要异或出 \(0\) ,然后方案数是 \(2^{free}\) 。

不难发现自由元 \(free\) 的个数其实就是 \(m - n + 1\) ,因为极大线性无关组大小为 \(n - 1\) 是一定可达的,然后一开始共有 \(m\) 个向量,减一减就是的了。

然后剩下只需要维护每个点被删去的答案,这个用类似于 Tarjan 求割点的方法去做。具体来说只需要维护一下断开某个点会产生几个联通块,以及每个联通块是否合法即可。

复杂度是 \(\mathcal O(n + m)\) 的。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2524.in", "r", stdin);
    freopen ("2524.out", "w", stdout);
#endif
}

const int N = 1e5 + 1e3, Mod = 1e9 + 7;

int n, m, deg[N]; char S[N];

vector<int> G[N]; bool pass[N]; 

int dfn[N], lowlink[N], clk;
int cut[N], sz[N], sub[N], bel[N], now;

#define chk(x) !((x) & 1)

void Tarjan(int u, int fa = 0) {
    dfn[u] = lowlink[u] = ++ clk;
    pass[u] = true; sz[u] = (S[u] ^ 48);
    bel[u] = now; sub[u] = cut[u] = 0;
    for (int v : G[u]) if (!dfn[v]) {
        Tarjan(v, u);
        chkmin(lowlink[u], lowlink[v]);
        sz[u] += sz[v];
        if (lowlink[v] >= dfn[u]) {
            ++ cut[u];
            sub[u] += sz[v];
            pass[u] &= chk(sz[v]);
        }
    } else if (v != fa) chkmin(lowlink[u], dfn[v]);
    if (!fa) -- cut[u];
}

int Pow2[N];

int main () {

    File();

    int cases = read();
    while (cases --) {

        n = read(); m = read();
        For (i, 1, n) G[i].clear(), deg[i] = dfn[i] = 0;

        For (i, 1, m) {
            int u = read(), v = read();
            G[u].push_back(v); G[v].push_back(u);
            ++ deg[u]; ++ deg[v];
        }
        scanf ("%s", S + 1);

        int cnt_odd = 0, ans = m - n;
        For (i, 1, n) if (!dfn[i])
            now = i, Tarjan(i), cnt_odd += (sz[i] & 1), ++ ans;

        Pow2[0] = 1;
        For (i, 1, m) Pow2[i] = Pow2[i - 1] * 2 % Mod;

        printf ("%d", cnt_odd ? 0 : Pow2[ans]);
        For (i, 1, n)
            if (!deg[i]) {
                printf (" %d", (cnt_odd - sz[i]) ? 0 : Pow2[ans]);
            } else {
                if (pass[i] && chk(sz[bel[i]] - sub[i] - (S[i] ^ 48)) && !(cnt_odd - (sz[bel[i]] & 1)))
                    printf (" %d", Pow2[ans - deg[i] + (cut[i] + 1)]);
                else printf (" 0");
            }
        puts("");

    }

    return 0;

}

「HAOI2018」字串覆盖

题意

小C对字符串颇有研究,他觉得传统的字符串匹配太无聊了,于是他想到了这样一个问题.

对于两个长度为 \(n\) 的串 \(A, B\) , 小C每次会给出给出 \(4\) 个参数 \(s, t, l, r\) . 令 \(A\) 从 \(s\) 到 \(t\) 的子串(从 \(1\) 开始标号)为 \(T\),令 \(B\) 从 \(l\) 到 \(r\) 的子串为 \(P\).然后他会进行下面的操作:

如果 \(T\) 的某个子串与 \(P\) 相同,我们就可以覆盖 \(T\) 的这个子串,并获得 \(K - i\) 的收益,其中 \(i\) 是初始时 \(A\) 中(注意不是 \(T\) 中)这个子串的起始位置,\(K\)是给定的参数.一个位置不能被覆盖多次.覆盖操作可以进行任意多次,你需要输出获得收益的最大值.

注意每次询问都是独立的,即进行一次询问后,覆盖的位置会复原.

对于所有数据,有 \(1 \le n, q \le 10^5\) ,\(A, B\) 仅由小写英文字母组成,\(1 \le s \le t \le n, 1 \le l \le r \le n, n < K \le 10^9\).

对于 \(n = 10^5\) 的测试点,满足 \(51 \le r - l \le 2000\) 的询问不超过 \(11000\) 个,且 \(r - l\) 在该区间内均匀随机.

题解

其实那个数据范围提示了一下做法。。。就是对于 \(r - l\) 的长度进行讨论。

对于 \(r - l\) 较大的点,显然段数比较少,这个地方用数据结构模拟选取方案就行了。一个比较经典的解法是 后缀数组+主席树

我们把 \(\underline{A\#B}\) 做后缀排序,就是我们可以选取的串是和 \(B_{l, r}\) 相同的串,这对应了后缀数组上的一段连续区间。然后我们要从这个区间里选取尽量靠前的点,满足和前面选取的区间不重复。这个直接在主席树上二分即可。

那么现在只需要解决 \(r - l\) 比较小的情况。我们对于每种长度单独考虑即可。就是从前往后依次选很多段,每个相邻两端连边的话就构成了一个树上结构,利用树上倍增统计答案即可。

利用均值法在 \(\sqrt n\) 分开讨论复杂度是最优的,为 \(\mathcal O(n \sqrt n \log n)\) 。

但是这样应该会跑的比较慢,dy 为了这个显著快于暴力,限制了长度的频率,我们在 \(50\) 分开就行了。

好像 孔爷 在知乎上回答可以做到 \(\mathcal O(n \sqrt n)\) 。

具体来说继续利用均值法,前面用值域分块,后面用轻重链剖分贪心。

代码

实在比较难写。。抄了一些 dy 的写法, dyy 码力还是强啊。

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;
using ll = long long;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2525.in", "r", stdin);
    freopen ("2525.out", "w", stdout);
#endif
}

const int N = 2e5 + 1e3, Gap = 50;

int Log2[N], n, K, q; char S[N]; 

int m, sa[N], rk[N], tmp[N], pre[N];

void Radix_Sort() {
    For (i, 1, m) pre[i] = 0;
    For (i, 1, n) ++ pre[rk[i]];
    For (i, 1, m) pre[i] += pre[i - 1];
    Fordown (i, n, 1) sa[pre[rk[tmp[i]]] --] = tmp[i];
}
void Build_SA() {
    For (i, 1, n) rk[tmp[i] = i] = S[i];
    m = 255; Radix_Sort();
    for (int k = 1, p = 0; k <= n; k <<= 1, p = 0) {
        For (i, n - k + 1, n) tmp[++ p] = i;
        For (i, 1, n) if (sa[i] > k) tmp[++ p] = sa[i] - k;
        Radix_Sort(); swap(rk, tmp);
        rk[sa[1]] = m = 1;
        For (i, 2, n)
            rk[sa[i]] = (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + k] == tmp[sa[i - 1] + k]) ? m : ++ m;
        if (m >= n) break;
    }
}

int height[N];
void Get_Height() {
    for (int i = 1, j, k = 0; i <= n; ++ i) {
        if (k) -- k; j = sa[rk[i] + 1];
        if (!j) continue;
        while (S[i + k] == S[j + k]) ++ k;
        height[rk[i]] = k;
    }
}

template<int Maxn>
struct President_Segment_Tree {

    int ls[Maxn], rs[Maxn], tot[Maxn], Node;

#define mid ((l + r) >> 1)

    void Update(int &o, int pre, int l, int r, int up) {
        tot[o = ++ Node] = tot[pre] + 1; ls[o] = ls[pre]; rs[o] = rs[pre];
        if (l == r) return;
        if (up <= mid) Update(ls[o], ls[pre], l, mid, up);
        else Update(rs[o], rs[pre], mid + 1, r, up);
    }

    int Find(int x, int y, int l, int r, int qp) {
        if (!(tot[y] - tot[x])) return 0;
        if (l == r) return l;
        int res = qp <= mid ? Find(ls[x], ls[y], l, mid, qp) : 0;
        return res ? res : Find(rs[x], rs[y], mid + 1, r, qp);
    }

};

President_Segment_Tree<N * 20> T; int rt[N];

int minv[N][22];

struct Query {
    int s, t, l, len, id, pos;
} Q[N];

int fa[N][22]; ll sum[N][22], ans[N];

inline void Work(int len, int &it) {
    static int pos[N];
    if (Q[it].len != len) return;
    for (int l = 1, r = l; l <= n * 2 + 1; r = l = r + 1) {
        int cnt = 0;
        while (height[r] >= len) ++ r;
        For (j, l, r) if (sa[j] <= n) pos[++ cnt] = sa[j];

        if (Q[it].pos < l || Q[it].pos > r) continue;

        sort(pos + 1, pos + cnt + 1);
        int lim = Log2[min(cnt + 1, n / len)], k = 1;
        pos[cnt + 1] = 2 * n + 1;

        For (i, 1, cnt) {
            while (pos[k] - pos[i] < len) ++ k;
            fa[i][0] = k, sum[i][0] = K - pos[i];
        }

        For (j, 1, lim) For (i, 1, cnt) {
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
            sum[i][j] = sum[i][j - 1] + sum[fa[i][j - 1]][j - 1];
        }

        for (; Q[it].len == len && Q[it].pos <= r; ++ it) {
            int s = Q[it].s, t = Q[it].t, x = lower_bound(pos + 1, pos + cnt + 2, s) - pos;
            if (pos[x] <= t) {
                int u = x; ll res = 0;
                Fordown (i, lim, 0) if (pos[fa[u][i]] && pos[fa[u][i]] <= t)
                    res += sum[u][i], u = fa[u][i];
                ans[Q[it].id] = res + sum[u][0];
            }
        }

        For (i, 1, cnt + 1) For (j, 0, lim) fa[i][j] = sum[i][j] = 0;
    }
}

int main () {

    File();

    n = read(); K = read();

    scanf ("%s", S + 1); S[n + 1] = '#';
    scanf ("%s", S + n + 2);

    n = n * 2 + 1; Build_SA(); Get_Height(); 

    For (i, 1, n) minv[i][0] = height[i];
    For (i, 2, n) Log2[i] = Log2[i >> 1] + 1;
    For (j, 1, Log2[n])
        for (int i = 1; i + (1 << j) <= n; ++ i)
            minv[i][j] = min(minv[i][j - 1], minv[i + (1 << (j - 1))][j - 1]);

    n >>= 1;

    q = read();
    For (i, 1, q) {
        int s = read(), t = read(), l = read(), r = read();
        Q[i] = (Query) {s, t - (r - l), l, r - l + 1, i, rk[l + n + 1]};
    }
    sort(Q + 1, Q + q + 1, [&](Query lhs, Query rhs) { return lhs.len != rhs.len ? lhs.len < rhs.len : lhs.pos < rhs.pos; } );

    int it = 1;
    For (i, 1, min(n, Gap)) Work(i, it);
    For (i, 1, 2 * n + 1) {
        if (sa[i] <= n) T.Update(rt[i], rt[i - 1], 1, n, sa[i]);
        else rt[i] = rt[i - 1];
    }

    for (; it <= q; ++ it) {
        Query A = Q[it];
        int x = A.pos, l = x, r = x;
        Fordown (i, Log2[n], 0)
            if (l - (1 << i) > 0 && minv[l - (1 << i)][i] >= A.len) l -= 1 << i;
        Fordown (i, Log2[n], 0)
            if (r + (1 << i) <= 2 * n + 1 && minv[r][i] >= A.len) r += 1 << i;
        for(int pos = A.s, cur; pos <= n; pos = cur + A.len) {
            cur = T.Find(rt[l - 1], rt[r], 1, n, pos);
            if (!cur || cur > A.t) break;
            ans[A.id] += K - cur;
        }
    }

    For (i, 1, q) printf ("%lld\n", ans[i]);

    return 0;

}

「HAOI2018」苹果树

题意

小C在自己家的花园里种了一棵苹果树,树上每个结点都有恰好两个分支。经过细心的观察,小C发现每一天这棵树都会生长出一个新的结点。

第一天的时候, 果树会长出一个根结点,以后每一天,果树会随机选择一个当前树中没有长出过结点的分支,
然后在这个分支上长出一个新结点,新结点与分支所属的结点之间连接上一条边。

小C定义一棵果树的不便度为树上两两结点之间的距离之和,两个结点之间的距离定义为从一个点走到另一个点的路径经过的边数。

现在他非常好奇,如果 \(N\) 天之后小G来他家摘苹果,这个不便度的期望 \(E\) 是多少。但是小C讨厌分数,,所以他只想知道 \(E \times N!\) 对 \(P\) 取模的结果,可以证明这是一个整数。

\(N \le 2000, P \le 10^9 + 7\)

题解

还是太菜了,这道题还是记不清。

好像有千奇百怪的做法,最简单的还是考虑边对于点对的贡献。

你考虑 \(i \to fa_i\) 这条边被多少个点对穿过,考虑枚举一下 \(i\) 子树的大小 \(j\) ,那么会被 \(j \times (n - j)\) 对点穿过。

我们只需要统计 \(i\) 子树大小为 \(j\) 的方案数,先考虑算子树内的方案数,也就是从 \(n - i\) 个点里选 \(j - 1\) 个点乘上子树内的合法结构数,也就是 \(\displaystyle {n - i \choose j - 1} \times j!\) 。

然后考虑一下子树外的点,\(1 \sim i\) 的排列方式有 \(i!\) 种,剩下的 \(n - i - j + 1\) 个点需要逐个插上这棵树,但是不能插到 \(i\) 号点的子树内,这一部分贡献其实就是 \(\displaystyle i! \times \prod_{k = 1}^{n - i - j + 1} (i + k - 2)\) 。

那么最后的答案就是

\[
ans = \sum_{i = 1}^{n} \sum_{j = 1}^{n - i + 1} (j \times (n - j)) ({n - i \choose j - 1} j!)(i! \times \prod_{k = 1}^{n - i - j + 1}(i + k - 2))
\]

\(P\) 可能没有逆元有点烦,但考虑预处理 mulfac[i][j] 为 \(\prod_{k = 0}^{j} (i + k)\) ,就可以做到 \(\mathcal O(n^2)\) 。

其实有一种更好的方式,把前面那个式子化简就变成

\[
\sum\limits_{i=1}^n\sum\limits_{j=1}^{n-i+1}j! (n-j)! {n - i \choose j - 1} i(i-1)j
\]

就直接能做到 \(\mathcal O(n^2)\) 啦。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2526.in", "r", stdin);
    freopen ("2526.out", "w", stdout);
#endif
}

const int N = 2010;

int n, f[N], fac[N], Mod;

int Comb[N][N], mulfac[N][N];

int main () {

    File();

    n = read(); Mod = read();

    fac[0] = 1;
    For (i, 1, n) fac[i] = 1ll * fac[i - 1] * i % Mod;

    For (i, 0, n) {
        Comb[i][0] = 1;
        For (j, 1, i)
            Comb[i][j] = (Comb[i - 1][j] + Comb[i - 1][j - 1]) % Mod;
    }
    For (i, 1, n) {
        mulfac[i][0] = 1;
        For (j, 1, n)
            mulfac[i][j] = 1ll * mulfac[i][j - 1] * (i + j - 1) % Mod;
    }

    int ans = 0;
    For (i, 2, n) For (j, 1, n - i + 1) {
        int coef = mulfac[i - 1][n - (i + j) + 1], tmp = 1;
        ans = (ans + 1ll * fac[j] * Comb[n - i][j - 1] % Mod * (j * (n - j)) % Mod * coef % Mod * fac[i]) % Mod;
    }
    printf ("%d\n", ans);

    return 0;

}

「HAOI2018」染色

题意

为了报答小 C 的苹果, 小 G 打算送给热爱美术的小 C 一块画布,
这块画布可以抽象为一个长度为 \(N\) 的序列, 每个位置都可以被染成 \(M\) 种颜色中的某一种。

然而小 C 只关心序列的 \(N\) 个位置中出现次数恰好为 \(S\) 的颜色种数,如果恰好出现了 \(S\) 次的颜色有 \(K\) 种, 则小C会产生 \(W_k\) 的愉悦度。

小 C 希望知道对于所有可能的染色方案, 他能获得的愉悦度的和对 \(1004535809\) 取模的结果是多少。

\(N \le 10^7, M \le 10^5, S \le 150, 0 \le W_i < 1004535809\)

题解

看到 恰好 不难想到算 至少

我们令 \(f_i\) 为至少有 \(i\) 种颜色满足恰好出现 \(S\) 次的方案数。

颜色选法有 \(\displaystyle {m \choose i}\) 种,位置选法有 \(\displaystyle {n \choose iS}\) 种,选了的位置内部的排列就是一个可重集元素排列数有 \(\displaystyle \frac{(iS)!}{(S!)^i}\) 种,然后其他位置我们随意排除了这 \(i\) 种颜色的数有 \((m - i)^{n - iS}\) (特别的,我们可以令 \(0^0 = 1\) 虽然好像在数学上没有意义。。)。

那么我们现在算的 \(f_i\) 就是

\[
f_i = {m \choose i} {n \choose iS} \frac{(iS)!}{(S!)^i} (m - i)^{n - iS}
\]

但是这样算了之后,不难发现 \(f_i\) 算的答案会有点小问题,就是对于一个排列我们会重复计算很多次。

我们可以考虑 容斥 ,也就是考虑 \(f_j(i < j)\) 在 \(f_i\) 中算进去了几次。(假设当前的 \(f_j\) 取值刚好不重不漏)

不难发现对于每 \(i\) 个恰好出现 \(S\) 次的都会被算进去一次,那么总共会被算 \(\displaystyle {j \choose i}\) 次。

\(\displaystyle i = \min(m, \lfloor \frac{n}{S} \rfloor)\) 算的 \(f_i\) 一开始的取值一定正确,因为剩下的位置不够再出现一个恰好 \(S\) 次的颜色。

那么我们就有一个 \(\mathcal O(n^2)\) 的容斥啦。

\[
g_i = f_i - \sum_{j > i} g_j {j \choose i}
\]

此时得到的 \(g_i\) 就是恰好有 \(i\) 中颜色出现 \(S\) 次的方案数!

显然是可以用分治 \(FFT\) 优化的,但这么经典的卷积形式显然没必要。

考虑移项

\[
f_i = \sum_{j \ge i} {j \choose i} g_i
\]

不难发现这是个经典的二项式反演形式(广义容斥定理)。

\[
g_i = \sum_{j \ge i} (-1)^{j - i} {j \choose i} f_i
\]

这样就直接做一遍卷积就好啦,复杂度是 \(\mathcal O(m \log m)\) 的。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define div Div

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2527.in", "r", stdin);
    freopen ("2527.out", "w", stdout);
#endif
}

const int N = 1e7 + 1e3, Mod = 1004535809;

inline int fpm(int x, int power) {
    int res = 1;
    for (; power; power >>= 1, x = 1ll * x * x % Mod)
        if (power & 1) res = 1ll * res * x % Mod;
    return res;
}

inline void add(int &x, int y) {
    if ((x += y) >= Mod) x -= Mod;
}

inline void sub(int &x, int y) {
    if ((x -= y) < 0) x += Mod;
}

inline int mul(int x, int y) {
    return 1ll * x * y % Mod;
}

inline int div(int x, int y) {
    return 1ll * x * fpm(y, Mod - 2) % Mod;
}

namespace Poly {

    const int Maxn = 1 << 20, g = 3;

    int powg[Maxn], invpowg[Maxn];

    void NTT_Init() {
        for (int i = 2; i < Maxn; i <<= 1)
            invpowg[i] = fpm(powg[i] = fpm(g, (Mod - 1) / i), Mod - 2);
    }

    int len, rev[Maxn];

    void NTT(int *P, int opt) {
        Rep (i, len) if (i < rev[i]) swap(P[i], P[rev[i]]);
        for (int i = 2, p = 1; i <= len; p = i, i <<= 1) {
            int Wi = opt == 1 ? powg[i] : invpowg[i];
            for (int j = 0; j < len; j += i)
                for (int k = 0, x = 1; k < p; ++ k) {
                    int u = P[j + k], v = mul(x, P[j + k + p]);
                    P[j + k] = (u + v) % Mod;
                    P[j + k + p] = (u - v + Mod) % Mod;
                    x = mul(x, Wi);
                }
        }
        if (!~opt) {
            int inv = fpm(len, Mod - 2);
            Rep (i, len) P[i] = mul(P[i], inv);
        }
    }

    int A[Maxn], B[Maxn], C[Maxn];
    void Mult(int *a, int *b, int *c, int na, int nb) {
        int nc = na + nb, bit = 0;
        For (i, 0, na) A[i] = a[i];
        For (i, 0, nb) B[i] = b[i];

        for (len = 1; len <= nc; len <<= 1) ++ bit;
        Rep (i, len) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
        NTT(A, 1); NTT(B, 1);
        Rep (i, len) C[i] = mul(A[i], B[i]);
        NTT(C, -1);
        For (i, 0, nc) c[i] = C[i];
    }

}

int fac[N], ifac[N];

void Math_Init(int maxn) {
    fac[0] = ifac[0] = 1;
    For (i, 1, maxn) fac[i] = mul(fac[i - 1], i);
    ifac[maxn] = fpm(fac[maxn], Mod - 2);
    Fordown (i, maxn - 1, 1) ifac[i] = mul(ifac[i + 1], i + 1);
}

inline int comb(int n, int m) {
    if (n < 0 || m < 0 || n < m) return 0;
    return mul(mul(fac[n], ifac[n - m]), ifac[m]);
}

int n, m, s, w[N], f[N], g[N];

int main () {

    File();

    n = read(); m = read(); s = read();
    For (i, 0, m) w[i] = read();
    Math_Init(max(n, m));
    Poly :: NTT_Init();

    int lim = min(m, n / s);
    For (i, 0, lim) {
        int coef = mul(comb(n, i * s), comb(m, i));
        coef = mul(coef, div(fac[i * s], fpm(fac[s], i)));
        f[i] = mul(coef, fpm(m - i, n - i * s));
    }

    reverse(f, f + lim + 1);
    For (i, 0, lim)
        f[i] = mul(i & 1 ? Mod - 1 : 1, mul(f[i], fac[lim - i]));
    Poly :: Mult(f, ifac, g, lim, lim);
    For (i, 0, lim)
        g[i] = mul(mul(i & 1 ? Mod - 1 : 1, ifac[lim - i]), g[i]);
    reverse(g, g + lim + 1);

    int ans = 0;
    For (i, 0, lim)
        add(ans, mul(g[i], w[i]));
    printf ("%d\n", (ans + Mod) % Mod);

    return 0;

}

原文地址:https://www.cnblogs.com/zjp-shadow/p/10387789.html

时间: 2024-08-30 17:00:44

HAOI2018 简要题解的相关文章

AGC025简要题解

AGC025简要题解 B RGB Coloring 一道简单题,枚举即可. C Interval Game 考虑可以进行的操作只有两种,即左拉和右拉,连续进行两次相同的操作是没有用的. 左拉时肯定会选择右端点尽量小的,右拉选择左端点尽量大的,所以排序之后贪心即可. D Choosing Points 首先证明对于所有\(d\),假设让两个不能同时选的点之间连一条边,那么结果是一张二分图. \(d\)是奇数可以黑白染色,\(d\)是偶数的时候,显然连边的两点在同一个颜色内.那么我们可以只考虑这个颜

月考简要题解

模拟赛简要题解 一下题目均可在loj上找到 10178. 「一本通 5.5 例 4」旅行问题 简单题,将n扩大到2 * n,单调队列即可,注意正反向. #include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=2000

JXOI2018简要题解

JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法描述大致如下: 假设我们要对一个大小为 \(n\) 的数列 \(a\) 排序. 等概率随机生成一个大小为 \(n\) 的排列 \(p\) . 构造一个大小为 \(n\) 的数列 \(b\) 满足 \(b_i=a_{p_i}\) ,检查 \(b\) 是否有序,如果 \(b\) 已经有序了就结束算法,并

BJOI2018简要题解

BJOI2018简要题解 D1T1 二进制 题意 pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是 \(3\) 的倍数.他想研究对于二进制,是否也有类似的性质. 于是他生成了一个长为 \(n\) 的二进制串,希望你对于这个二进制串的一个子区间,能求出其有多少位置不同的连续子串,满足在重新排列后(可包含前导 \(0\))是一个 \(3\) 的倍数.两个位置不同的子区间指开始位置不同或结束位置不同. 由于他想尝试尽量多的情况,他有时会修改串中的一个位置,并且会进行多次询

杂题记录及简要题解(三)

以下是大概 5 月初开始做的一些题.以前的简要题解都是骗人的.这次真的是简要题解了(大雾 相对之前改良了一下题目名称的格式. 2017 计蒜之道 初赛 - 腾讯狼人杀 二分答案 \(x\) 后原问题变为检验是否存在选取方案 \((V, E)(|V| = k)\) 使得 \(\sum_\limits{e \in E} w_e - xk \cdot (2n- k)\).式子可以写成 \(\sum_\limits{e \in E} w_e + \frac{k(k - 1)}{2} \cdot 2x -

【简要题解】Hihocoder 重复旋律1-8简要题解

[简要题解]Hihocoder 重复旋律1-8简要题解 编号 名称标签 难度 1403 后缀数组一·重复旋律 Lv.4 1407 后缀数组二·重复旋律2 Lv.4 1415 后缀数组三·重复旋律3 Lv.4 1419 后缀数组四·重复旋律4 Lv.4 1445 后缀自动机二·重复旋律5 Lv.4 1449 后缀自动机三·重复旋律6 Lv.4 1457 后缀自动机四·重复旋律7 Lv.1 1465 后缀自动机五·重复旋律8 Lv.1 1466 后缀自动机六·重复旋律9 Lv.1 后缀数组 思路简单

《信奥一本通》提高版—简要题解

<信奥一本通>提高版-简要题解 贪心 活动安排: 按右端点排序,因为越早结束越好. 然后从1扫到n,每次比较当前位置线段的左端点是否大于上一个选的线段的右端点.如果大于,那么ans++,将上一个选的线段的右端点更新为当前线段的右端点:如果小于,那什么都不用做.因为选上一条一定比选当前这一条更优(结束时间更早). 种树 按右端点排序,对于每个区间的需求,从右端往左端扫,只要没种到树的就种,ans++. 因为要使前面的需求尽量与后面的需求重叠,从而使树的数量最少 喷水装置 观察+画图发现对于一个圆

【题解】CF616(Div 2)简要题解

[题解]CF616(Div 2)简要题解 A 分类讨论 若数码和是奇数 若最后一个数是奇数:找到从前往后第一个奇数数位删掉 若最后一个数是偶数:不断删除最后一个数直到这个剩下的数是奇数,由于之前删掉的数都是偶数所以对数码和\(\mod 2\)不会有影响.再做一遍第一个算法即可. 若数码和是偶数 若最后一个数是奇数:符合条件 若最后一个数是偶数:不断删除最后一个数直到奇数.由于之前删掉的数都是偶数所以对数码和\(\mod 2\)不会有影响,直接输出即可. 最后要判断一下前导零. B 可以发现若有合

Codeforces Round #483 (Div. 1) 简要题解

来自FallDream的博客,未经允许,请勿转载,谢谢. 为了证明一下我又来更新了,写一篇简要的题解吧. 这场比赛好像有点神奇,E题莫名是道原题,导致有很多选手直接过掉了(Claris 表演24s过题).然而D题比E题要难一些,分还少. A. Finite or not? 先把\(\frac{p}{q}\)约成最简分数,然后就是要判断是否\(q\)的所有质因数都是\(b\)的质因数. 每次取\(g=gcd(b,q)\),并尽可能的让\(q\)除\(g\),最后判断\(q\)是否是1即可. 还有一