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;
}