Exam Records 3
9.30
A
题目描述
给定 \(5\) 个长度为 \(n\) 的整数序列 \(A,B,C,D,E\),求
其中,\(med(a,b,c,d,e)\) 为 \(a,b,c,d,e\) 的中位数。
枚举中位数,计算即可。
B
题目描述
给定一个 \(n\) 个点 \(m\) 条边的有向图。不保证不存在重边自环。
记 \(f(x,y,k)\) 表示从 \(x\) 到 \(y\) 经过恰好 \(k\) 条边的不同路径数量,两条路径不同当且仅当存在存在某个 \(i\) 使得两条路径的第 \(i\) 条边不同。
称一个有向图是有趣的,当且仅当存在 \(x,y\),不存在任何正整数 \(M\),使得 \(k \ge M\) 时 \(f(x,y,k)\) 为一常数,也即 \(\lim\limits_{k\to+ \infty} f(x,y,k)\) 不存在。
你需要判断给定的有向图是不是有趣的。
仔细分析,如果有长度 \(\ge 2\) 的环,那么是有趣的。
如果两个自环可以互相到达,那么也是有趣的。
注意一个点有两个自环的情况。
否则就是无趣的。
C
题目描述
有 \(n\) 堆石子,第 \(i\) 堆石子有 \(a_i\)个,\(A\) 和 \(B\) 玩游戏,\(A\) 先手,每次操作可以进行以下操作:
- 选定一个还有石子的石子堆 \(i\),记剩下的石子为 \(a_i'\)。
- 选定一个 \(1\le x\le a_i'\),将该堆中的 \(x\) 个石子移走。
- 选定一个 \(0\le y\le a_i'-x\),将该堆中的 \(y\) 个石子以任意方式分配到剩余的非空石子堆中。
第一个不能操作者输,问 \(A\) 是否有必胜策略,多测。
如果所有数出现次数都为偶数则为必败态。
-
如果没有石子,那么所有数出现次数为偶数,此时为必败态。
-
对于一个必胜态,一定能转移到必败态。
设出现次数为奇数的点从小到大排序为 \(a_1,\cdots,a_m\)。如果 \(m\) 是奇数,可以用 \(a_m\) 把 \(a_x\) 补成 \(a_{x+1}\),\(x<m\) 且为奇数。最后剩下的 \(a_m\) 拿走。如果 \(m\) 是偶数,可以用 \(a_m\) 把 \(a_x\) 补成 \(a_{x+1}\),\(x<m\) 且为偶数,\(a_m\) 留下 \(a_1\) 颗石子,最后剩下的 \(a_m\) 拿走。
-
对于一个必败态,一定只能转移到必胜态。
所有数去重后从小到大排序为 \(a_1,\cdots,a_m\)。如果拿走 \(a_t\),那么第 \(a_t\) 的出现次数就是奇数,需要用 \(a_x(x < t)\) 去把 \(a_t\) 补成偶数出现次数。这时候 \(a_x\) 的出现次数又变成奇数了,以此类推。最终 \(a_1\) 出现次数是奇数,只能用拿走部分后剩下的 \(a_t'\) 去补。这时候发现总和是不变的,但是要求每一步至少总和 \(-1\),所以是不能转移到必败态的。
D
题目描述
你有一个奇怪的计数器,计数器上有一个数字 \(x\),每次你可以做如下操作:
选择一个 \(x=\overline{b_kb_{k-1}\cdots b_1b_0}_{(10)}\)中的一个数位 \(b_i\)。将 \(x\) 变为 \(x+b_i\) 或者 \(x-b_i\)。
例如,当 \(x=(616)_{10}\) 时,你可以选择 \(b=1\),然后让 \(x\) 变为 \(x−b=(615)_{10}\)。
你想要通过最少步数把 \(x\) 变成 \(y\),问最少步数是多少。
分治。对于区间 \([l,r]\),找到中间的 \(9\) 个数 \(x,\cdots,x+8\)。
由于每次最多走 \(9\) 个数,所以任意两个跨过中间数的点,在 \([x, x+8]\) 中,一定停留至少一个点。
对于中间的点分别对于区间 \([l,r]\) 跑最短路,处理区间 \([l,r]\) 的点到中间点的最短路和中间点到 \([l,r]\) 的最短路。
向下分治 \([l,x-1]\) 和 \([x+9,r]\) 即可。
询问时找到对应区间,枚举中间停留点即可。
10.1
A
题目描述
至少删掉多少数使得任意区间和 \(< S\)。
如果总不能满足,那么删掉所有数。
如果 \(S \le 0\),那么把所有 \(\ge S\) 的数删掉。
如果 \(S > 0\),考虑从前向后加入数,维护最大后缀,如果 \(\ge S\),那么弹出最大后缀中的最大数。
可以使用 multiset 维护。
但是弹出后 multiset 可能维护的就不是最大后缀了。
如果 multiset 维护的是正数就不会有这种情况。
遇到正数的时候只在 multiset 中加入。
遇到负数时,先把应该删掉的数删掉,使得最大后缀和 \(< S\)。
如果最大后缀加上当前数仍然 \(\le 0\),那么 multiset 清空。
否则,一直找到 multiset 中的最小数抵消 \(a_i\) 的影响,使 \(a_i\) 为正,然后在 multiset 中加入 \(a_i\)。
代码
#include <set>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1000005;
int n, m, ans, sum, a[N], vis[N];
multiset <int> s;
signed main()
{
freopen("score.in", "r", stdin);
freopen("score.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; ++ i)
scanf("%lld", &a[i]);
if(m <= 0)
{
for(int i = 1; i <= n; ++ i)
ans += a[i] >= m;
printf("%lld\n", ans);
return 0;
}
for(int i = 1; i <= n; ++ i)
{
if(a[i] >= 0)
{
sum += a[i], s.insert(a[i]);
while(sum >= m)
sum -= *prev(s.end()), ++ ans, s.erase(prev(s.end()));
}
else
{
if(sum + a[i] > 0)
{
sum += a[i];
while(a[i] < 0 && s.size())
a[i] += *s.begin(), s.erase(*s.begin());
s.insert(a[i]);
}
else
sum = 0, s.clear();
}
}
printf("%lld\n", ans);
return 0;
}
B
题目描述
大切题这个游戏的完整地图,可以看做是一棵 \(n\) 个节点的树,树根为 \(1\),节点编号 \(1\) 到 \(n\)。
每次任务可以抽象为从一个题单 \(u\) 开始,不断向上切题升级(只能向上走),一直到题单 \(v\) 为止。(保证 \(v\) 是 \(u\) 的祖先)。
在一次任务开始的时候,你会得到一个板子,码风优秀度为 \(c\),然后每一关都有一个板子宝箱,宝箱中的板子有一个码风优秀度 \(a_i\)。如果宝箱中板子的码风优秀度高于你手中的,你就会选择用这个板子替换手中的板子。
这样的任务总共有 \(q\) 个。给出每一个关卡中板子的码风优秀度,以及 \(q\) 次任务的起点与终点,以及任务初始时板子的码风优秀度。
现在想知道,对于每个任务,你会更换多少次板子。
倍增预处理每个点向上更换多少次板子后能到达哪个点。
C
题目描述
该国有 \(N\) 个编号为 \(1\) 到 \(N\) 的城市,由 \(N-1\) 条无向道路连接,第 \(i\) 条道路连接城市 \(i+1\) 和城市 \(a_i\) ,经过道路 \(i\) 的费用为 \(v_i\)。
小 \(D\) 想在这个国家进行一次旅行。出于他的强迫症,行程有如下限制:
旅行必须在城市 \(1\) 开始和结束。如果城市形成的树上有 \(m\) 片叶子结点,那么旅行的天数必须是 \(m+1\) 。每天结束时,除最后一天外,小 \(D\) 必须住在叶子城市的某家酒店。在整个行程中,小 \(D\) 必须在所有叶子结点城市只停留一次。在整个行程中,该国的所有道路必须正好经过两次。除第一天和最后一天外,小 \(D\) 需要自行支付的费用为旅行期间发生的单日最大总通行费。剩余的通行费将由凉心出题人承担。
请帮助 小 \(D\) 设计满足条件且费用尽可能少的旅行。
二份答案 \(mid\)。
设 \(f_{u,a,b}\) 表示 \(u\) 子树,去第一个叶子节点的费用为 \(a\),最终回到 \(u\) 点的费用为 \(b\),中间距离 \(\le mid\) 是否可行。
这样会有很多无用状态。考虑每个节点存满足条件的 \((a,b)\) 点对。对于不同点对 \((a,b), (c, d)\),如果 \(a \le c,b\le d\),那么不需要记录 \((c,d)\) 点对。这样需要记录的点对最多有字数内叶子节点个数个。把这些点对按照 \(a\) 从小到大排序,\(b\) 一定是递减的。合并时可以双指针。
D
最小斯坦纳树板子。
不过这题 \(n \le 100,m \le 500,k\le 10\)。
最终一定是一棵树。
状压。\(f_{i,S}\) 表示以 \(i\) 为根的树,覆盖了 \(S\) 中的点的最小距离。
先枚举 \(S\)。
如果 \(i\) 点有不止一个子树,枚举子集 \(T\):
如果只有一个子树:
类似最短路松弛操作。
杂
10.3
accoder 1 \(\color{white}waccjyt2024\)
*D
题目描述
有一颗 \(n\) 个点有标号的树,点从 \(1 \sim n\) 标号,边从 \(1 \sim n - 1\) 标号为 \(u_i,v_i\)。
现在他想在这棵树的基础上构造一些新的树。
具体的,他想知道在能构造出的所有 \(n\) 个点有标号无根树中,恰好的有 \(x\) 条边与原树相同的数量。并且对于每个 \(x\in[0,n-1] \cap \Z\),输出答案 \(\mod 10^9+7\)。
\(n \le 8000\)。
Prüfer 序列,状压 DP 可以处理 \(n\) 很小的点。
Cayley 定理
节点个数为 \(n\) 的有标号无根树的个数为 \(n^{n-2}\)。
扩展 Cayley 定理 1
\(n\) 个有标号节点形成一个 \(s\) 棵无根树的森林,且 \(s\) 个点所在树两两不同的方案数为 \(sn^{n-s-1}\)。
证明考虑数学归纳法。
扩展 Cayley 定理 2
\(n\) 个点有标号无根树,已经添加了若干条边,目前的连通块大小为 \(a_1,a_2,\cdots,a_m\),连成一棵树的方案数为 \(n^{m-2}\prod a_i\)。
证明使用 Matrix-Tree 定理。
二项式反演
设 \(f_{u,i,j}\) 表示 \(u\) 子树,当前连通块大小为 \(i\),选择相同的变数为 \(j\) 的方案数。
每次讨论是否选择当前边。
选择这条边:
如果不选择:
要防止最后分好连通块后连边时,连上这条边,所以有转移:
考虑扩展 Cayley 定理 \(2\) 中的式子 \(\prod a_i\),是在每个连通块中选择一个点的方案数。
\(f_{u,i,0/1}\) 表示 \(u\) 子树,钦定选择 \(i\) 条边,\(u\) 点所在连通块是否选择点的方案数。
为了转移方便,这里不考虑最后是否连上了没有钦定选择的边,最后二项式反演即可。
由于空间开不下,使用 vector 维护,\(O(n)\) 空间树上背包。
考虑求出 \(f_{1}\) 数组的 \(\text{OGF}\),即多项式 \(F(x) = \sum_{i} f_{1,i,0}x^i\)。
每次转移就是两个多项式相乘或者再乘点什么东西,于是可以代入 \(n\) 个数,然后求出这 \(n\) 个数的 \(F\) 值,然后拉格朗日插值点值转系数。
拉格朗日插值点值转系数
时间复杂度 \(O(n^2)\)。
枚举 \(i\),计算 \(H_i = y_i\prod_{j\not=i}\frac 1 {x_i-x_j}\)。
设 \(F(x)=\prod_{i=1}^n(x-x_i)\),\(G_i(x)=\frac {F(x)}{x-x_i}\)。
则
\(x_j=0\) 的情况要特殊判掉。
最终答案为
代码
#include <cstdio>
#include <vector>
#include <algorithm>
#define int long long
using namespace std;
const int N = 8005;
const int mod = 1000000007;
vector <int> f[N][2];
int n, tot, inv, fac[N], ifa[N], head[N], siz[N], g[N][2];
struct Edge
{
int to, nex;
};
Edge e[N << 1];
void add(int u, int v)
{
e[++ tot] = {v, head[u]};
head[u] = tot;
}
int Pow(int x, int y)
{
int r = 1;
for(; y; y >>= 1, x = x * x % mod)
if(y & 1)
r = r * x % mod;
return r;
}
void dfs(int u, int fa)
{
siz[u] = 1;
f[u][0].resize(5);
f[u][1].resize(5);
f[u][1][0] = f[u][0][0] = 1;
for(int i = head[u], v; i; i = e[i].nex)
{
if((v = e[i].to) == fa)
continue;
dfs(v, u);
for(int j = 0; j <= siz[u] + siz[v]; ++ j)
g[j][0] = g[j][1] = 0;
for(int j = 0; j < siz[v]; ++ j)
{
for(int k = 0; k < siz[u]; ++ k)
{
g[j + k + 1][1] = (g[j + k + 1][1] + f[u][0][k] * f[v][1][j] + f[u][1][k] * f[v][0][j]) % mod;
g[j + k + 1][0] = (g[j + k + 1][0] + f[u][0][k] * f[v][0][j]) % mod;
g[j + k][1] = (g[j + k][1] + f[u][1][k] * f[v][1][j] % mod * n) % mod;
g[j + k][0] = (g[j + k][0] + f[u][0][k] * f[v][1][j] % mod * n) % mod;
}
}
siz[u] += siz[v];
f[v][0].clear();
f[v][1].clear();
f[v][0].shrink_to_fit();
f[v][1].shrink_to_fit();
f[u][0].resize(siz[u] + 5);
f[u][1].resize(siz[u] + 5);
for(int j = 0; j < siz[u]; ++ j)
f[u][0][j] = g[j][0], f[u][1][j] = g[j][1];
}
}
int C(int n, int m)
{
return fac[n] * ifa[m] % mod * ifa[n - m] % mod;
}
signed main()
{
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
scanf("%lld", &n);
for(int i = 1, u, v; i < n; ++ i)
scanf("%lld%lld", &u, &v), add(u, v), add(v, u);
dfs(1, 0);
fac[0] = ifa[0] = 1;
for(int i = 1; i <= n; ++ i)
fac[i] = fac[i - 1] * i % mod;
ifa[n] = Pow(fac[n], mod - 2);
for(int i = n - 1; i >= 1; -- i)
ifa[i] = ifa[i + 1] * (i + 1) % mod;
inv = Pow(n, mod - 2);
for(int i = 0; i < n; ++ i)
f[1][1][i] = f[1][1][i] * inv % mod;
for(int i = 0; i < n; ++ i)
{
int ans = 0;
for(int j = i; j < n; ++ j)
ans = (ans + C(j, i) * f[1][1][j] % mod * (j - i & 1 ? mod - 1 : 1)) % mod;
printf("%lld ", ans);
}
printf("\n");
return 0;
}
10.4
A
题目描述
给定一个序列 \(a\),我们希望选出一个 \(a\) 的子序列 \(b\),其中 \(a\) 中的第 \(i\) 个元素有 \(p_i\) 的概率被选中,每个元素是否被选中之间是相互独立的。
如果 \(b\) 的异或和为 \(s\),称它的权值为 \(s^2\),求bb的权值的期望。
答案对 \(10^9+7\) 取模。
设 \(a_i\) 为二进制第 \(i\) 位为 \(0\) 或 \(1\)。答案为:
DP。设 \(f_{i,j,0/1,0/1}\) 表示结果二进制下第 \(i\) 位为 \(0/1\),第 \(j\) 位为 \(0/1\) 的概率。
B
题目描述
给定 \(n,m\),对于每个 \(1 \le k \le m\),计数满足以下条件的 \(01\) 串数量:
-
长度为 \(n\),且恰有 \(m\) 个 \(1\) 和 \(n - m\) 个 \(0\)。
-
最长的 \(1\) 连续段长度恰好为 \(k\)。
注意 \(m \neq 0\),因此最长的 \(1\) 连续段长度不可能是 \(0\)。
答案对 \(10^9+7\) 取模。
共有 \(n-m\) 个 \(0\),这些 \(0\) 把 \(1\) 分成了 \(n-m+1\) 段,每段可空。
如果不考虑最长连续段长度的限制,使用插板法,方案数为 \({m+(n-m+1)-1 \choose (n-m+1)-1}\)。
转化题意为计算最长 \(1\) 连续段长度 \(\le k\),然后进行差分。
容斥,枚举钦定有多少段长度 \(>k\),设有 \(x\) 段,相当于 \(1\) 的数量减去 \(x(k+1)\),然后再在 \(m\) 段中选 \(x\) 段加上 \(k+1\)。
复杂度调和级数。
C
题目描述
在石头剪刀布中,一共有三种手势:\(R(Rock),P(Paper),S(Scissors)\),其中 \(R\) 能赢 \(S\),\(S\) 能赢 \(P\),\(P\) 能赢 \(R\)。
现在,我们定义 \(w(x,y)\) 是 \(x\) 和 \(y\) 中获胜的手势,特别地,如果 \(x\) 和 \(y\) 相同(也就是平局),那么 \(w(x,y)\) 也和 \(x,y\) 均相同。\(x\) 和 \(y\) 均为 \(R,P,S\) 中的某一种。
我们定义一个对长度为 \(n\) 的字符串 \(s\) 的操作 \(f(s_1s_2\cdots s_n)=w(s_1,s_2)w(s_2,s_3)\cdots w(s_{n−1},s_n)\),也就是说操作结果是一个长度为 \(n-1\) 的字符串,它的第 \(i\) 位恰是 \(w(s_i,s_{i+1})\),也就是 \(s_i\) 和 \(s_{i+1}\) 中获胜的手势。如 \(f(RRRR)=RRR\),\(f(RSPR)=RSP\)。
我们定义一个长度为 \(n\),且由 \(R,P,S\) 组成的字符串的“最终胜者”是对这个字符串进行连续 \(n-1\) 次 \(f\) 操作得到的结果,它显然是某个长度为 \(1\) 的字符串,并且是 \(R,P,S\) 中的一种。
现在,给定一个长度为 \(n\) 的字符串,你需要支持 \(q\) 次操作,操作有两种:
1 k x
:将这个字符串的第 \(k\) 个字符修改为 \(x\),\(k\) 是 \(1-n\) 之间的整数,\(x\in R,P,S\)。
2 l r
:查询这个字符串的第 \(l\) 个字符到第 \(r\) 个字符形成的连续子串的“最终胜者”,\(1 \le l \ le r\)。
定义 \(x>y\) 为 \(x\) 手势可以赢 \(y\) 手势。
-
一段连续的手势可以只留下一个,不影响结果。
-
如果 \(a_i<a_{i-1},a_i<a_{i+1}\),可以只留下 \(a_{i-1}\),不影响结果。
考虑全局询问。
使用栈维护这一操作。栈内元素 \(s_{top} < s_{top - 1} < \cdots < s_1\)。
若当前要加入 \(a_i\) 元素,则 \(s_{top} = a_{i-1}\)。
-
如果 \(a_i = a_{i-1}\),那么不加入当前元素。
-
如果 \(a_i < a_{i-1}\),加入当前元素。
-
如果 \(a_i > a_{i-1}\),由于 \(s_{top - 1} > s_{top}\),可以只留下 \(s_{top-1}\),弹栈。
如果栈中没有元素则加入当前元素。最终答案为栈底元素。
设 \(f_i\) 表示加入 \(a_i\) 后栈内的元素个数。
答案也就是最后的 \(f_i = 1\) 的 \(a_i\)。
其实也可以把 \(\max\) 去掉,就是 \(f_i\) 最小的 \(a_i\)。
对于区间询问也是如此,找区间中 \(f_i\) 最小的元素。可以使用线段树区间修改,区间求最小值维护。
*D
弱化版:CF1874F *3500
题目描述
给定一个序列 \(m_1,m_2,\cdots,m_n\),对于每个 \(1\le k \le \frac {n(n+1)} 2\),求满足以下条件的 \([1,2,...,n]\) 的排列 \(p\) 个数:
-
对于每个 \(l \le r \le m_l\),区间 \([p_l,p_{l+1},\cdots,p_r]\) 不是 \([l,l+1,\cdots,r]\) 的一个排列。
-
恰有 \(k\) 组 \(1 \le l \le r \le n\),使得区间 \([p_l,p_{l+1},\cdots,p_r]\) 是 \([l,l+1,\cdots,r]\) 的一个排列。
注意 \([p_1,p_2,\cdots,p_n]\) 始终是 \([1,2,\cdots,n]\) 的排列,\(m\) 序列不一定递增。
答案对 \(10^9+7\) 取模。
\(1 \le n \le 40\)。
定义合法区间为区间 \([p_l,p_{l+1},\cdots,p_r]\) 是 \([l,l+1,\cdots,r]\) 的一个排列。
定义本题中的本原连续段为不存在与之相交且不包含的合法区间。
合点的儿子一定是把它分成了若干段,且每个儿子都是析点。
析点可能没有儿子,或者若干区间之间不相邻,且 \(l,r\) 处没有区间儿子,\(l,r\) 处只能是叶子儿子。
设 \(A_n\) 为只有 \([1,n]\) 合法的排列个数。
证明:
P4566 证明
这个定义的是只有 \([1, n]\) 是连续段的方案数。
-
如果原排列满足条件,只要 \(n\) 不在 \(n-1\) 的右边就满足条件。
-
如果原排列不满足条件,一定有且仅有一个极长不合法的区间,设区间长度为 \(i\),区间值域不能包括 \(n-1\),否则插入 \(n\) 后这还是一个连续段。这样的区间值域有 \(n-i-1\) 种选择方法。
原排列中,只有这一个极长的不合法区间,设这个区间值域为 \([x, x + i - 1]\),看成一个点。那么原排列的方案数为 \(A_{n-i}\)。
把 \(n\) 插入这个极长区间,这个区间长度变为 \(i\),打破了之前排列的所有不合法区间。把 \(n\) 看成 \(i + 1\),原排列看成值域 \([1, i]\)。相当于原排列的所有连续段都跨过最大值的方案数。原排列的逆排列 \(b_{a_i} = i\)。原排列的连续段下标 \([i,j]\),值域 \([l,r]\),对应逆排列中连续段下标 \([l,r]\),值域 \([i,j]\),一一对应关系。由于 \(r = i + 1\),所以在逆排列中,每个连续段都是一个后缀。因此方案数为 \(A_i\)。
-
如果原排列是合法的,那么原排列 \(i \to a_i\) 连边,这些置换环不会占据一个区间。则新插入 \(n\) 在任意置换环的边上都合法。方案数 \((n - 1) A_{n - 1}\)。
-
如果原排列不合法。考虑原排列的析合树。因为新加入 \(n\) 只能打破一个不合法区间。所以 \([1,n]\) 的节点只能是析点,且析点只有一个区间节点。枚举这个区间节点的长度 \(i \in [1,n-3]\),值域有 \(n-i-2\) 种可能的取值。
这个区间的值域确定,那么所在位置确定。剩下的 \(n-i-1\) 个点需要不构成其他合法区间,方案数为 \(A_{n-i-1}\)。
这个区间长度为 \(i\),仿照 P4566 证明,考虑逆排列,相当于所有合法区间在逆排列上都过最后一点的方案数,方案数为 \(A_{i+1}\)。
建立本题的析合树,定义和原析合树定义不同。
合点的儿子一定是把它分成了若干段,且每个儿子都是析点。
析点可能没有儿子,或者若干区间之间不相邻,且 \(l,r\) 处没有区间儿子,\(l,r\) 处只能是叶子儿子。
设 \(f_{l,r,x,y}\) 表示将 \([l,r]\) 合并成一个合点,\([l,r]\) 内是 \([l,r]\) 的一个排列。只考虑 \([l,r]\) 的部分,共有 \(x\) 个合法区间,有 \(y\) 个儿子的方案数。
设 \(g_{l,r,x,y}\) 表示将 \([l,r]\) 合并成一个析点,\([l,r]\) 内是 \([l,r]\) 的一个排列。只考虑 \([l,r]\) 的部分,共有 \(x\) 个合法区间,有 \(y\) 个叶子儿子的方案数。
合点转移每次枚举最后一个析点。设原来有 \(v\) 个析点,新加的析点和之前的析点会新增加 \(v\) 个合法区间。合点最开始初始化为一个析点。
析点转移,相当于每次添加一个叶子或一个区间再加一个叶子。设有 \(v\) 个叶子,叶子间的排列方案数为 \(A_v\)。
这样的复杂度是 \(O(n^8)\) 的。使用拉格朗日插值优化 \(x\) 一维,复杂度 \(O(n^6)\)。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 41;
const int mod = 1e9 + 7;
int n, C[N], A[N], B[N], G[N][N], F[N][N], g[N][N][N], f[N][N][N], P[825][825];
int m, a[825], b[825], c[825], d[825], e[825], inv[825];
int Pow(int x, int y)
{
int r = 1;
for(; y; y >>= 1, x = x * x % mod)
if(y & 1)
r = r * x % mod;
return r;
}
signed main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
scanf("%lld", &n);
for(int i = 1; i <= n; ++ i)
scanf("%lld", &C[i]);
A[0] = A[1] = 1;
for(int i = 2; i <= n; ++ i)
{
A[i] = (A[i] + (i - 1) * A[i - 1]) % mod;
for(int j = 2; j <= i - 2; ++ j)
A[i] = (A[i] + (j - 1) * A[j] % mod * A[i - j]) % mod;
}
for(int i = 0; i <= n; ++ i)
B[i] = Pow(A[i], mod - 2);
for(int i = 1; i <= n * (n + 1) / 2 + 1; ++ i)
{
P[i][0] = 1;
for(int j = 1; j <= n * (n + 1) / 2 + 1; ++ j)
P[i][j] = P[i][j - 1] * i % mod;
}
for(int i = 1; i <= n * (n + 1) / 2 + 1; ++ i)
{
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
memset(F, 0, sizeof(F));
memset(G, 0, sizeof(G));
for(int x = 1; x <= n; ++ x)
{
g[x][x][1] = i;
if(C[x] < x)
G[x][x] = f[x][x][1] = i;
}
for(int len = 2; len <= n; ++ len)
{
for(int l = 1, r = len; r <= n; ++ l, ++ r)
{
if(r > C[l])
{
for(int v = 1; v <= len; ++ v)
{
for(int x = l + 1; x <= r; ++ x)
f[l][r][v] = (f[l][r][v] + f[l][x - 1][v - 1] * G[x][r] % mod * P[i][v - 1]) % mod;
F[l][r] = (F[l][r] + f[l][r][v]) % mod;
}
}
for(int v = 1; v <= len; ++ v)
{
for(int x = l + 1; x <= r; ++ x)
{
if(x == r)
g[l][r][v] = (g[l][r][v] + g[l][x - 1][v - 1] * B[v - 1] % mod * A[v]) % mod;
else
g[l][r][v] = (g[l][r][v] + g[l][x - 1][v - 1] * B[v - 1] % mod * A[v] % mod * (G[x][r - 1] + F[x][r - 1])) % mod;
}
if(r > C[l])
G[l][r] = (G[l][r] + g[l][r][v]) % mod;
}
f[l][r][1] = G[l][r];
}
}
a[i] = (F[1][n] + G[1][n]) % mod;
}
m = n * (n + 1) / 2 + 1;
inv[0] = inv[1] = 1;
for(int i = 2; i <= m; ++ i)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
for(int i = 1; i <= m; ++ i)
{
b[i] = a[i];
for(int j = 1; j < i; ++ j)
b[i] = b[i] * inv[i - j] % mod;
for(int j = i + 1; j <= m; ++ j)
b[i] = - b[i] * inv[j - i] % mod;
}
c[0] = 1;
for(int i = 1; i <= m; ++ i)
{
for(int j = i; j >= 1; -- j)
c[j] = (c[j - 1] - i * c[j]) % mod;
c[0] = - i * c[0] % mod;
}
for(int i = 1; i <= m; ++ i)
{
d[0] = - c[0] * inv[i] % mod;
for(int j = 1; j <= m; ++ j)
d[j] = (d[j - 1] - c[j]) % mod * inv[i] % mod;
for(int j = 0; j <= m; ++ j)
e[j] = (e[j] + d[j] * b[i]) % mod;
}
for(int i = 1; i <= n * (n + 1) / 2; ++ i)
printf("%lld ", (e[i] + mod) % mod);
printf("\n");
return 0;
}
10.6
A
题目描述
邻面合并
给定一个 \(n \times m\) 的网格,每个格子上有 \(0\) 和 \(1\)。用一些长方形覆盖 \(1\) 格子,不能重叠或者超过网格界限。问最少需要多少长方形。
\(n \le 100\),\(m \le 8\)。
反正不能直接需要分割就分割。要是大样例弱就不好了。\(m\) 小,直接做。
B
对于二维平面直角坐标系的题,一定要重点考虑 \(x=0\) 或 \(y=0\) 的情况。
*C
题目描述
百鸽笼
在 UOJ 管理员群里一共有 \(N\) 个管理员,为了容纳这些管理员,vfk 准备了 \(N + 1\) 个鸽笼。
为了节省空间,vfk 把这些鸽笼堆了起来,共有 \(n\) 列,第 \(i\) 列放了 \(a_i\) 个鸽笼,满足 \(\sum a_i = N+1\)。
每当 UR 结束,管理员们就会按照编号从小到大的顺序回到鸽笼里,每个管理员回来的时候,会先等概率的在所有还有剩余的鸽笼的列中随机一个列,然后住到这列剩下的鸽笼里编号最小的一个中。
现在 \(N\) 个管理员都回笼了之后,还有一列会空出一个鸽笼。你能对于每一列,求出这一列有空鸽笼的概率吗?
抛一个六面骰子,如果抛到 \(6\) 就再抛一次。和抛一个五面骰子,抛到 \(1,2,3,4,5\) 的概率是相同的。
那么,我们放满鸽子后还可以再放。这样的概率还是相同。
每次进那一列的鸽笼,就在操作序列后加入哪列鸽笼。操作序列长度为 \(N+1\),对于每一列,计算操作序列最后一位为这一列的总方案数,再除以总方案数。
考虑对 \(i\) 计算答案。容斥。设 \(g(S)\) 表示钦定 \(S\) 集合中这些列的鸽笼比 \(i\) 晚填满的概率。答案为 \(\sum_{S} (-1)^{\lvert S \rvert}g(S)\)。也就是说 \(S\) 中的数 \(j\) 在 \(i\) 最后一次出现前的出现次数 \(< a_j\)。
设 \(f_x\) 表示只考虑 \(S\) 中的数在 \(i\) 最后一次出现之前,在操作序列中填入 \(x\) 个数的方案数。每次转移乘组合数。最后考虑填入 \(i\)。
总方案数为 \((\lvert S \rvert + 1)^{x + a_i}\)。
这是背包形式,对于每个 \(i\) 撤销背包。
代码
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int mod = 998244353;
int n, sum, a[35], C[905][905], f[35][905], g[35][905];
int Pow(int x, int y)
{
int r = 1;
for(; y; y >>= 1, x = x * x % mod)
if(y & 1)
r = r * x % mod;
return r;
}
signed main()
{
scanf("%lld", &n);
for(int i = 1; i <= n; ++ i)
scanf("%lld", &a[i]), sum += a[i] - 1;
for(int i = 0; i <= 900; ++ i)
{
C[i][0] = 1;
for(int j = 1; j <= i; ++ j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
f[0][0] = 1;
for(int i = 1, num = 0; i <= n; ++ i)
{
num += a[i] - 1;
for(int j = i; j >= 1; -- j)
for(int k = 0; k <= num; ++ k)
for(int o = 0; o <= min(a[i] - 1, k); ++ o)
f[j][k] = (f[j][k] + f[j - 1][k - o] * C[k][o]) % mod;
}
for(int i = 1; i <= n; ++ i)
{
for(int j = 0; j <= n; ++ j)
for(int k = 0; k <= sum; ++ k)
g[j][k] = f[j][k];
for(int j = 1; j < n; ++ j)
for(int k = 0; k <= sum - a[i] + 1; ++ k)
for(int o = 0; o <= min(a[i] - 1, k); ++ o)
g[j][k] = (g[j][k] - g[j - 1][k - o] * C[k][o]) % mod;
int ans = 0;
for(int j = 0; j < n; ++ j)
{
for(int k = 0; k <= sum - a[i] + 1; ++ k)
{
if(j & 1)
ans = (ans - g[j][k] * C[k + a[i] - 1][k] % mod * Pow(Pow(j + 1, k + a[i]), mod - 2)) % mod;
else
ans = (ans + g[j][k] * C[k + a[i] - 1][k] % mod * Pow(Pow(j + 1, k + a[i]), mod - 2)) % mod;
}
}
printf("%lld ", (ans + mod) % mod);
}
printf("\n");
return 0;
}
*D
题目描述
滑稽树下你和我
我,年幼的人赢,总是和我的同桌一起在滑稽树下讨论问题。
我的滑稽树是一棵长在平面上的,有 \(n\) 个点的树。点从 \(1\) 到 \(n\) 标号。这 \(n\) 个点满足任意三点互不共线,并且树的边不相交。
现在我都和我的同桌一起在滑稽树上每人各控制一个点。我的点最开始在 \(\mathrm{stx}\),我的同桌的点最开始在 \(\mathrm{sty}\)。我们可以任意沿着树的边移动各自的点。当两个点都在树上的某个叶子的时候游戏就结束了。
现在,我想要最小化任意时刻我们两个所控制的点之间距离的最大值。请问你能帮我解决这个问题吗?注意某个时刻两个点可能都在某一条边上,这时候的距离也要统计入答案。
一句话题意:给定一棵平面上的树,两个点在树上任意移动,最小化从开始到两个点都到达叶子的任意时刻两个点直线距离的最大值。
\(3 \le n \le 1000\),\(0 \le x_i,y_i \le 10^6\)。
二分答案。把每条边建一个点。\(f_{x,y}\) 表示一个在 \(x\) 点,一个在 \(y\) 点的状态能不能到达。bfs 转移。
利用叉乘点乘计算点线距,线线距。
代码
#include <queue>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2005;
int n, m, sx, sy, f[N][N], ind[N];
double ans, dis[N][N];
struct Edge
{
int to, nex;
};
Edge e[N << 1];
int tot, head[N];
void add(int u, int v)
{
e[++ tot] = {v, head[u]};
head[u] = tot;
e[++ tot] = {u, head[v]};
head[v] = tot;
++ ind[u];
++ ind[v];
}
struct Point
{
double x, y;
};
Point a[N];
struct Vector
{
double x, y;
};
inline Vector operator - (Point i, Point j)
{
return (Vector){i.x - j.x, i.y - j.y};
}
inline double operator * (Vector i, Vector j)
{
return i.x * j.y - i.y * j.x;
}
inline double operator ^ (Vector i, Vector j)
{
return i.x * j.x + i.y * j.y;
}
inline double Dis(Point i, Point j)
{
return sqrt((i.x - j.x) * (i.x - j.x) + (i.y - j.y) * (i.y - j.y));
}
inline double Dis(Point i, Point j, Point k)
{
Vector a = i - j, b = k - j, c = i - k;
if((a ^ b) >= 0)
{
if((c ^ b) <= 0)
return abs(a * b) / sqrt(b.x * b.x + b.y * b.y);
else
return sqrt(c.x * c.x + c.y * c.y);
}
return sqrt(a.x * a.x + a.y * a.y);
}
bool check(double w)
{
queue < pair <int, int> > q;
memset(f, 0, sizeof(f));
f[sx][sy] = f[sy][sx] = 1;
q.push(make_pair(sx, sy));
while(!q.empty())
{
auto [u, v] = q.front();
q.pop();
for(int i = head[u], x; i; i = e[i].nex)
{
if(!f[x = e[i].to][v] && dis[x][v] <= w)
{
f[x][v] = f[v][x] = 1;
q.push(make_pair(x, v));
if(ind[x] == 1 && ind[v] == 1)
return 1;
}
}
for(int i = head[v], x; i; i = e[i].nex)
{
if(!f[x = e[i].to][u] && dis[x][u] <= w)
{
f[x][u] = f[u][x] = 1;
q.push(make_pair(x, u));
if(ind[x] == 1 && ind[u] == 1)
return 1;
}
}
}
return 0;
}
int main()
{
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
scanf("%d%d%d", &n, &sx, &sy);
for(int i = 1, x, y; i <= n; ++ i)
scanf("%d%d", &x, &y), a[i].x = x, a[i].y = y;
m = n;
for(int i = 1, u, v; i < n; ++ i)
{
scanf("%d%d", &u, &v);
a[++ m].x = u;
a[m].y = v;
add(m, u);
add(m, v);
}
for(int i = 1; i <= m; ++ i)
{
for(int j = i + 1; j <= m; ++ j)
{
if(i <= n && j <= n)
dis[i][j] = dis[j][i] = Dis(a[i], a[j]);
else if(i <= n && j > n)
dis[i][j] = dis[j][i] = Dis(a[i], a[(int)a[j].x], a[(int)a[j].y]);
else
dis[i][j] = dis[j][i] = min(Dis(a[(int)a[i].x], a[(int)a[j].x], a[(int)a[j].y]), Dis(a[(int)a[i].y], a[(int)a[j].x], a[(int)a[j].y])),
dis[i][j] = dis[j][i] = min({dis[i][j], Dis(a[(int)a[j].x], a[(int)a[i].x], a[(int)a[i].y]), Dis(a[(int)a[j].y], a[(int)a[i].x], a[(int)a[i].y])});
}
}
if(ind[sx] == 1 && ind[sy] == 1)
return printf("%.6lf\n", dis[sx][sy]), 0;
double l = dis[sx][sy], r = 1414223;
while(r - l >= 1e-7)
{
double mid = (l + r) / 2;
if(check(mid))
ans = mid, r = mid;
else
l = mid;
}
printf("%.6lf\n", ans);
return 0;
}
10.7
*D
题目描述
量子隧穿问题(experiment)
1935 年的秋天,薛定谔邀请巴甫洛夫观看他在盒子里对猫的实验。巴甫洛夫带着他的狗来了。
薛定谔有 \(n\) 个盒子摆成一排。有些盒子肯定包含猫,有些盒子肯定不包含猫,有些盒子可能包含也可能不包含猫。每个盒子只够容纳一只猫。每个盒子 \(i\) 有一个单向隧道 \(i \to b_i\)。如果盒子 \(b_i\) 为空,盒子 \(i\) 的猫可以移动到盒子 \(b_i\)。
当一个不速之客按响门铃时,巴甫洛夫的狗立即兴奋起来,开始奔跑和吠叫。狗从盒子 \(1\) 开始,奔向盒子 nn,依次从每个盒子旁边经过。当狗经过一个装有猫的盒子旁边时,那个盒子里的猫会受到惊吓。受惊的猫会检查该盒子的隧道,如果隧道的目标盒子是空的,则用隧道来逃跑,否则猫会留在其当前盒子里。注意,如果同一只猫移动到狗之后将要到达的盒子中,它可能会受到不止一次的惊吓。
设有 \(k\) 个未知的盒子,求 \(2k\) 种每个盒子是否包含猫的初始配置中,有多少种将导致最终最后一个盒子中有一只猫。
连边 \(i \to b_i\),是有向基环树。
考虑有向树怎么做。设 \(f_i\) 表示当前盒子中有猫的概率,最后答案为 \(f_n\) 乘上总方案数。
依次加边,加边前,两个连通块独立。计算加边后格子的概率即可。
但是会有环。找到环上第一个加入的边,枚举这条边两个点的状态,计算达到这个状态的概率和从这个状态继续计算的答案。
代码
#include <string>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 5005;
const int mod = 1e9 + 7;
int T, n, ans, pos, a[N], b[N], f[N], p[N];
char s[N];
int find(int x)
{
return x == f[x] ? x : f[x] = find(f[x]);
}
void work()
{
ans = pos = 0;
memset(b, 0, sizeof(b));
scanf("%lld", &n);
scanf("%s", s + 1);
for(int i = 1; i <= n; ++ i)
scanf("%lld", &a[i]), f[i] = i;
for(int i = 1, u, v; i <= n; ++ i)
if((u = find(i)) != (v = find(a[i])))
f[u] = v;
for(int i = 1; i <= n; ++ i)
if(find(i) == find(n))
b[i] = 1;
for(int i = 1; i <= n; ++ i)
f[i] = i;
for(int i = n, u, v; i >= 1; -- i)
{
if(b[i] && (u = find(i)) != (v = find(a[i])))
f[u] = v;
else if(b[i])
pos = i;
}
for(int x = 0; x <= 1; ++ x)
{
for(int y = 0; y <= 1; ++ y)
{
for(int i = 1; i <= n; ++ i)
{
if(s[i] == 'C')
p[i] = 1;
else if(s[i] == '.')
p[i] = 0;
else
p[i] = (mod + 1) / 2;
}
int res = 1;
for(int i = 1; i <= n; ++ i)
{
if(i == pos)
{
res = (x ? p[i] : (1 - p[i])) * (y ? p[a[i]] : 1 - p[a[i]]) % mod;
p[i] = x;
p[a[i]] = y;
}
int A = p[i], B = p[a[i]];
p[i] = A * B % mod;
p[a[i]] = (B + A * (1 - B)) % mod;
}
ans = (ans + res * p[n]) % mod;
}
}
for(int i = 1; i <= n; ++ i)
if(s[i] == '?')
ans = ans * 2 % mod;
printf("%lld\n", (ans + mod) % mod);
}
signed main()
{
freopen("experiment.in", "r", stdin);
freopen("experiment.out", "w", stdout);
scanf("%lld", &T);
while(T --)
work();
return 0;
}
10.8
A
题目描述
你可能知道欧几里得是一位数学家。莫里斯也知道,他想捉弄欧几里得,于是他给他托了这样的一个梦。
在欧几里得的噩梦中,他收到了一个包含 \(n\) 个长度为 \(m\) 的二进制数的集合。欧几里得可以求出任意子集的异或和。他想知道他能得到多少个数,并求出一个最小的子集,满足可以得到的集合和全集相同。输出数量和一个大小最小且编号字典序最小的子集。
每个二进制数最多有两个位置上为 \(1\)。
\(1\le n, m \le 5\times 10^5\)。
模仿线性基。线性基数组最多只有 \(2\) 个位置上为 \(1\)。如果插入只有一个位置上有 \(1\) 的数,会从高到低位,如果这一位为 \(1\),则异或线性基数组。如果线性基数组为 \(2^x+2^y(x>y)\),那么 \(x\) 向 \(y\) 连边。特别的,\(2^x\) 的值则 \(x\) 向 \(0\) 连边。相当于每次加入异或一条链。如果插入两个位置为 \(1\) 的数,相当于两位分别做以上操作。并查集维护即可。
B
\(a_i\) 为第 \(i\) 个节点的石头。设 \(f_i\) 表示 \(i\) 节点向上能拓展多少路径,\(s_i\) 表示 \(\sum_{j\in son_i} f_j\)。在节点 \(i\) 处,路径可以两两合并,可以继续向上。那么:
即
求出的 \(f\) 数组需要满足的条件为 \(0 \le f_u \le a_u\),\(f_v \le a_u\)。
C
题目描述
有一个商店,商店里卖 \(n\) 个商品,第 \(i\) 个的价格为 \(a_i\) 我们称一个正整数kk是美妙的,当且仅当我们可以在商店里选购若干个商品,使得价格之和落在区间 \([k,2k]\) 中。问:有多少个美妙的数。
\(n \le 10^5\),\(a_i \le 10^9\)。
一个价值为 \(x\) 的商品,会对 \(2k \in [x, 2x]\) 的 \(k\) 有贡献。
维护这些区间即可。
对于一个区间 \([a, b]\),\(b \ge 2a\),区间个数为 \(\log\) 级别。
D
题目描述
ants
贪玩的 dirty 又开始了他的第三个游戏。
dirty 抓来了 \(m\) 只蚂蚁,并且赋予每只蚂蚁不同的编号,编号从 \(1\) 到 \(n\),最开始,她们按某个顺序排成一列。现在 dirty 想要进行m场比赛,每场比赛给出 \(l\) 和 \(r\),表示选出从左向右数第 \(l\) 只至第 \(r\) 只蚂蚁。被选出的蚂蚁需要快速的按编号从小到大排序,之后这些蚂蚁中编号连续的蚂蚁将围成一个圈。每场比赛结束后,蚂蚁们还需要快速的回到最开始的位置。
按照蚂蚁的审美标准,围成的圈越大美观值就越大。于是 dirty 每次需要找到最大的圈,但由于比赛多到难以处理,他只需要知道这个圈中蚂蚁的数目。
回滚莫队,链表。维护每条链两端即可。
可以离线的题要想莫队。
10.9
B
区间本质不同子串个数。
\(n \le 3000\),\(q \le 20000\)。
可以 SAM \(O(nq)\) 做。
可以 Hash。对于一个子串,看它对哪些区间有贡献。二维差分前缀和。
C
题目描述
在一个遥远的魔法王国里,图书馆里藏有一本神秘的魔法书,这本书收录了 \(n\) 个魔法咒语。
小 Z 是魔法王国中的一员,他现在需要创造新的咒语,这些新咒语必须遵循以下两个规则之一:
- 新咒语必须是魔法书中已有的咒语。
- 新咒语可以由两部分组成,其中第一部分是魔法书中的一个咒语或者该咒语的非空开头部分(非空前缀),第二部分是魔法书中的一个咒语或者该咒语的非空结尾部分(非空后缀)。这两部分可以来自同一个咒语。
小 Z 想知道,按照这些规则,他们能创造出多少种不同的新咒语。
正串反串各建一个 Trie。对于一个可以合并成的新咒语,在最长前缀处统计贡献。即对于正串的 Trie 树上的点 \(u\),如果它没有字符为 \(c\) 的儿子,那么统计以 \(c\) 开头的后缀贡献。
注意单个字符,还有一处细节。
D
把模数分解成若干互质数。线段树维护以每个数开始最后的答案。CRT 合并。