2022 年牛客多校第六场补题记录

A Array

题意:给定长度为 n n n 的序列 { a n } \{a_n\} {an}。现构造一个序列 { b m } \{b_m\} {bm},令 { c } \{c\} {c} { b m } \{b_m\} {bm} 序列的无穷拼接,要求 { c } \{c\} {c} 中每连续 a i a_i ai 个数字就得出现一次 i i i。要求 { b m } \{b_m\} {bm} 序列长度 m ≤ 1 × 1 0 6 m\leq 1\times 10^6 m1×106

解法:设最终长度为 M M M。首先注意到,对于每个数字,最少填放次数为 ⌈ M a i ⌉ \left \lceil \dfrac{M}{a_i}\right \rceil aiM,因而理论上满足 ∑ i = 1 n ⌈ M a i ⌉ ≤ M \displaystyle \sum_{i=1}^n \left \lceil \dfrac{M}{a_i}\right \rceil \leq M i=1naiMM 即可保证有解,可推出即 ∑ i = 1 n 1 a i ≤ 1 \displaystyle \sum_{i=1}^n \dfrac{1}{a_i}\leq 1 i=1nai11。此题条件更为苛刻: ∑ i = 1 n 1 a i ≤ 1 2 \displaystyle \sum_{i=1}^n \dfrac{1}{a_i}\leq \dfrac{1}{2} i=1nai121,暗示可能需要对 a i a_i ai 进行适当的缩小,至多可以缩小到原来的一半。

因而可以考虑这样的构造:令 M = 2 k M=2^k M=2k ∀ i ∈ [ 1 , n ] \forall i \in [1,n] i[1,n] a i ′ ← 2 j a_i' \leftarrow 2^j ai2j,其中 2 j 2^j 2j 为小于等于 a i a_i ai 的最大值。这样 ∑ i = 1 n 1 a i ′ ≤ 1 \displaystyle \sum_{i=1}^n \dfrac{1}{a_i'}\leq 1 i=1nai11。同时,由于 a i ′ ∣ M a_i'|M aiM,因而不会出现多个数字都要填在同一处导致某些数字需要出现的更多以满足条件,从小到大的对 a i ′ a_i' ai 进行填放。即可。实际操作中可取 M = 2 19 M=2^{19} M=219

#include <bits/stdc++.h>
using namespace std;
const int N = 100000, M = 1 << 19;
pair<int, int> a[N + 5];
int ans[M + 5];
int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n;i++)
	{
		scanf("%d", &a[i].first);
		a[i].second = i;
		for (int j = 20; j >= 0;j--)
			if (a[i].first >> j & 1)
			{
				a[i].first = 1 << j;
				break;
			}
	}
	sort(a + 1, a + n + 1);
	int now = 1;
	for (int i = 1; i <= n; i++)
	{
		for (int j = now; j <= M; j += a[i].first)
			ans[j] = a[i].second;
		while (ans[now])
			now++;
	}
	printf("%d\n", M);
	for (int i = 1; i <= M;i++)
		printf("%d ", !ans[i] ? 1 : ans[i]);
	return 0;
}

B Eezie and Pie

题意:给定一个 n n n 个点的树,第 i i i 个节点可以到达其第 j , j ∈ [ 1 , d i ] j,j \in [1,d_i] j,j[1,di] 级祖先。问每个点能被几个点到达。 n ≤ 2 × 1 0 6 n \leq 2\times 10^6 n2×106

解法:树上差分。对于每个点,其贡献在于从它往上 d i d_i di 个节点的一条链。倒过来从儿子往父亲差分,那么只需要给当前节点打上 + 1 +1 +1 标记, d i + 1 d_i+1 di+1 级祖先打上 − 1 -1 1 标记即可。总时间复杂度 O ( n ) \mathcal O(n) O(n)

#include <bits/stdc++.h>
#define fp(i, a, b) for (int i = a, i##_ = (b) + 1; i < i##_; ++i)

using namespace std;
const int N = 2e6 + 5;
using ll = int64_t;
int n, d[N], sz[N], dep[N], fa[N][21];
vector<int> G[N];
int get(int u, int k) {
    for (int i = 20; ~i; --i)
        if (k >= dep[u] - dep[fa[u][i]])
            k -= dep[u] - dep[fa[u][i]], u = fa[u][i];
    return u;
}
void dfs(int u, int p) {
    dep[u] = dep[p] + 1;
    fa[u][0] = p, sz[u] = 1;
    fp(i, 1, 20) fa[u][i] = fa[fa[u][i - 1]][i - 1];
    --sz[get(u, d[u] + 1)];
    for (auto v : G[u])
        if (v != p)
            dfs(v, u), sz[u] += sz[v];
}
void Solve() {
    scanf("%d", &n);
    for (int u, v, i = 1; i < n; ++i)
        scanf("%d%d", &u, &v),
        G[u].push_back(v), G[v].push_back(u);
    fp(i, 1, n) scanf("%d", d + i);
    dfs(1, 0);
    fp(i, 1, n) printf("%d ", sz[i]);
}
int main() {
    int t = 1;
    while (t--) Solve();
    return 0;
}

C Forest

题意:给定 n n n 个点 m m m 条带权边的无向图,问从中选出若干条边和全部的点构成的 2 m 2^m 2m 张子图中,最小生成森林的边权值和。 n ≤ 16 n \leq 16 n16 m ≤ 100 m \leq 100 m100

解法:首先从小到大的对边进行排序,等边权的按照边编号排序,保证生成森林唯一。

从小到大依次加入每条边考虑贡献。考虑第 i i i 条边 ( u , v ) (u,v) (u,v) 在哪些子图中会加入到生成森林中: u , v u,v u,v 不连通。考虑补集,设比它大的还有 l l l 条边,则答案为 f i − 1 , S g U / S 2 l f_{i-1,S}g_{U/S}2^l fi1,SgU/S2l,其中 f i − 1 , S f_{i-1,S} fi1,S 表示前 i − 1 i-1 i1 条边构成的所有 2 i − 1 2^{i-1} 2i1 个子图中, u , v u,v u,v 所在连通块为 S S S 的子图个数, U U U 为全集, g S g_{S} gS 表示 S S S 全部的导出子图个数,统计 S S S 中边数目为 x x x,则 g S = 2 x g_S=2^x gS=2x

考虑如何计算 f i , S f_{i,S} fi,S。首先加入 ( u , v ) (u,v) (u,v) 只会对 { u , v } ⊂ S \{u,v\} \sub S {u,v}S S S S 有贡献,有:
f i , S ← 2 f i − 1 , S + ∑ T ⊂ S f i − 1 , T f i − 1 , S / T f_{i,S} \leftarrow 2f_{i-1,S}+\sum_{T \sub S} f_{i-1,T}f_{i-1,S / T} fi,S2fi1,S+TSfi1,Tfi1,S/T
其含义为:对于已经连通的 f i − 1 , S f_{i-1,S} fi1,S,这条边是否出现在子图中是无所谓的;然后枚举构成 S S S 的两个子连通块 T , S / T T,S/T T,S/T,要求 u ∈ T u \in T uT v ∈ S / T v \in S/T vS/T,利用这条边进行连通。这一过程可以通过子集卷积进行加速到 O ( n 2 2 n ) \mathcal O(n^22^n) O(n22n),朴素实现为 O ( 3 n ) \mathcal O(3^n) O(3n)

因而总的复杂度为 O ( m 3 n ) \mathcal O(m3^n) O(m3n)

#include <bits/stdc++.h>
using namespace std;
const int N = 1 << 16;
const long long mod = 998244353;
long long g[N], f[105][N];
long long th[105];
struct line
{
    int from;
    int to;
    int w;
    bool operator<(const line &b)const
    {
        return w < b.w;
    }
    line(int _from, int _to, int _w)
    {
        from = _from;
        to = _to;
        w = _w;
    }
};
int main()
{
    th[0] = 1;
    for (int i = 1; i <= 100;i++)
        th[i] = th[i - 1] * 2 % mod;
    int n, x;
    scanf("%d", &n);
    int all = (1 << n) - 1;
    vector<line> que;
    for (int i = 0; i < n;i++)
        for (int j = 0; j < n;j++)
        {
            scanf("%d", &x);
            if (x && i < j)
                que.emplace_back(i, j, x);
        }
    sort(que.begin(), que.end());
    for (int i = 0; i < n;i++)
        f[0][1 << i] = 1;
    for (int i = 0; i < 1 << n;i++)
        g[i] = 1;
    long long ans = 0;
    for (int i = 0; i < que.size(); i++)
    {
        long long now = th[i];
        int u = que[i].from, v = que[i].to, w = que[i].w;
        for (int S = 0; S < 1 << n; S++)
            if ((S >> u & 1) && (S >> v & 1))
                now = (now - f[i][S] * g[all ^ S] % mod + mod) % mod;
        ans = (ans + now * w % mod * th[que.size() - i - 1] % mod) % mod;
        for (int S = 0; S < 1 << n;S++)
        {
            f[i + 1][S] = f[i][S];
            if (!(S >> u & 1) || !(S >> v & 1))
                continue;
            f[i + 1][S] = f[i + 1][S] * 2 % mod;
            g[S] = g[S] * 2 % mod;
            for (int T = (S - 1) & S; T > S - T; T = (T - 1) & S)
                if ((T >> u & 1) ^ (T >> v & 1))
                    f[i + 1][S] = (f[i + 1][S] + f[i][T] * f[i][S ^ T] % mod) % mod;
        }
    }
    printf("%lld", ans);
    return 0;
}

D Fourier and Theory for the Universe

题意:记一个整数 N N N 是好的,当且仅当 N = ∏ i = 1 k p i α i \displaystyle N=\prod_{i=1}^k p_i^{\alpha_i} N=i=1kpiαi ∑ α i \sum \alpha_i αi 为偶数,且 ∀ j ∈ [ 1 , k ] \forall j \in [1,k] j[1,k] 2 α j ≤ ∑ α i 2\alpha_j \leq \sum \alpha_i 2αjαi。求 [ 1 , n ] [1,n] [1,n] 区间中有多少个好整数。 n ≤ 1 × 1 0 11 n \leq 1\times 10^{11} n1×1011

解法:在做这道题之前,先需要了解一下 min_25 筛的基本原理和流程。

min_25 筛是用于计算积性函数 f ( x ) f(x) f(x) 的前缀和,时间复杂度为 O ( n 3 4 ) log ⁡ n \dfrac{\mathcal O(n^{\frac{3}{4}})}{\log n} lognO(n43),其使用条件: f ( x ) f(x) f(x) 为一积性函数,满足 f ( p ) f(p) f(p) 为一多项式函数,且 f ( p c ) f(p^c) f(pc) 可以以较低时间复杂度求出,基本思想为首先计算出小于等于 n n n所有质数 f ( x ) f(x) f(x) 的和,然后逐步从大到小的插入质数。

整体分为两步递推:第一步为计算质数处的信息,第二步是利用质数处信息计算真正的 f ( x ) f(x) f(x) 和。令 K K K 为小于等于 n \sqrt n n 的质数个数。

质数处 f ( x ) f(x) f(x) 递推得到 g ( n , k ) g(n,k) g(n,k)

为了简单起见,令
f 1 ( x ) = { f ( x ) , x ∈ P ∏ i = 1 k f 1 α i ( p i ) , x = ∏ i = 1 k p i α i ∩ x ∉ P f_1(x)= \begin{cases}f(x),x \in \Bbb P\\ \displaystyle \prod_{i=1}^kf_1^{\alpha_i}(p_i),x=\prod_{i=1}^k p_i^{\alpha_i} \cap x \not \in \Bbb P\\ \end{cases} f1(x)= f(x),xPi=1kf1αi(pi),x=i=1kpiαixP
为一完全积性函数。通常在实际操作过程中,利用 f ( x ) f(x) f(x) 为一多项式函数这一条件,可以令这一 f 1 ( x ) f_1(x) f1(x) 为一单项式 x m x^m xm,对每个单项式进行单独计算再合并。设 l n l_n ln 为合数 n n n 的最小质因子, p k p_k pk 为从小到大的第 k k k 个质数, P \Bbb P P 为质数集合,构造函数 g ( n , k ) = ∑ i = 1 n [ i ∈ P ∪ l i > p k ] i m \displaystyle g(n,k)=\sum_{i=1}^n [i \in {\Bbb P} \cup l_i > p_k]i^m g(n,k)=i=1n[iPli>pk]im。注意 g ( n , k ) g(n,k) g(n,k) 不包含 1 1 1 g ( n , k ) g(n,k) g(n,k) 这一函数可以从 k = 0 k=0 k=0 从小到大的递推到 K K K,其中 g ( n , 0 ) g(n,0) g(n,0) 为全部整数的信息,可以利用自然数幂和 O ( 1 ) O(1) O(1) 的求出,而 g ( n , K ) g(n,K) g(n,K) 则是我们想要的全体质数处的 f ( x ) f(x) f(x) 和(因为这部分数字 f 1 ( x ) = f ( x ) f_1(x)=f(x) f1(x)=f(x))。

考虑从 k = j k=j k=j 转移到 k = j + 1 k=j+1 k=j+1
g ( n , j + 1 ) ← g ( n , j ) − f 1 ( p j + 1 ) ( g ( ⌊ n p j + 1 ⌋ , j ) − g ( p j + 1 , j ) ) g(n,j+1)\leftarrow g(n,j)-f_1(p_{j+1})\left(g\left(\left \lfloor \dfrac{n}{p_{j+1}}\right \rfloor,j\right)-g(p_{j+1},j)\right) g(n,j+1)g(n,j)f1(pj+1)(g(pj+1n,j)g(pj+1,j))
显然从 k = j k=j k=j 提升到 k = j + 1 k=j+1 k=j+1,需要筛除 l i = p j + 1 l_i=p_{j+1} li=pj+1 的所有合数 i i i 对答案的贡献。对于 [ 1 , n ] [1,n] [1,n] 范围内满足 l i = p j + 1 l_i=p_{j+1} li=pj+1 的合数 i i i,有 i p j + 1 \dfrac{i}{p_{j+1}} pj+1i 的最小质因子大于等于 p j + 1 p_{j+1} pj+1。因而问题转化到了 [ 1 , ⌊ n p j + 1 ⌋ ] \left[1,\left \lfloor \dfrac{n}{p_{j+1}}\right \rfloor\right] [1,pj+1n] 的子问题上,因而容斥的第一项为 g ( ⌊ n p j + 1 ⌋ , j ) g\left(\left \lfloor \dfrac{n}{p_{j+1}}\right \rfloor,j\right) g(pj+1n,j)。但是问题是我们 g ( n , k ) g(n,k) g(n,k) 中保留了小于等于 p j + 1 p_{j+1} pj+1 的质数,但它们不属于要筛除的部分——这些小质数乘以 p j + 1 p_{j+1} pj+1 构成的合数的 l l l 值小于等于 p j + 1 p_{j+1} pj+1,因而对于原本的式子是不需要减掉的。然后注意到现在的 f 1 ( x ) f_1(x) f1(x) 为一完全积性函数 n p j + 1 \dfrac{n}{p_{j+1}} pj+1n p j + 1 p_{j+1} pj+1 不互质也是可以直接拆分的,因而质数 p j + 1 p_{j+1} pj+1 对这部分合数的贡献可以一口气拆出来。对于形如 p j + 1 e ∏ k = j + 2 K p k α k , e ≥ 2 \displaystyle p_{j+1}^e\prod_{k=j+2}^K p_k^{\alpha_k},e \geq 2 pj+1ek=j+2Kpkαk,e2 的数字,在这一步中只拆出一个 p j + 1 p_{j+1} pj+1,更多的 p j + 1 p_{j+1} pj+1 在子问题的递归中逐步除去。因而有上述递推式。

但是有一个问题——对于每一个 n n n 都需要 n n n ⌊ n p j ⌋ \left \lfloor \dfrac{n}{p_j}\right \rfloor pjn 进行二元递推,因而会得到大量的形如 ⌊ ⌊ n p i ⌋ p j ⌋ \left \lfloor\dfrac{\left \lfloor \frac{n}{p_i}\right \rfloor}{p_j} \right \rfloor pjpin 的上界。而 ⌊ ⌊ n p i ⌋ p j ⌋ = ⌊ n p i p j ⌋ \left \lfloor\dfrac{\left \lfloor \frac{n}{p_i}\right \rfloor}{p_j} \right \rfloor =\left \lfloor\dfrac{n}{p_ip_j} \right \rfloor pjpin =pipjn,那么总的会需要 O ( n ) O\left(\sqrt n \right) O(n ) 个形如 ⌊ n ∏ p i α i ⌋ \left \lfloor \dfrac{n}{\prod p_i^{\alpha_i}}\right \rfloor piαin 的上限(由整除分块可得,第一个变量至多有 O ( n ) O(\sqrt n) O(n ) 种取值, n n n 为带求值域)需要求出。但是观察上式可以发现,只有当 ⌊ n p j ⌋ > p j \left \lfloor \dfrac{n}{p_{j}} \right \rfloor>p_j pjn>pj p j 2 < n p_j^2<n pj2<n 时才会有二元递推,否则就是直接继承,因而只对 p j 2 < n p_j^2<n pj2<n 的进行递推,复杂度才不是 O ( n n log ⁡ n ) O\left (\sqrt n \dfrac{\sqrt n}{\log \sqrt n}\right ) O(n logn n ) 而是 O ( n 3 4 ) log ⁡ n \dfrac{\mathcal O(n^{\frac{3}{4}})}{\log n} lognO(n43)

利用 g ( n , k ) g(n,k) g(n,k) 得到真正的 ∑ f ( x ) \sum f(x) f(x)

S ( n , k ) = ∑ i = 1 n [ l i > p k ] f ( i ) \displaystyle S(n,k)=\sum_{i=1}^n [l_i >p_k]f(i) S(n,k)=i=1n[li>pk]f(i)(注意 S ( 1 , k ) = 0 S(1,k)=0 S(1,k)=0,不会计入 1 1 1)。同时利用线性筛计算出质数处 P j = ∑ i = 1 j f ( p i ) \displaystyle P_j=\sum_{i=1}^jf(p_i) Pj=i=1jf(pi) 的和。本递推由 k = K k=K k=K k = 0 k=0 k=0 递推, S ( n , 0 ) S(n,0) S(n,0) 即为答案。本步递推相当“暴力”:暴力枚举本步的质数和指数。
S ( n , j ) = { g ( n , K ) , j = K g ( n , K ) − P j + ∑ k = j K ∑ e = 1 + ∞ f ( p k e ) ( S ( ⌊ n p k e ⌋ , j + 1 ) + [ e > 1 ] ) , j < K S(n,j)= \begin{cases} g(n,K),j=K\\ \displaystyle g(n,K)-P_j+\sum_{k=j}^{K} \sum_{e=1}^{+\infty} f(p_k^e)\left(S\left(\left \lfloor \dfrac{n}{p_k^e}\right \rfloor,j+1 \right)+[e>1]\right),j<K \end{cases} S(n,j)= g(n,K),j=Kg(n,K)Pj+k=jKe=1+f(pke)(S(pken,j+1)+[e>1]),j<K
即由于 f ( x ) f(x) f(x) 没有完全积性,因而必须枚举出 p k p_k pk 的具体次数 e e e,一口气将其从 l i ≥ p k l_i \geq p_k lipk 的合数中拆出。上式的 j < K j<K j<K 的计算分为两部分:质数处和 l i = p k l_i =p_k li=pk 的合数处。 g ( n , K ) − P j g(n,K)-P_j g(n,K)Pj 为质数的计算。对于求和式内部,对于形如 p k e p_k^e pke 的数字,它们是要计入的,但是 S ( ⌊ n p k e ⌋ , j + 1 ) S\left(\left \lfloor \dfrac{n}{p_k^e}\right \rfloor,j+1 \right ) S(pken,j+1) 中没有算,因为它们在这部分被当作了 1 1 1;而 e = 1 e=1 e=1 p k p_{k} pk 这一数字,已经在 g ( n , K ) − P j g(n,K)-P_j g(n,K)Pj 中计算过了,因而在后面的求和式中不能重复计算。在实际的操作中,会对 e = 1 e=1 e=1 进行单独的讨论。

注意:该步不需要记忆化也可以保证正确的复杂度

回到本题。设 f ( n , k , m , s ) f(n,k,m,s) f(n,k,m,s) 为小于等于 n n n 的数字, l i > p k l_i>p_k li>pk,且质因数拆分中指数最大次方为 m m m,指数和为 s s s 的数字个数,那么利用上述的递推可以得到:
f ( n , k , m , s ) = S u c ( max ⁡ ( m , 1 ) , s + 1 ) ( K − k ) + ∑ j = k + 1 K ∑ e = 1 + ∞ f ( ⌊ n p j e ⌋ , j , max ⁡ ( m , e ) , s + e ) + [ e > 1 ∩ S u c ( max ⁡ ( m , e ) , s + e ) ] f(n,k,m,s)={\rm Suc}(\max(m,1),s+1)(K-k)+\sum_{j=k+1}^K\sum_{e=1}^{+\infty} f\left(\left\lfloor \dfrac{n}{p_j^e}\right \rfloor,j,\max(m,e),s+e\right)+[e>1 \cap {\rm Suc}(\max(m,e),s+e)] f(n,k,m,s)=Suc(max(m,1),s+1)(Kk)+j=k+1Ke=1+f(pjen,j,max(m,e),s+e)+[e>1Suc(max(m,e),s+e)]
其中 S u c ( m , s ) {\rm Suc}(m,s) Suc(m,s) 表示质因数拆分中指数最大次方为 m m m,指数和为 s s s 的指数状态是否合法,函数值仅为 0 , 1 0,1 0,1,递推思路和 S ( n , k ) S(n,k) S(n,k) 完全相同。注意 min_25 筛中无记忆化操作,因而本题只需要利用 min_25 筛计算质数个数的时候,最后进行 S S S 递推时,多传递几个参数即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1000000;
int tot, cnt;
long long block[N + 5];
int ind1[N + 5], ind2[N + 5];
bool vis[N + 5];
long long prime[N + 5];
long long g[N + 5], n;
int get_id(long long x)
{
    if (x <= N)
        return ind1[x];
    else
        return ind2[n / x];
}
void sieve(int n)
{
    for (int i = 2; i <= n;i++)
    {
        if(!vis[i])
            prime[++tot] = i;
        for (int j = 1; j <= tot && prime[j] * i <= n;j++)
        {
            int num = prime[j] * i;
            vis[num] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
}
bool check(int maxtimes, int sumtimes)
{
    return sumtimes % 2 == 0 && maxtimes <= sumtimes / 2;
}
long long dfs(long long up, int prime_cnt, int maxtimes, int sumtimes)
{
    if(up <= prime[prime_cnt])
        return 0;
    long long ans = 0;
    if (check(max(maxtimes, 1), sumtimes + 1))
        ans += g[get_id(up)] - prime_cnt;
    for (int i = prime_cnt + 1; i <= tot && prime[i] * prime[i] <= up; i++)
    {
        long long th = prime[i];
        for (int j = 1; th <= up; th *= prime[i], j++)
        {
            ans += dfs(up / th, i, max(maxtimes, j), sumtimes + j);
            if (j > 1 && check(max(j, maxtimes), sumtimes + j))
                ans++;
        }
    }
    return ans;
}
int main()
{
    scanf("%lld", &n);
    sieve(N);
    for (long long l = 1, r; l <= n; l = r + 1)
    {
        r = n / (n / l);
        block[++cnt] = n / l;
        g[cnt] = n / l - 1;
        if (block[cnt] <= N)
            ind1[block[cnt]] = cnt;
        else
            ind2[n / block[cnt]] = cnt;
    }
    for (int i = 1; i <= tot && prime[i] <= n; i++)
        for (int j = 1; j <= cnt && prime[i] * prime[i] <= block[j]; j++)
            g[j] -= g[get_id(block[j] / prime[i])] - (i - 1); //因为每次都是前面的数字要用后面的转移,所以不会覆盖,原理同背包
    printf("%lld", dfs(n, 0, 0, 0));
    return 0;
}

F Hash

题意:记 n n n 个点的树哈希 H ( T ) = ∑ i = 1 n ∑ j = i + 1 n x i y j z l c a ( i , j )   m o d   P \displaystyle H(T)=\sum_{i=1}^n \sum_{j=i+1}^n x^iy^jz^{{\rm lca}(i,j)} \bmod P H(T)=i=1nj=i+1nxiyjzlca(i,j)modP,其中 P = 998244353 P=998244353 P=998244353。给定 H , x , y , z H,x,y,z H,x,y,z,构造一个不超过 50 50 50 个点的树满足其树哈希为 H H H

解法:考虑构造 37 = 1 + 6 × 6 37=1+6\times 6 37=1+6×6 个点的树—— 1 1 1 为根,剩余 36 36 36 个节点分为 6 6 6 组,每组中选出一个节点 u u u 和两个节点 v 1 , v 2 v_1,v_2 v1,v2,使得 v 1 , v 2 v_1,v_2 v1,v2 的父亲是 u u u,而其他所有节点的父亲均为 1 1 1。这样每组均有 6 ( 5 2 ) = 60 \displaystyle 6{5\choose 2}=60 6(25)=60 种变化方式,树的结构总共有 6 0 6 60^6 606 种变化,达到了 40 P 40P 40P,因而不碰撞到给定数值是不可能事件(概率过低的可能事件)。而找到这一碰撞只需要使用双向广搜,记录前三个和后三个的全部变化,即可在 O ( 6 0 3 log ⁡ 60 ) \mathcal O(60^3 \log 60) O(603log60) 的复杂度内通过。

关于哈希:有三类哈希问题,原像(已知 y y y x x x 使得 f ( x ) = y f(x)=y f(x)=y)、第二原像(给定 x 1 x_1 x1,求 x ≠ x 1 x \neq x_1 x=x1 f ( x ) = f ( x 1 ) f(x)=f(x_1) f(x)=f(x1))、碰撞(找到 x 1 ≠ x 2 x_1 \neq x_2 x1=x2 使得 f ( x 1 ) = f ( x 2 ) f(x_1)=f(x_2) f(x1)=f(x2))。对于原像和第二原像问题,其大概率成功所需要查询谕示器的次数为 O ( P ) O(P) O(P),其中 P P P 为哈希池大小。构造哈希碰撞的期望次数仅为 O ( P ) O(\sqrt P) O(P )。而构造碰撞的第一要务就是找到 O ( P ) O(\sqrt P) O(P ) 级别的组合可能。由生日悖论,超过 O ( P ) O(\sqrt P) O(P ) 级别的哈希值已经有极高概率碰撞。利用这一“悖论”,密码学中有一种生日攻击的方法可在很短的时间内制造 80bit 的哈希碰撞。通常建议使用长度为 160bit 的信息摘要(哈希)。

#include <bits/stdc++.h>
using namespace std;
const long long mod = 998244353;
const int N = 6, M = 6, ALL = N * M + 1;
const int WAY = M * (M - 1) * (M - 2) / 2;
const int INTREE = (M - 1) * (M - 2) / 2;
int spe[N][WAY];
pair<int, int> newson[N][WAY];
long long delta[N][WAY];
long long h, x, y, z;
long long thx[ALL + 5], thy[ALL + 5], thz[ALL + 5];
long long cal(int u, int v, int lca)
{
    if(u > v)
        swap(u, v);
    return thx[u] * thy[v] % mod * thz[lca] % mod;
}
int main()
{
    for (int i = 0; i < WAY;i++)
        for (int k = 0; k < N;k++)
        {
            int fa = (i / INTREE) + M * k + 2;
            spe[k][i] = fa;
            int sona = -1, sonb = -1, cnt = 0;
            for (int s1 = 0; s1 < M && sona == -1;s1++)
                if (s1 != i / 10)
                    for (int s2 = s1 + 1; s2 < M && sonb == -1;s2++)
                        if (s2 != i / INTREE)
                        {
                            if (cnt == i % 10)
                            {
                                sona = s1 + M * k + 2;
                                sonb = s2 + M * k + 2;
                            }
                            cnt++;
                        }
            newson[k][i] = make_pair(sona, sonb);
        }
    int t;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%lld%lld%lld%lld", &h, &x, &y, &z);
        printf("%d\n", ALL);
        thx[0] = thy[0] = thz[0] = 1;
        for (int i = 1; i <= ALL;i++)
        {
            thx[i] = thx[i - 1] * x % mod;
            thy[i] = thy[i - 1] * y % mod;
            thz[i] = thz[i - 1] * z % mod;
        }
        long long ori = 0;
        for (int i = 1; i <= ALL;i++)
            for (int j = i + 1; j <= ALL;j++)
                ori = (ori + thx[i] * thy[j] % mod * thz[1] % mod) % mod;
        if (ori == h)
        {
            for (int i = 2; i <= ALL;i++)
                printf("1 %d\n", i);
            continue;
        }
        memset(delta, 0, sizeof(delta));
        for (int k = 0; k < N; k++)
            for (int i = 0; i < WAY; i++)
            {
                delta[k][i] = (delta[k][i] - cal(spe[k][i], newson[k][i].first, 1) + mod - cal(spe[k][i], newson[k][i].second, 1) + mod - cal(newson[k][i].second, newson[k][i].first, 1) + mod) % mod;
                delta[k][i] = (delta[k][i] + cal(spe[k][i], newson[k][i].first, spe[k][i]) + cal(spe[k][i], newson[k][i].second, spe[k][i]) + cal(newson[k][i].first, newson[k][i].second, spe[k][i])) % mod;
            }
        map<long long, int> memo;
        function<void(int, int, long long)> dfs_front = [&](int step, int id, long long H)
        {
            if (step == N / 2)
            {
                memo[H] = id;
                return;
            }
            for (int i = 0; i < WAY; i++)
                dfs_front(step + 1, id * WAY + i, (H + delta[step][i]) % mod);
        };
        dfs_front(0, 0, 0);
        vector<int> mode(N, -1);
        function<bool(int, int, long long)> dfs_back = [&](int step, int id, long long H)
        {
            if (step == N / 2 - 1)
            {
                long long front = (h - H + mod - ori + mod) % mod;
                if (memo.count(front))
                {
                    int former = memo[front], latter = id;
                    for (int i = N / 2 - 1; i >= 0; i--, former /= WAY)
                        mode[i] = former % WAY;
                    for (int i = N / 2; i < N; i++, latter /= WAY)
                        mode[i] = latter % WAY;
                    return true;
                }
                else
                    return false;
            }
            for (int i = 0; i < WAY; i++)
            {
                auto d = dfs_back(step - 1, id * WAY + i, (H + delta[step][i]) % mod);
                if(d)
                    return true;
            }
            return false;
        };
        dfs_back(N - 1, 0, 0);
        vector<int> father(ALL + 1, 1);
        for (int i = 0; i < N;i++)
            father[newson[i][mode[i]].first] = father[newson[i][mode[i]].second] = spe[i][mode[i]];
        for (int i = 2; i <= ALL;i++)
            printf("%d %d\n", father[i], i);
    }
    return 0;
}

G Icon Design

题意:画出给定缩放尺寸的 NFLS字符画。

解法:找出定位点,暴力模拟。

#include <bits/stdc++.h>
using namespace std;
char a[30][1001];
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= 4 * n + 5;i++)
        for (int j = 1; j <= 13 * n + 19;j++)
            a[i][j] = '.';
    for (int i = 1; i <= 13 * n + 19; i++)
        a[1][i] = a[4 * n + 5][i] = '*';
    for (int i = 1; i <= 4 * n + 5;i++)
        a[i][1] = a[i][13 * n + 19] = '*';
    for (int x = n + 2, y = n + 3, i = 1; i <= 2 * n + 3;i++, x++, y++)
        a[x][y] = a[x][n + 3] = a[x][3 * n + 5] = '@';
    for (int y = 4 * n + 7, i = 1; i <= 2 * n + 3;i++, y++)
        a[n + 2][y] = a[2 * n + 3][y] = '@';
    for (int i = 1, x = n + 2; i <= 2 * n + 3;i++, x++)
        a[x][4 * n + 7] = '@';
    for (int i = 1, x = n + 2; i <= 2 * n + 3;i++, x++)
        a[x][7 * n + 11] = '@';
    for (int i = 1, y = 7 * n + 11; i <= 2 * n + 3;i++, y++)
        a[3 * n + 4][y] = '@';
    for (int i = 1, x = 10 * n + 15; i <= 2 * n + 3;i++, x++)
        a[n + 2][x] = a[2 * n + 3][x] = a[3 * n + 4][x] = '@';
    for (int i = 1, y = n + 2; i <= n + 1;y++, i++)
        a[y][10 * n + 15] = '@';
    for (int i = 1, y = 2 * n + 3; i <= n + 1; i++, y++)
        a[y][12 * n + 17] = '@';
    for (int i = 1; i <= 4 * n + 5; i++)
        printf("%s\n", a[i] + 1);
    return 0;
}

I Line

题意:给定大小为 n n n 的向量集 S v S_v Sv,构造一个整点集 S p S_p Sp 使得 ∀ P ∈ S p \forall P\in S_p PSp ∀ l ⃗ ∈ S v \forall \vec{l} \in S_v l Sv,直线 ( P , l ⃗ ) (P,\vec l) (P,l ) 恰好经过 S p S_p Sp 中的 d d d 个点。 n , d ≤ 6 n,d \leq 6 n,d6,向量集中横纵坐标均为 [ 0 , 6 ] [0,6] [0,6] 的整数。

解法:首先观察 n = 2 n=2 n=2 的情况,取 d = 3 d=3 d=3

在这里插入图片描述

n = 3 n=3 n=3 时,可以看到其本质为将 n = 2 n=2 n=2 的图形向新加入的向量 l 3 ⃗ \vec{l_3} l3 方向进行了平移。

在这里插入图片描述

因而做法即是,枚举每个向量,然后将已有的点向该方向进行平移。为了不让超过 d d d 个点共线,因而每次平移的距离需要是上一次的倍数,使得上一次的图形不会和这一次的发生重叠。本题可以取 23 23 23 倍。

#include <bits/stdc++.h>
using namespace std;
int th[10];
int main()
{
    th[0] = 1;
    for (int i = 1; i < 10;i++)
        th[i] = th[i - 1] * 23;
    int n, d;
    scanf("%d%d", &n, &d);
    set<pair<int, int>> s, ans;
    for (int i = 1, x, y; i <= n;i++)
    {
        scanf("%d%d", &x, &y);
        int d = __gcd(x, y);
        s.insert(make_pair(x / d, y / d));
    }
    ans.insert(make_pair(0, 0));
    int id = -1;
    for (auto dir : s)
    {
        id++;
        auto t = ans;
        for (auto i : t)
            for (int j = 1; j < d;j++)
                ans.insert(make_pair(i.first + j * dir.first * th[id], i.second + j * dir.second * th[id]));
    }
    printf("%d\n", ans.size());
    for(auto i:ans)
        printf("%d %d\n", i.first, i.second);
    return 0;
}

J Number Game

题意:给定三个数字 A , B , C A,B,C A,B,C,一次操作可以令 B ← A − B B \leftarrow A-B BAB C ← B − C C \leftarrow B-C CBC。问 C C C 能否变成 x x x A , B , C , x ∈ [ − 1 × 1 0 18 , 1 × 1 0 18 ] A,B,C,x \in [-1\times 10^{18},1\times 10^{18}] A,B,C,x[1×1018,1×1018] T T T 组测试, T ≤ 1 × 1 0 5 T \leq 1\times 10^5 T1×105

题意:显然不会连续进行两次 B ← A − B B \leftarrow A-B BAB,否则等于没有操作,因而一定是两个操作交替进行的。设初始值为 A 0 , B 0 , C 0 A_0,B_0,C_0 A0,B0,C0

  1. B ← A − B B \leftarrow A-B BAB A 0 , A 0 − B 0 , C 0 A_0,A_0-B_0,C_0 A0,A0B0,C0
  2. C ← B − C C \leftarrow B-C CBC A 0 , A 0 − B 0 , A 0 − B 0 − C 0 A_0,A_0-B_0,A_0-B_0-C_0 A0,A0B0,A0B0C0
  3. B ← A − B B \leftarrow A-B BAB A 0 , B 0 , A 0 − B 0 − C 0 A_0,B_0,A_0-B_0-C_0 A0,B0,A0B0C0
  4. C ← B − C C \leftarrow B-C CBC A 0 , B 0 , 2 B 0 − A 0 + C 0 A_0,B_0,2B_0-A_0+C_0 A0,B0,2B0A0+C0

可以注意到最终的 C C C 会与 C 0 C_0 C0 差一个 A 0 − 2 B 0 A_0-2B_0 A02B0 的倍数,具体倍数的正负与先进行第一个操作与第二个操作有关。因而最终只需要判断 x − C 0 x-C_0 xC0 A 0 − 2 B 0 A_0-2B_0 A02B0 的整除关系即可。当然 C 0 C_0 C0 也可以先进行一次操作变成 B 0 − C 0 B_0-C_0 B0C0,因而再判断一次即可。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int t;
    scanf("%d", &t);
    long long A, B, C, x;
    while(t--)
    {
        scanf("%lld%lld%lld%lld", &A, &B, &C, &x);
        if (A == 2 * B)
        {
            if (x == C || x == B - C)
                printf("Yes\n");
            else
                printf("No\n");
            continue;
        }
        bool flag = 0;
        if ((x - (B - C)) % (A - 2 * B) == 0)
            flag = 1;
        if ((x - C) % (A - 2 * B) == 0)
            flag = 1;
        if(flag)
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}

M Z-Game on grid

题意:给定一个 n × m n\times m n×m 的棋盘,上面有 A,B,.三种字符。有一个棋子从 ( 1 , 1 ) (1,1) (1,1) 出发,如果走到 A的格子则 先手获胜,走到 B的格子则后手获胜,走到 ( n , m ) (n,m) (n,m) 且为 .则为平局。若一个棋子在 ( i , j ) (i,j) (i,j) 可以走到 ( i + 1 , j ) (i+1,j) (i+1,j) 或者 ( i , j + 1 ) (i,j+1) (i,j+1),在不走出棋盘的情况下。二人轮流操作,不允许不操作。问先手是否有必胜、平、败策略。 n , m ≤ 500 n,m \leq 500 n,m500

解法:设 f i , j f_{i,j} fi,j 为走到 ( i , j ) (i,j) (i,j) 的先手情况。先手必胜即是只有走到 A才有 f i , j = 1 f_{i,j}=1 fi,j=1,必败是走到 B才算 f i , j = 1 f_{i,j}=1 fi,j=1,平局则不能碰到任何一个非 .的格子,只有 f n , m = 1 f_{n,m}=1 fn,m=1。利用 i + j i+j i+j 的奇偶性判断先后手,先手取 max ⁡ \max max 转移,后手取 min ⁡ \min min 转移。注意清空数组

#include <bits/stdc++.h>
using namespace std;
const int N = 500;
int f[N + 5][N + 5];
char a[N + 5][N + 5];
int main()
{
    int t, n, m;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n;i++)
            scanf("%s", a[i] + 1);
        memset(f, 0, sizeof(f));
        for (int i = n; i >= 1;i--)
            for (int j = m; j >= 1;j--)
            {
                if (a[i][j] == 'A')
                    f[i][j] = 1;
                else if (a[i][j] == 'B' || (i == n && j == m))
                    continue;
                else if (i == n)
                    f[i][j] = f[i][j + 1];
                else if (j == m)
                    f[i][j] = f[i + 1][j];
                else
                {
                    if ((i + j) % 2 == 0)
                        f[i][j] = max(f[i + 1][j], f[i][j + 1]);
                    else
                        f[i][j] = min(f[i + 1][j], f[i][j + 1]);
                }
            }
        if(f[1][1])
            printf("yes ");
        else
            printf("no ");
        memset(f, 0, sizeof(f));
        for (int i = n; i >= 1;i--)
            for (int j = m; j >= 1;j--)
            {
                if (a[i][j] != '.')
                    f[i][j] = 1;
                else if (i == n && j == m)
                    f[i][j] = 0;
                else if (i == n)
                    f[i][j] = f[i][j + 1];
                else if (j == m)
                    f[i][j] = f[i + 1][j];
                else
                {
                    if ((i + j) % 2 == 0)
                        f[i][j] = min(f[i + 1][j], f[i][j + 1]);
                    else
                        f[i][j] = max(f[i + 1][j], f[i][j + 1]);
                }
            }
        if(!f[1][1])
            printf("yes ");
        else
            printf("no ");
        memset(f, 0, sizeof(f));
        for (int i = n; i >= 1;i--)
            for (int j = m; j >= 1;j--)
            {
                if (a[i][j] == 'B')
                    f[i][j] = 1;
                else if (a[i][j] == 'A' || (i == n && j == m))
                    continue;
                else if (i == n)
                    f[i][j] = f[i][j + 1];
                else if (j == m)
                    f[i][j] = f[i + 1][j];
                else
                {
                    if ((i + j) % 2 == 0)
                        f[i][j] = max(f[i + 1][j], f[i][j + 1]);
                    else
                        f[i][j] = min(f[i + 1][j], f[i][j + 1]);
                }
            }
        if(f[1][1])
            printf("yes\n");
        else
            printf("no\n");
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值