CSP-S模拟20

T1.归隐

签到题吧算是。看到数据范围直接来推结论。先把对数去掉,就变成了指数项的加法。容易发现\(a_i=3a_{i-1}+1\),除了两侧的数,其它的贡献都翻了一倍放在中间。然后用等比数列推一下式子就好了。\(a_i=\frac{3^{i-1}+1}{2}\)\(\sum\limits_{i=1}^{n}a_i=\frac{3^n+2n-1}{4}\)

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define int long long 
const int mod = 998244353;
inline int qpow(int a, int b, int c) { int res = 1; while (b) { if (b & 1) res = res * a % c; a = a * a % c; b >>= 1; } return res; }
sandom main()
{
    fre(gy, gy);
    int n; std::cin >> n;
    std::cout << (qpow(3, n, mod) + 2 * n - 1) % mod * qpow(4, mod - 2, mod) % mod;
    return 0;
}

T2.按位或

只能想到基础\(dp\),然而是一道容斥基础题?因为\(n\)个数完全等价,所以\(dp\)显然不如组合效率更高。先定义一些东西:\(2\)的偶数次方为\(1\)类数,即\(\%3=1\)\(2\)的奇数次方为\(2\)类数,即\(\%3=2\),容易发现我们并不关心这些数在\(t\)中的具体位置,而只关注数量,把总数分别记作\(tot1、tot2\)。定义\(f[i][j]\)为对于一个数\(x\)\(3\)的倍数,且二进制中至多有\(i\)个一类数,\(j\)个二类数的方案数,转移用组合数是非常显然的。现在考虑容斥,其实直接套用基础一维容斥的式子即可,只不过把它扩展为了两个维度,效果是一样的。有一个细节:\(+1、-1\)的系数问题,我们的目标是最后恰好分别有\(tot1、tot2\)\(1\)类数、\(2\)类数,也就是或和为\(t\),所以只能确定\(f[tot1][tot2]\)这个状态的系数必然为\(1\),那么其它层的状态根据此类推。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
#define dwn(i, a, b) for (re (i) = (a); (i) >= (b); --(i))
using namespace std;
const int Z = 33; const int mod = 998244353;
inline int qpow(int a, int b, int c) { int res = 1; while (b) { if (b & 1) res = res * a % c; a = a * a % c; b >>= 1; } return res; }

int n, m, tot1, tot2, ans;
int f[Z][Z];
void divide(int x)//二进制拆分
{
    for (re i = 0; i <= 60; ++i) 
        if ((x >> i) & 1)
        {
            if (i & 1) tot2++;
            else tot1++;
        }
}
int fac[Z], inv[Z];
inline void init(int n)
{
    fac[0] = 1;
    rep(i, 1, n) fac[i] = fac[i - 1] * i % mod;
    inv[n] = qpow(fac[n], mod - 2, mod);
    dwn(i, n - 1, 0) inv[i] = inv[i + 1] * (i + 1) % mod;
}
inline int C(int n, int m) { return fac[n] * inv[m] % mod * inv[n - m] % mod; }

sandom main()
{
    fre(or, or);
    cin >> n >> m; divide(m);
    init(max(tot1, tot2));
    rep(i, 0, tot1) rep(j, 0, tot2) rep(k, 0, i) rep(t, 0, j)//至多会有这些位是1且是3的倍数的方案数
        if ((k + 2 * t) % 3 == 0) (f[i][j] += C(i, k) * C(j, t)) %= mod;
    dwn(i, tot1, 0) dwn(j, tot2, 0)//容斥
    {
        int tmp = (tot1 - i + tot2 - j & 1) ? -1 : 1;
        ans += tmp * C(tot1, i) * C(tot2, j) % mod * qpow(f[i][j], n, mod) % mod;
    }
    cout << (ans % mod + mod) % mod;
    return 0;
}

T3.最短路径

根据前天\(T4\)的套路,首先有一个结论:如果已经确定了是哪\(k\)个点,那么最短路径长度就是这棵虚树的边长减去直径长(除了直径其他都需要回溯),考虑分开计算答案。第一个很显然,把每条边的贡献分开算,枚举每条边两侧有几个关键点(至少各有一个);第二个可以对于这\(m\)个点,枚举可能形成的直径的两端,共\(m^2\)种,那么其它的关键点到任意点的长度是不能超过直径的,这个可以暴力\(O(m^3)\),也可以通过排序单调性做到\(O(m^2logm)\)。枚举直径端点的时候优先枚举当前整棵树的直径的端点,能避免去重问题。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
#define dwn(i, a, b) for (re (i) = (a); (i) >= (b); --(i))
#define ede(i, rt) for (re i = head[rt]; i; i = e[i].ne)
using namespace std; 
const int Z = 2010; const int W = 310; const int mod = 998244353; 
inline char getc() { static char buf[1 << 18], *p1, *p2; if (p1 == p2) { p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f = c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }
inline int qpow(int a, int b, int c) { int res = 1; while (b) { if (b & 1) res = res * a % c; a = a * a % c; b >>= 1; } return res; }

int n, m, k, ans;
struct edge { int v, ne; } e[Z << 1];
int head[Z], cnt, id[W], di[W];
inline void add(int x, int y) { e[++cnt] = edge{y, head[x]}; head[x] = cnt; } 
int fac[W], inv[W];
inline void init(int n)
{
    fac[0] = 1;
    rep(i, 1, n) fac[i] = fac[i - 1] * i % mod;
    inv[n] = qpow(fac[n], mod - 2, mod);
    dwn(i, n - 1, 0) inv[i] = inv[i + 1] * (i + 1) % mod;
}
inline int C(int n, int m) { return (n < m) ? 0 : fac[n] * inv[m] % mod * inv[n - m] % mod; }
inline int _C(int n, int m) { return fac[m] * fac[n - m] % mod * inv[n] % mod; }

bool vs[Z];
int dep[Z], siz[Z], root;
void dfs(int rt, int fa)
{
    ede(i, rt)
    {
        int son = e[i].v;
        if (son == fa) continue;
        dfs(son, rt); siz[rt] += siz[son];
        rep(j, 1, min(siz[son], k - 1)) (ans += 2 * C(siz[son], j) * C(m - siz[son], k - j)) %= mod;//不考虑直径边的贡献
    }
}
void dfs2(int rt, int fa)
{
    dep[rt] = dep[fa] + 1;
    if (vs[rt] && dep[rt] > dep[root]) root = rt;
    ede(i, rt) if (e[i].v != fa) dfs2(e[i].v, rt);
}

sandom main()
{
    fre(tree, tree);
    n = read(), m = read(), k = read();
    init(m);
    rep(i, 1, m) id[i] = read(), siz[id[i]] = vs[id[i]] = 1;
    rep(i, 2, n)
    {
        int u = read(), v = read();
        add(u, v), add(v, u);
    }
    dfs(1, 0); dfs2(1, 0);
    rep(i, 1, m)
    {
        vs[root] = 0; dfs2(root, 0);//找到直径的一端
        int tot = 0;
        rep(j, 1, m) if (vs[id[j]]) di[++tot] = dep[id[j]] - 1;
        sort(di + 1, di + 1 + tot);
        rep(j, 1, tot) (ans -= di[j] * C(j - 1, k - 2)) %= mod;
    }
    ans = ans * _C(m, k) % mod;
    cout << (ans + mod) % mod;
    return 0;
}

T4.最短路

可以高精度水一些分,但是我最短路打挂了……赛后正解但是最短路仍然是错的……首先发现边权都是\(2\)的幂次,观察最短路模型,发现每经过一条边最多使二进制位加\(1\),但是可能会进位,记\(x\)后面第一位为\(0\)的位是\(pos\),那么实际上就是把\([x,pos-1]\)变为\(0\)\(pos\)这一位变为\(1\),显然可以线段树二分来解决。但是考虑到代表节点的转移,所以需要主席树来维护。
这时就有疑问了:主席树如何支持区间覆盖?发现本题的操作相当于区间清空,那么完全可以让这段区间没有孩子节点,因为贡献都是\(0\),根本不存在下传标记的必要性。
在路径长度进行比较时可以用\(hash\)判断是否相等,如果高位\(hash\)相等,就递归低位,否则递归高位。
哈希直接使用二进制,并对\(1e9+7\)取模,这样方便最后直接输出答案。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
#define ede(i, rt) for (re i = head[rt]; i; i = e[i].ne)
using namespace std;
const int Z = 1e5 + 10; const int W = 4e5 + 10; const int mod = 1e9 + 7; 
inline char getc() { static char buf[1 << 18], *p1, *p2; if (p1 == p2) { p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f = c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }

int n, m, k;
struct edge { int v, w, ne; } e[W];
int head[Z], cnt;
inline void add(int x, int y, int z) { e[++cnt] = edge{y, z, head[x]}; head[x] = cnt; } 

struct tree
{
    int l, r, has;
    #define lk tr[rt].l
    #define rk tr[rt].r
    #define mid (L + R >> 1)
}; tree tr[Z * 500];
int root[Z], tot, bs[W];
inline void pushup(int rt, int L, int R) { tr[rt].has = (tr[lk].has + bs[mid + 1 - L] * tr[rk].has) % mod; }
bool cmp(int x, int y, int L, int R)
{
    if (L == R) return tr[x].has > tr[y].has;
    if (tr[tr[x].r].has == tr[tr[y].r].has) return cmp(tr[x].l, tr[y].l, L, mid);
    else return cmp(tr[x].r, tr[y].r, mid + 1, R);
}
void build(int &rt, int L, int R)
{
    rt = ++tot;
    if (L == R) { tr[rt].has = 1; return; }
    build(lk, L, mid), build(rk, mid + 1, R);
    pushup(rt, L, R);
}
int update(int pre, int L, int R, int l, int r, int op)//单点修改1和区间覆盖0
{
    if (l > r) return pre;
    int rt = ++tot; tr[rt] = tr[pre];
    if (l <= L && r >= R)
    {
        tr[rt].l = tr[rt].r = 0;
        tr[rt].has = op; return rt;
    }
    if (l <= mid) lk = update(lk, L, mid, l, r, op);
    if (r > mid) rk = update(rk, mid + 1, R, l, r, op);
    pushup(rt, L, R); return rt;
}
int query(int rt, int L, int R, int l)
{
    if (tr[rt].has == bs[R - L + 1] - 1) return 0;//区间全为1
    if (L == R) return L;
    int op = 0;
    if (l <= mid) op = query(lk, L, mid, l);
    return op ? op : query(rk, mid + 1, R, l);
}
bool vs[Z];
struct nod
{
    int id, di;
    friend bool operator <(nod A, nod B) { return cmp(A.di, B.di, 0, k); }
};
priority_queue <nod> q;
void dijk(int s)
{
    build(root[0], 0, k);
    rep(i, 1, n) root[i] = root[0];
    root[s] = ++tot; q.push(nod{s, root[s]});
    while (!q.empty())
    {
        int u = q.top().id; q.pop();
        if (vs[u]) continue; vs[u] = 1;
        ede(i, u)
        {
            int v = e[i].v, pos = query(root[u], 0, k, e[i].w);
            int f = update(root[u], 0, k, e[i].w, pos - 1, 0);
            f = update(f, 0, k, pos, pos, 1);
            if (cmp(root[v], f, 0, k)) { root[v] = f; q.push(nod{v, root[v]}); }
        }
    }
}

sandom main()
{
    fre(hellagur, hellagur);
    n = read(), m = read();
    rep(i, 1, m)
    {
        int u = read(), v = read(), w = read();
        add(u, v, w), add(v, u, w); k = max(k, w);
    }
    bs[0] = 1; k <<= 1;
    rep(i, 1, k) bs[i] = bs[i - 1] * 2 % mod;//二进制
    int s = read(), t = read(); dijk(s);
    if (!vs[t]) cout << -1;
    else cout << tr[root[t]].has;
    return 0;
}
posted @ 2022-10-22 19:50  sandom  阅读(33)  评论(2编辑  收藏  举报