官方数据和标程:2023杭电多校资料包
目录
难度评价
7题慢一百名,5题快两百名,4题中四百名。
C、F、J、L签到,D、G、K中等,B、E、H、I困难,A大难。
A. Number Table
数学
、DP
过难
B. Simple Tree Problem
数据结构
要求维护子树內点权计数信息,支持查询指定范围內最大值,还要同步维护整棵树抠掉当前子树后的信息,常规想法是线段树合并。
维护子树信息的方法是显然的,但维护整棵树抠掉当前子树后的信息却比较麻烦,这是因为即使预处理出一棵完整的权值线段树,在DFS过程中,抠除的结点信息也难以恢复。
注意到这正是树上启发式合并的优势之一,即访问重儿子前,所有访问的轻儿子的影响都不会保留,于是我们可以考虑一种
O
(
N
l
o
g
2
N
)
O(Nlog^2N)
O(Nlog2N) 的方法——直接做树上启发式合并,用两棵动态开点权值线段树维护信息,一棵加点(初始没有任何信息),一棵删点(初始有完整信息)。
数据范围很大, N ≤ 1 0 6 N \leq 10^6 N≤106,并且不离散化的话值域达到 1 0 9 10^9 109,内存和时间上都过于极限,为了卡过同时使用了离散化、前向星、快读。
#include <bits/stdc++.h>
using namespace std;
namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
inline int read()
{
int s = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
ch = getchar();
while (ch >= '0' && ch <= '9')
{
s = s * 10 + ch - '0';
ch = getchar();
}
return s;
}
inline void write(int x)
{
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
};
using namespace fastIO;
#define ls tr[rt].lc
#define rs tr[rt].rc
constexpr int MAXN = 1e6 + 5, N = 2e6;
struct SegmentTree
{
struct Node
{
int lc, rc, mx;
} tr[MAXN * 60];
int tot, root;
void init() { tr[1] = Node(), root = tot = 1; }
void pushUp(int rt) { tr[rt].mx = max(tr[ls].mx, tr[rs].mx); }
void add(int val, int C, int l, int r, int &rt)
{
if (!rt)
rt = ++tot, tr[rt] = Node();
if (l == r)
{
tr[rt].mx += C;
return;
}
int mid = (l + r) >> 1;
if (val <= mid)
add(val, C, l, mid, ls);
else
add(val, C, mid + 1, r, rs);
pushUp(rt);
}
int query(int L, int R, int y, int l, int r, int rt)
{
int mid = (l + r) >> 1;
if (L <= l && r <= R)
{
if (tr[rt].mx < y)
return 0;
else if (l == r)
return l;
else if (tr[rs].mx >= y)
return query(L, R, y, mid + 1, r, rs);
else
return query(L, R, y, l, mid, ls);
}
if (L > mid)
return query(L, R, y, mid + 1, r, rs);
else if (R <= mid)
return query(L, R, y, l, mid, ls);
else
return max(query(L, R, y, l, mid, ls), query(L, R, y, mid + 1, r, rs));
}
} t1, t2;
int L[MAXN], R[MAXN], id[MAXN], sz[MAXN], son[MAXN], dfn;
int a[MAXN], ans[MAXN], lsh[MAXN * 2];
int head[MAXN], tot = 1;
unordered_map<int, int> mp;
struct Edge
{
int to, next, w;
} e[MAXN * 2];
void add(int u, int v, int w)
{
e[++tot].to = v, e[tot].w = w, e[tot].next = head[u];
head[u] = tot;
}
void dfs1(int v, int fa)
{
L[v] = ++dfn;
id[dfn] = v;
sz[v] = 1;
son[v] = 0;
for (int i = head[v]; i; i = e[i].next)
{
int u = e[i].to;
if (u == fa)
continue;
dfs1(u, v);
sz[v] += sz[u];
if (sz[u] > sz[son[v]])
son[v] = u;
}
R[v] = dfn;
}
void dfs2(int v, int fa, int k, int eid, bool keep)
{
int soneid = 0, sonw = 0;
for (int i = head[v]; i; i = e[i].next)
{
int u = e[i].to, w = e[i].w;
if (u == son[v])
soneid = i, sonw = w;
else if (u != fa)
dfs2(u, v, w, i, 0);
}
if (son[v])
dfs2(son[v], v, sonw, soneid, 1);
t1.add(a[v], 1, 1, N, t1.root);
t2.add(a[v], -1, 1, N, t2.root);
for (int j = head[v]; j; j = e[j].next)
{
int u = e[j].to;
if (u == son[v] || u == fa)
continue;
for (int i = L[u]; i <= R[u]; i++)
{
int x = a[id[i]];
t1.add(x, 1, 1, N, t1.root);
t2.add(x, -1, 1, N, t2.root);
}
}
ans[eid / 2] = max(t1.query(1, N, mp[k], 1, N, 1), t2.query(1, N, mp[k], 1, N, 1));
if (!keep)
{
for (int i = L[v]; i <= R[v]; i++)
t2.add(a[id[i]], 1, 1, N, t2.root);
t1.init();
}
}
int main()
{
int size(512 << 20);
__asm__("movq %0, %%rsp\n" ::"r"((char *)malloc(size) + size));
int t = read();
while (t--)
{
int n = read(), num = 0;
for (int i = 1; i <= n; i++)
a[i] = read(), lsh[++num] = a[i];
for (int i = 1; i < n; i++)
{
int v = read(), u = read(), w = read();
add(v, u, w), add(u, v, w);
lsh[++num] = w;
}
sort(lsh + 1, lsh + num + 1);
num = unique(lsh + 1, lsh + num + 1) - lsh - 1;
for (int i = 1; i <= num; i++)
mp[lsh[i]] = i;
for (int i = 1; i <= n; i++)
a[i] = mp[a[i]];
t1.init(), t2.init();
for (int i = 1; i <= n; i++)
t2.add(a[i], 1, 1, N, t2.root);
dfs1(1, 0);
dfs2(1, 0, 1e9, 0, 1);
for (int i = 1; i < n; i++)
write(lsh[ans[i]]), putchar('\n');
fill(head + 1, head + n + 1, 0);
dfn = 0, tot = 1;
mp.clear();
}
flush();
exit(0);
}
C. Simple Set Problem
数据结构
、贪心
十分类似上一场的题目Operation Hope。目标仍然是尽可能减小极差,如果只有增大操作,我们就能便捷地维护,这实际上容易做到。
将所有集合从小到大排序,并分别将第一个数加入到堆中,此后每次取出堆中第一个最小的数,将其增大,即换入对应集合的下一个数。如果已经达到该集合末尾,则极差无法减小,操作结束。
注:略卡常,加快读并使用数组可轻松通过。
#include <bits/stdc++.h>
using namespace std;
namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
inline int read()
{
int s = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
s = s * 10 + ch - '0';
ch = getchar();
}
return s * f;
}
inline void write(long long x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
};
using namespace fastIO;
using tpl = tuple<int, int, int>;
vector<int> vec[1000005];
int main()
{
int t = read();
while (t--)
{
int n = read();
set<tpl> s;
for (int i = 0; i < n; i++)
{
int k, x;
k = read();
while (k--)
x = read(), vec[i].push_back(x);
sort(vec[i].begin(), vec[i].end());
s.emplace(vec[i].front(), i, 0);
}
int ans = get<0>(*s.rbegin()) - get<0>(*s.begin());
while (1)
{
auto [mn, mnid, mnpos] = *s.begin();
if (mnpos + 1 == vec[mnid].size())
break;
s.erase(s.begin());
++mnpos;
s.emplace(vec[mnid][mnpos], mnid, mnpos);
ans = min(ans, get<0>(*s.rbegin()) - get<0>(*s.begin()));
}
write(ans), putchar('\n');
for (int i = 0; i < n; i++)
vec[i].clear();
}
flush();
return 0;
}
D. Data Generation
数学
队友写的。
每个位置不等于本身的概率相同,只需考虑一个数,最后乘上
n
n
n。如果本身就是
i
i
i,变化一次后不是
i
i
i 的概率为
2
∗
(
n
−
1
)
/
n
2
2*(n-1)/n^2
2∗(n−1)/n2;如果本身不是
i
i
i,变化一次后变为
i
i
i 的概率是
2
/
n
2
2/n^2
2/n2。另外两种概率用
1
1
1 减去即可。使用矩阵快速幂加速递推。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
constexpr i64 P = 998244353;
i64 qpow(i64 x,i64 y)
{
i64 ret = 1LL;
while(y)
{
if(y&1LL)
{
ret = ret * x % P;
}
y /= 2;
x = x * x % P;
}
return ret;
}
class mat
{
public:
static constexpr int maxsz = 2;
i64 a[maxsz][maxsz];
int sz = 0;
mat(){};
mat(int sz)
{
for (int i = 0; i < sz; i++)
{
for (int j = 0; j < sz; j++)
{
a[i][j] = 0;
}
}
this->sz = sz;
}
void init()
{
for (int i = 0; i < this->sz; i++)
{
for (int j = 0; j < this->sz; j++)
{
a[i][j] = (i == j);
}
}
}
mat operator-(const mat &rhs) const
{
mat res(this->sz);
for (int i = 0; i < sz; i++)
{
for (int j = 0; j < sz; j++)
{
res.a[i][j] = (a[i][j] - rhs.a[i][j]) % P;
}
}
return res;
}
mat operator+(const mat &rhs) const
{
mat res(this->sz);
for (int i = 0; i < sz; i++)
{
for (int j = 0; j < sz; j++)
{
res.a[i][j] = (a[i][j] + rhs.a[i][j]) % P;
}
}
return res;
}
mat operator*(const mat &rhs) const
{
mat res(this->sz);
i64 tmp;
for (int i = 0; i < sz; i++)
{
for (int k = 0; k < sz; k++)
{
tmp = a[i][k];
for (int j = 0; j < sz; j++)
{
res.a[i][j] += rhs.a[k][j] * tmp % P;
res.a[i][j] %= P;
}
}
}
return res;
}
mat operator^(i64 y) const
{
mat res(this->sz), bas(this->sz);
res.init();
for (int i = 0; i < sz; i++)
{
for (int j = 0; j < sz; j++)
{
bas.a[i][j] = a[i][j] % P;
}
}
while (y)
{
if (y & 1LL)
{
res = res * bas;
}
bas = bas * bas;
y /= 2;
}
return res;
}
};
mat bas;
void solve()
{
i64 n, m;
cin >> n >> m;
bas = mat(2);
n %= P;
bas.a[0][1] = 2 * (n - 1) % P * qpow(n %P* n % P, P - 2) % P;
bas.a[0][0] = (1 - bas.a[0][1] + P)%P;
bas.a[1][0] = 2 * qpow(n %P* n % P, P - 2) % P;
bas.a[1][1] = (1 - bas.a[1][0] + P) % P;
bas = bas ^ m;
i64 res = bas.a[0][1] * n % P;
cout << (res%P+P)%P << "\n";
}
int main()
{
cin.tie(0)->sync_with_stdio(0);
i64 T = 1;
cin >> T;
while(T--)
{
solve();
}
return 0;
}
E. Teyvat
图论
、DP
在简单无向图上求最短路径能覆盖给定点集的点对数量,相似类别的问题都是广义圆方树的经典应用。
对于点集大小 k k k,分类讨论:
- k = 1 k=1 k=1,DP预处理出经过每个点的圆点点对数即可 O ( 1 ) O(1) O(1) 回答
- k > 1 k>1 k>1,所有点形成一条自底向上的链,记 s z [ v ] sz[v] sz[v] 为 v v v 子树中圆点个数, v , u v,u v,u 分别是链顶、链尾, w w w 是 v v v 在圆方树上对应的下一个点,答案为 s z [ u ] ∗ ( n − s z [ w ] ) sz[u]*(n-sz[w]) sz[u]∗(n−sz[w])
- k > 1 k>1 k>1,所有点形成一条倒V型链,记 v , u v,u v,u 分别为链两端,答案为 s z [ v ] ∗ s z [ u ] sz[v]*sz[u] sz[v]∗sz[u]
- k > 1 k>1 k>1,不是以上情况,即不能被一条路径覆盖,答案为 0 0 0
DP处理参考第二场Counter Strike。判断是否是链状,一种简单实现是使用虚树,如果只有LCA出度为2,或者没有点出度为2,就是链状。
略卡常,还卡内存,需要快读。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 1e6 + 5;
int low[MAXN], num[MAXN], dfn, nn, n, m, q;
int dep[MAXN], father[MAXN][20], lg[MAXN], sz[MAXN];
int stk[MAXN], tp, cnt, leaf1, leaf2;
int64_t dp[MAXN];
vector<int> G[MAXN], T[MAXN], ver;
stack<int> st;
namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
inline int read()
{
int s = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
ch = getchar();
while (ch >= '0' && ch <= '9')
s = s * 10 + ch - '0', ch = getchar();
return s;
}
inline void write(long long x)
{
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
};
using namespace fastIO;
void tarjan(int v)
{
low[v] = num[v] = ++dfn;
st.push(v);
for (auto u : G[v])
{
if (!num[u])
{
tarjan(u);
low[v] = min(low[u], low[v]);
if (low[u] >= num[v])
{
++nn;
int z;
do
{
z = st.top(), st.pop();
T[z].push_back(nn), T[nn].push_back(z);
} while (z != u);
T[v].push_back(nn), T[nn].push_back(v);
}
}
else
low[v] = min(low[v], num[u]);
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
swap(x, y);
while (dep[x] > dep[y])
x = father[x][lg[dep[x] - dep[y]] - 1];
if (x == y)
return x;
for (int k = lg[dep[x]] - 1; k >= 0; k--)
if (father[x][k] != father[y][k])
x = father[x][k], y = father[y][k];
return father[x][0];
}
int jump(int v, int u)
{
int d = dep[v] - dep[u] - 1;
for (int i = 19; i >= 0; i--)
if (d & (1 << i))
v = father[v][i];
return v;
}
void dfs1(int v, int fa)
{
sz[v] = (v <= n);
dp[v] = 0;
num[v] = ++dfn;
father[v][0] = fa;
dep[v] = dep[fa] + 1;
for (int i = 1; i < 20; i++)
father[v][i] = father[father[v][i - 1]][i - 1];
for (auto u : T[v])
{
if (u == fa)
continue;
dfs1(u, v);
dp[v] += 1ll * sz[v] * sz[u];
sz[v] += sz[u];
}
dp[v] += 1ll * sz[v] * (n - sz[v]);
}
int build(vector<int> &p)
{
sort(p.begin(), p.end(), [](int v, int u)
{ return num[v] < num[u]; });
auto root = p[0];
for (int i = 1; i < p.size(); i++)
root = lca(root, p[i]);
stk[tp = 1] = root;
G[root].clear();
for (auto v : p)
{
if (v != root)
{
auto LCA = lca(v, stk[tp]);
if (LCA != stk[tp])
{
while (num[LCA] < num[stk[tp - 1]])
G[stk[tp - 1]].push_back(stk[tp]), --tp;
if (num[LCA] > num[stk[tp - 1]])
G[LCA].clear(), G[LCA].push_back(stk[tp]), stk[tp] = LCA;
else
G[LCA].push_back(stk[tp--]);
}
G[v].clear(), stk[++tp] = v;
}
}
for (int i = 1; i < tp; i++)
G[stk[i]].push_back(stk[i + 1]);
return root;
}
void dfs2(int v)
{
if (G[v].size() >= 2)
++cnt;
else if (G[v].size() == 0)
leaf1 == 0 ? leaf1 = v : leaf2 = v;
for (auto u : G[v])
dfs2(u);
}
int main()
{
int size(256 << 20);
__asm__("movq %0, %%rsp\n" ::"r"((char *)malloc(size) + size));
for (int i = 1; i < MAXN; i++)
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
ver.reserve(1000000);
int t = read();
while (t--)
{
n = read(), m = read(), q = read(), nn = n;
while (m--)
{
int v = read(), u = read();
G[v].push_back(u), G[u].push_back(v);
}
tarjan(1);
for (int i = 1; i <= n; i++)
G[i].clear();
dfn = 0;
dfs1(1, 0);
while (q--)
{
int k = read(), x;
for (int i = 0; i < k; i++)
x = read(), ver.push_back(x);
if (k == 1)
write(dp[ver[0]] + 1), putchar('\n'); // S=T也算
else
{
auto rt = build(ver);
cnt = leaf1 = leaf2 = 0;
dfs2(rt);
if (cnt == 1 && G[rt].size() == 2)
write(1ll * sz[leaf1] * sz[leaf2]), putchar('\n');
else if (cnt == 0)
write(1ll * sz[leaf1] * (n - sz[jump(leaf1, rt)])), putchar('\n');
else
putchar('0'), putchar('\n');
}
ver.clear();
}
dfn = 0;
fill(num + 1, num + nn + 1, 0);
for (int i = 1; i <= nn; i++)
G[i].clear(), T[i].clear();
}
flush();
exit(0);
}
F. PSO
数学
显然最大距离只在
n
=
2
n=2
n=2 时为
1
1
1,其余情况均为
2
2
2。
期望距离分类讨论,距离为
2
2
2 的点对有
C
n
−
1
2
C_{n-1}^{2}
Cn−12 对,距离为
1
1
1 的点对有
n
−
1
n-1
n−1 对,答案为
C
n
−
1
2
∗
2
+
(
n
−
1
)
∗
1
C
n
2
=
2
−
2
n
\cfrac{C_{n-1}^{2}*2+(n-1)*1}{C_n^2}=2-\cfrac{2}{n}
Cn2Cn−12∗2+(n−1)∗1=2−n2。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cout << fixed << setprecision(9);
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
if (n == 2)
cout << 1.0 << " " << 1.0 << "\n";
else
cout << 2.0 - 2.0 / n << " " << 2.0 << "\n";
}
return 0;
}
G. Guess
数学
、暴力
给队友了。主要问题在于打表找规律,以及注意Pollard-Rho判素数的时间效率。
#include <bits/stdc++.h>
using i64 = long long;
using ld = long double;
constexpr i64 N = 1e5;
constexpr i64 P = 998244353;
using namespace std;
i64 gcd(i64 a, i64 b)
{
return b == 0 ? a : gcd(b, a % b);
}
i64 qpow(i64 x, i64 y, i64 P)
{
i64 ret = 1LL;
while (y)
{
if (y & 1LL)
{
ret = (__int128)ret * x % P;
}
y /= 2;
x = (__int128)x * x % P;
}
return ret;
}
bool is_prime(i64 x)
{
if (x < 3)
{
return x == 2;
}
if (x % 2 == 0)
{
return 0;
}
i64 d = x - 1, r = 0;
while (d % 2 == 0)
{
d /= 2;
++r;
}
i64 A[] = {2, 325, 9375, 28178, 450775, 9780504, 1795265022};
for (auto &a : A)
{
i64 v = qpow(a, d, x);
if (v <= 1 || v == x - 1)
{
continue;
}
for (i64 i = 0; i < r; ++i)
{
v = (__int128)v * v % x;
if (v == x - 1 && i != r - 1)
{
v = 1;
break;
}
if (v == 1)
{
return 0;
}
}
if (v != 1)
{
return false;
}
}
return true;
}
template <class T>
T randint(T l, T r = 0)
{
static mt19937 eng(time(0));
if (l > r)
{
swap(l, r);
}
uniform_int_distribution<T> dis(l, r);
return dis(eng);
}
i64 Pollard_Rho(i64 N)
{
if (N == 4)
{
return 2;
}
if (is_prime(N))
{
return N;
}
while (1)
{
i64 c = randint(1LL, N - 1);
auto f = [=](i64 x)
{
return ((__int128)x * x + c) % N;
};
i64 t = 0, r = 0, p = 1, q;
do
{
for (i64 i = 0; i < 128; ++i)
{
t = f(t), r = f(f(r));
if (t == r || (q = (__int128)p * abs(t - r) % N) == 0)
{
break;
}
p = q;
}
i64 d = gcd(p, N);
if (d > 1)
{
return d;
}
} while (t != r);
}
}
std::unordered_map<i64, i64> mp;
i64 mx = 1e18;
void find(i64 n)
{
if (n == 1)
{
return;
}
if (is_prime(n))
{
mx = std::min(mx, n);
mp[n]++;
return;
}
i64 p = n;
while (p >= n)
{
p = Pollard_Rho(p);
}
find(p);
find(n / p);
}
void solve()
{
i64 n;
i64 m;
cin >> n;
m = n;
if (is_prime((n)))
{
cout << n % P << " ";
}
else
{
mp.clear();
mx = 1e18 + 10;
while (n > 1)
{
i64 tmp = Pollard_Rho(n);
if (is_prime(tmp))
{
mx=std::min(mx,tmp);
mp[tmp]++;
}
else
{
find(tmp);
}
n /= tmp;
}
bool ok1 = 1;
for (auto &e : mp)
{
if (e.second >= 2)
{
ok1 = 0;
break;
}
}
bool ok2 = (mp.size() > 1);
if (ok1)
{
cout << "1 ";
}
else if (!ok1 && ok2)
{
cout << "1 ";
}
else if (!ok1 && !ok2)
{
cout << mx % P << " ";
}
}
}
int main()
{
i64 T = 1;
cin.tie(0)->sync_with_stdio(0);
cin >> T;
while (T--)
{
solve();
}
return 0;
}
H. String and GCD
字符串
、数学
莫反+fail树,队友写的。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
const int MOD=998244353;
int t,n,m,tot,ans;
int p[N+5],nex[N+5],cnt[N+5];
char s[N+5];
vector<int> yz[N+5],G[N+5];
bool not_p[N+5];
int prime[N + 5],phi[N+5];
void get_phi()
{
phi[1]=1;
for (int i = 2; i <= N;i++)
{
if(!not_p[i])
{
prime[++tot] = i;
phi[i]=i-1;
}
for (int j = 1; j <= tot && i * prime[j] <= N;j++)
{
not_p[prime[j] * i] = 1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
void getYz()
{
for(int i=1;i<=N;i++)
for(int j=i;j<=N;j+=i)
yz[j].push_back(i);
}
void csh()
{
ans=1;
for(int i=0;i<=n;i++)
{
nex[i]=0;
G[i].clear();
}
}
void dfs(int v)
{
int sum=1;
for(int i=0;i<yz[v].size();i++)
{
int x=yz[v][i];
sum=(sum+1ll*cnt[x]*phi[x])%MOD;
cnt[x]++;
}
ans=(1ll*ans*sum)%MOD;
for(int i=0;i<G[v].size();i++)
dfs(G[v][i]);
for(int i=0;i<yz[v].size();i++)
cnt[yz[v][i]]--;
}
int main()
{
int size(512<<20); // 512M
__asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
get_phi();
getYz();
cin>>t;
while(t--)
{
csh();
scanf("%s",s+1);
n=strlen(s+1);
int j=0;
for(int i=2;i<=n;i++)
{
while(j&&s[i]!=s[j+1]) j=nex[j];
if(s[i]==s[j+1]) j++;
nex[i]=j;
}
for(int i=1;i<=n;i++)
G[nex[i]].push_back(i);
dfs(0);
printf("%lld\n",ans);
}
exit(0);
}
I. WO MEI K
数学
、图论
、数据结构
先考虑 k = 2 k=2 k=2 的情况,可以直接考虑每条边对答案的贡献,对一条边的两个端点,分别向外扩展直至遇到同边权的边对应的端点为止,所得的连通块大小分别为 s z [ v ] , s z [ u ] sz[v],sz[u] sz[v],sz[u],则该边贡献就是 s z [ v ] ∗ s z [ u ] sz[v]*sz[u] sz[v]∗sz[u]。显然每次考虑边权为 w w w 的所有边时,相当于在原树上抠除这些边,形成若干个连通块,只要能维护连通块大小,就可以计算每条边的贡献。
记所有边贡献之和为 r e s res res,则对于所有 k ≥ 2 k \geq 2 k≥2,对应结果为 r e s ∗ C n − 2 k − 2 C n k \cfrac{res*C_{n-2}^{k-2}}{C_n^k} Cnkres∗Cn−2k−2,其中乘上 C n − 2 k − 2 C_{n-2}^{k-2} Cn−2k−2 是因为选定的边在上述 C n − 2 k − 2 C_{n-2}^{k-2} Cn−2k−2 种组合中都提供贡献。最终答案是所有结果的异或和(给结果取模,但不要给最终答案取模)。
官方题解做法不再解释。这里提供一种使用LCT的做法,因为维护动态树的连通块大小是LCT的经典应用之一。这一做法需要知道如何使用LCT维护子树信息,参考P4219 [BJOI2014]大融合,维护方法完全一样。时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN),但常数较大,使用快读可以卡过。
具体而言,因为通常的LCT只维护了实链上的信息
s
z
1
[
v
]
sz1[v]
sz1[v],要想完整维护子树信息,我们可以增加一个
s
z
2
[
v
]
sz2[v]
sz2[v] 记录虚儿子传来的信息,用
s
z
1
[
v
]
sz1[v]
sz1[v] 记录完整子树信息。
于是所有涉及虚实儿子变化的函数都要对应修改,修改pushup、access、link即可,cut只会断开实链,无需修改。具体方法见代码。
#include <bits/stdc++.h>
using namespace std;
namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
char buf[1000000], *p1 = buf, *p2 = buf;
#endif
inline int read()
{
int s = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
ch = getchar();
while (ch >= '0' && ch <= '9')
s = s * 10 + ch - '0', ch = getchar();
return s;
}
};
using namespace fastIO;
#define ls tr[x].ch[0]
#define rs tr[x].ch[1]
constexpr int64_t MAXN = 2e5 + 5, mod = 998244353;
struct LCT
{
struct Node
{
int fa, ch[2], lazy;
int sz1, sz2;
} tr[MAXN];
bool isRoot(int x)
{
int g = tr[x].fa;
return tr[g].ch[0] != x && tr[g].ch[1] != x;
}
void reverse(int x)
{
if (!x)
return;
swap(ls, rs);
tr[x].lazy ^= 1;
}
void pushup(int x) { tr[x].sz1 = tr[ls].sz1 + tr[rs].sz1 + tr[x].sz2 + 1; }
void pushdown(int x)
{
if (tr[x].lazy)
{
reverse(ls);
reverse(rs);
tr[x].lazy = 0;
}
}
void push(int x)
{
if (!isRoot(x))
push(tr[x].fa);
pushdown(x);
}
void rotate(int x)
{
int y = tr[x].fa, z = tr[y].fa, k = tr[y].ch[1] == x;
if (!isRoot(y))
tr[z].ch[tr[z].ch[1] == y] = x;
tr[x].fa = z;
tr[y].ch[k] = tr[x].ch[k ^ 1];
if (tr[x].ch[k ^ 1])
tr[tr[x].ch[k ^ 1]].fa = y;
tr[y].fa = x;
tr[x].ch[k ^ 1] = y;
pushup(y);
}
void splay(int x)
{
push(x);
while (!isRoot(x))
{
int y = tr[x].fa, z = tr[y].fa;
if (!isRoot(y))
(tr[z].ch[0] == y) ^ (tr[y].ch[0] == x) ? rotate(x) : rotate(y);
rotate(x);
}
pushup(x);
}
void access(int x)
{
for (int ch = 0; x; ch = x, x = tr[x].fa)
{
splay(x);
tr[x].sz2 += tr[rs].sz1 - tr[ch].sz1;
rs = ch;
pushup(x);
}
}
void makeroot(int x) { access(x), splay(x), reverse(x); }
void split(int x, int y) { makeroot(x), access(y), splay(y); }
void link(int x, int y)
{
makeroot(x);
makeroot(y);
tr[y].sz2 += tr[x].sz1;
tr[x].fa = y;
pushup(y);
}
void cut(int x, int y)
{
split(x, y);
if (tr[y].ch[0] != x || rs)
return;
tr[x].fa = tr[y].ch[0] = 0;
pushup(x);
}
int findroot(int x)
{
access(x);
splay(x);
while (ls)
pushdown(x), x = ls;
return x;
}
} t1;
int64_t fac[MAXN];
vector<pair<int, int>> E[MAXN];
int64_t quickpow(int64_t a, int64_t b = mod - 2, int64_t m = mod)
{
int64_t ans = 1;
while (b > 0)
{
if (b & 1)
ans = ans * a % m;
a = a * a % m;
b >>= 1;
}
return ans % m;
}
inline int64_t C(int n, int m) { return fac[n] * quickpow(fac[m]) % mod * quickpow(fac[n - m]) % mod; }
int main()
{
fac[0] = 1;
for (int64_t i = 1; i < MAXN; i++)
fac[i] = fac[i - 1] * i % mod;
int t = read();
while (t--)
{
int n = read();
memset(t1.tr, 0, sizeof(LCT::Node) * (n + 1));
unordered_set<int> s;
for (int i = 1; i < n; i++)
{
int v = read(), u = read(), w = read();
E[w].emplace_back(v, u), s.insert(w);
t1.link(v, u);
}
int64_t ans = 0, res = 0;
for (auto w : s)
{
auto &e = E[w];
for (auto [v, u] : e)
t1.cut(v, u);
for (auto [v, u] : e)
{
t1.makeroot(v);
int64_t sz1 = t1.tr[v].sz1;
t1.makeroot(u);
int64_t sz2 = t1.tr[u].sz1;
res = (res + sz1 * sz2 % mod) % mod;
}
for (auto [v, u] : e)
t1.link(v, u);
e.clear();
}
for (int k = 2; k <= n; k++)
ans ^= res * C(n - 2, k - 2) % mod * quickpow(C(n, k)) % mod;
printf("%lld\n", ans); // 没要求给ans取模
}
return 0;
}
J. Kong Ming Qi
搜索
、暴力
打表,发现规律比较明显。唯一不清楚的只有 n = m = 4 n=m=4 n=m=4 的情况,手动模拟得到答案为1。于是可以猜测如果 n , m n,m n,m 若有一者能被 3 3 3 整除,则答案为2。只有一行或一列的情况需要特判。
N M Ans
1 1, 1
1 2, 1
1 3, 2
1 4, 2
1 5, 3
1 6, 3
2 1, 1
2 2, 1
2 3, 2
2 4, 1
2 5, 1
2 6, 2
3 1, 2
3 2, 2
3 3, 2
3 4, 2
#include <bits/stdc++.h>
using namespace std;
const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int N, M, ans;
bool g[20][20];
void dfs()
{
int cnt = 0;
for (int i = 0; i < N + 2; i++)
{
for (int j = 0; j < M + 2; j++)
if (g[i][j])
++cnt;
}
ans = min(ans, cnt);
for (int i = 0; i < N + 2; i++)
{
for (int j = 0; j < M + 2; j++)
{
if (!g[i][j])
continue;
for (int k = 0; k < 4; k++)
{
int nx = i + dx[k], ny = j + dy[k];
if (nx < 0 || nx > N + 1 || ny < 0 || ny > M + 1 || !g[nx][ny])
continue;
int tx = nx + dx[k], ty = ny + dy[k];
if (tx < 0 || tx > N + 1 || ty < 0 || ty > M + 1 || g[tx][ty])
continue;
g[i][j] = 0, g[nx][ny] = 0, g[tx][ty] = 1;
dfs();
g[i][j] = 1, g[nx][ny] = 1, g[tx][ty] = 0;
}
}
}
}
void bf()
{
for (N = 1; N <= 3; N++)
{
for (M = 1; M <= 6; M++)
{
if (N == 3 && M >= 5)
break;
ans = 100;
memset(g, 0, sizeof(g));
for (int i = 1; i <= N; i++)
for (int j = 1; j <= M; j++)
g[i][j] = 1;
dfs();
cout << N << " " << M << ", " << ans << endl;
}
}
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
// bf();
int t;
cin >> t;
while (t--)
{
int n, m;
cin >> n >> m;
if (n > m)
swap(n, m);
if (n == 1)
cout << (m - 1) / 2 + 1 << "\n";
else
cout << (n % 3 == 0 || m % 3 == 0 ? "2\n" : "1\n");
}
return 0;
}
K. Circuit
图论
、DP
找有向图的最小环是一个经典问题,既可以用floyd在 O ( N 3 ) O(N^3) O(N3) 时间內解决,也可以用dijkstra在 O ( N ( N + M ) l o g N ) O(N(N+M)logN) O(N(N+M)logN) 时间內解决,参照本文。为了方便理解,我们使用dijkstra算法。
本题的关键在于计数。首要一点是去重,设当前枚举的起点为
s
s
s,我们在最短路过程中强制不走所有
u
<
s
u<s
u<s 的点,则每个环只在以最小环点为起点时被计算一次。
同时,在最短路过程中维护到当前点的方案数,则
d
p
[
s
]
dp[s]
dp[s] 就是当前起点对应的最小环数量。
#include <bits/stdc++.h>
using namespace std;
#define int int64_t
constexpr int MAXN = 505, INF = 1e17, mod = 998244353;
using PII = pair<int, int>;
vector<PII> G[MAXN];
int d[MAXN], dp[MAXN], n, m;
bool vis[MAXN];
void dijkstra(int s)
{
fill(vis + s, vis + n + 1, 0);
fill(d + s, d + n + 1, INF);
d[s] = 0, dp[s] = 1;
priority_queue<PII, vector<PII>, greater<PII>> q;
q.emplace(0, s);
bool flg = 1;
while (!q.empty())
{
auto [_, v] = q.top();
q.pop();
if (vis[v])
continue;
vis[v] = 1;
for (auto [u, w] : G[v])
{
if (u < s)
continue;
if (!vis[u] && d[v] + w <= d[u])
{
if (d[v] + w < d[u])
d[u] = d[v] + w, dp[u] = dp[v], q.emplace(d[u], u);
else
dp[u] = (dp[v] + dp[u]) % mod;
}
}
if (flg)
d[s] = INF, vis[s] = 0, flg = 0;
}
}
int32_t main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int v, u, w;
cin >> v >> u >> w;
G[v].emplace_back(u, w);
}
int mn = 1e17, ways = 0;
for (int i = 1; i <= n; i++)
{
dijkstra(i);
if (d[i] < mn)
mn = d[i], ways = dp[i];
else if (d[i] == mn)
ways = (ways + dp[i]) % mod;
}
if (mn == 1e17)
cout << "-1 -1\n";
else
cout << mn << " " << ways << "\n";
for (int i = 1; i <= n; i++)
G[i].clear();
}
return 0;
}
L. a-b Problem
博弈论
、排序
、贪心
按 a + b a+b a+b 排序后从大到小轮流取即可。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
priority_queue<tuple<int, int, int>> q;
for (int i = 1, a, b; i <= n; i++)
{
cin >> a >> b;
q.emplace(a + b, a, b);
}
int64_t ans1 = 0, ans2 = 0;
bool flg = 1;
while (!q.empty())
{
auto [_, a, b] = q.top();
q.pop();
flg ? ans1 += a : ans2 += b;
flg ^= 1;
}
cout << ans1 - ans2 << "\n";
}
return 0;
}