test20190816 NOIP2019 模拟赛
100+100+20=220,T3吐槽:整个考室没有一个人正确地理解了题意。
树上路径(phantasm)
Akari 的学校的校门前生长着一排 n 棵树,从西向东依次编号为 1 ∼ n。相邻两棵树间的距离 都是 1。
Akari 上课的教学楼恰好在树 1 旁,所以每个课间,Akari 都很想走出教室,上树活动。Akari会依次经过 m 棵树,从树 1 一路向东跳到树 n。临近上课时,Akari 会再次上树,经过 m 棵树从树n 一路向西跳到树 1 ,准备上课。由于 Akari 睡眠很充足,Akari 每次跳跃至少会移动 k 的距离, 因此 Akari 在上树前需要合理规划她的跳跃路线。我们称每次上树过程中 Akari 跳过的全部 m 棵 树(包含树 1 和树 n)的集合为一条树上路径。
Akari 喜欢按不同的顺序观察各种树木,因此她每次上树时选择的树上路径不会与之前选择过 的重复。这意味着,Akari 不会选择之前的课间选过的树上路径,且在从树 n 跳回树 1 时,也不会 沿这次跳到树 n 的树上路径原路返回。
如果一次课间开始时,Akari 找不到符合条件的树上路径,那么她从此会放弃上树活动,开始 专心学习。如果一次课间即将即将结束时,Akari 还在树 n 且找不到符合条件的树上路径回到树 1, 她就会十分沮丧,选择逃课。
请你帮助 Akari 判断,她是否会在某个课间选择逃课。
【输入格式】
从文件 phantasm.in 中读入数据。 每个测试点可能包含多组数据。第一行一个正整数 T ,表示数据组数。 每组数据包括一行三个正整数 n, m, k,含义见题目描述。
【输出格式】
输出到文件 phantasm.out 中。
对于每组数据,输出一行一个字符串。如果 Akari 会逃课,输出 Yes ,否则输出 No 。
请. 注. 意. 输. 出. 字. 符. 串. 的. 大. 小. 写. 。
【样例 1 输入】
3
10 3 2
5 3 1
15 5 3
【样例 1 输出】
No Yes No
【样例 1 解释】
第一组数据中,除了起点和终点外,合法的树上路径只能经过 3, 4, 5, 6, 7, 8 这 6 棵树,所以合 法的树上路径只有 6 种,经过 3 个课间后 Akari 就会停止上树活动。
第二组数据中,合法的树上路径有 3 种,Akari 会在第 2 个课间结束时逃课。 第三组数据中,合法的树上路径有 10 种。
【样例 2 输入】
4
15 | 4 | 3 |
---|---|---|
15 | 4 | 4 |
15 | 5 | 3 |
16 | 3 | 7 |
【样例 2 输出】
Yes No No No
【样例 3】
见选手目录下的 phantasm/phantasm3.in 与 phantasm/phantasm3.ans。
【子任务】
测试点 | n | m | k | T |
---|---|---|---|---|
1 | ≤ 10 | ≤ 10 | ≤ 10 | ≤ 10 |
2 | ≤ 16 | ≤ 16 | ≤ 16 | ≤ 102 |
3 | ||||
4 | ≤ 18 | ≤ 18 | ≤ 18 | |
5 | ||||
6 | ||||
7 | ≤ 5, 000 | ≤ 5, 000 | ≤ 1 | ≤ 5, 000 |
8 | ≤ 3 | ≤ 5, 000 | ||
9 | ≤ 5, 000 | ≤ 2 | ||
10 | ≤ 200 | ≤ 200 | ≤ 200 | ≤ 200 |
11 | ||||
12 | ≤ 5, 000 | |||
13 | ||||
14 | ≤ 5, 000 | ≤ 5, 000 | ≤ 5, 000 | |
15 | ||||
16 | ≤ 2 × 106 | |||
17 | ||||
18 | ||||
19 | ≤ 109 | ≤ 109 | ≤ 109 | |
20 |
对于所有数据,2 ≤ n, m ≤ 109 , 1 ≤ k ≤ 109 , 0 ≤ T ≤ 2 × 106 ,保证第一个课间 Akari 从树 1 跳到
树 n 的符合条件的树上路径存在。
【提示】
本题的输入数据可能很大,请避免使用过于缓慢的读入方式。
题解
首先把那个点数化成长度,然后不同的长度拆分就是不同的方案。
那么答案就是有下界的拆分,直接套公式(或者像我一样手动构造)即可。
使用卢卡斯定理即可。时间复杂度\(O(T \log v)\)。
bool binom(LL n,LL m){
if(n<m) return 0;
if(m==0) return 1;
return (n&1)>=(m&1) && binom(n>>1,m>>1);
}
int main(){
freopen("phantasm.in","r",stdin),freopen("phantasm.out","w",stdout);
for(int T=read<int>();T--;){
LL n=read<LL>(),m=read<LL>(),k=read<LL>();
puts(binom(n-1-(k-1)*(m-1)-1,m-1-1)?"Yes":"No");
}
return 0;
}
利用二进制的优良性质,可以将单次操作化为\(O(1)\)的,参见std
int ks = io;
while (ks--) {
int n = io, m = io, k = io;
io << ((((n - 2 - (m - 1) * (k - 1)) & (m - 2)) == m - 2) ? "Yes\n" : "No\n");
}
话说回来,我好像不会证明卢卡斯定理,放一个图
非常的有道理啊。
另外既然我都看到\((1+x)^p \equiv x^p + 1 \mod p\),那我就说一下今天讨论出来的多项式快速幂做法。
若保证\(f_0=1\),则\(f^k(x)=f^{k\mod p}(x)\mod x^n\)。
证明大概就是有组合数的项因为卢卡斯定理变成\(0\)了,而剩下的是\(x^{kp},k=1\sim n-1\)和\(1\)。但是我们要对\(x^n\)取模,所以那些有\(x\)的项都被消掉了。所以\(f^p(x)=1 \mod x^n\)。
泳池(skylines)
【题目描述】
小 A 的城市里有 n 座工厂,编号分别为 1 ∼ n。工厂间连有 n − 1 条双. 向. 管道,形成一个无向连. 通. 图,其中每条管道都有一定的长度,连接在两座不同的工厂间。
每座工厂都装有废水处理设施,工厂 i 的蓄水量记为 ci 。由于工厂规模有限,工厂产生的废水必须经由管道输送到另. 一座工厂进行处理。
工厂 u 将废水输送到工厂 v 处理时,所需的运. 输. 成. 本. 等于无向图中 u, v 间最短路径的长度,并且会产生 cu − cv 的额. 外. 成. 本. (可能为负)。总成本等于运输成本与额外成本的和。
为了降低污染,在接下来的 q 天内,每一天只有一座工厂会产生废水。你需要确定这座工厂将废水输送到哪一座工厂进行处理,可使得总成本最小。由于选择可能不唯一,你只需输出最小的总 成本。
【输入格式】
从文件 skylines.in 中读入数据。 第一行一个正整数 n。
第二行 n 个正整数 ci 。
下接 n − 1 行,每行三个正整数 u, v, w,表示一条双向管道两端工厂的编号及长度。 第 n + 2 行一个正整数 q。
下接 q 行,每行一个正整数 x,表示这一天进行生产的工厂的编号。
【输出格式】
输出到文件 skylines.out 中。
输出 q 行,每行一个整数,表示这一天总成本的最小值。
【样例 1 输入】
5
7 | 7 | 6 9 9 |
---|---|---|
2 | 5 | 5 |
2 | 3 | 1 |
4 | 1 | 1 |
1 | 2 | 2 |
4
2
5
3
4
【样例 1 输出】
1
7
0
3
【样例 1 解释】
第 1 天,工厂 2 输送到工厂 4 是一种最优方案,成本为 3 + (−2) = 1。
第 2 天,工厂 5 输送到工厂 2 是一种最优方案,成本为 5 + 2 = 7。
第 3 天,工厂 3 输送到工厂 2 是一种最优方案,成本为 1 + (−1) = 0。
第 4 天,工厂 4 输送到工厂 1 是一种最优方案,成本为 1 + 2 = 3。
【样例 2】
见选手目录下的 skylines/skylines2.in 与 skylines/skylines2.ans。
【样例 3】
见选手目录下的 skylines/skylines3.in 与 skylines/skylines3.ans。
【样例 4】
见选手目录下的 skylines/skylines4.in 与 skylines/skylines4.ans。
【子任务】
测试点 | n, q | 链 |
---|---|---|
1 | ≤ 10 | 否 |
2 | ≤ 200 | |
3 | ≤ 2, 000 | 是 |
4 | ||
5 | 否 | |
6 | ||
7 | ≤ 2 × 105 | 是 |
8 | ||
9 | 否 | |
10 |
对于表格中“链”为“是”的数据,保证所有管道满足 v = u + 1 且以 u 递增的顺序输入。
对于所有数据,2 ≤ n ≤ 2 × 105 , 1 ≤ q ≤ 2 × 105 , 1 ≤ u, v, x ≤ n, 1 ≤ w ≤ 5000, 1 ≤ ci ≤ 108 。
题解
显然这是一道离线的问题。
开始像“情报中心”那样推式子
确定一个根后,能作为\(u\)的\(lca\)的只可能是\(u\)的祖先,所以这就变成了统计子树内部\(dep_v-c_v\)的睿智题。注意支持换根需要记录最优次优解,或者像我一样记录儿子的值来搞序列问题。
时间复杂度\(O(n+q)\)。
co int N=200000+10;
co LL INF=1e18;
int n;LL c[N];
vector<int> to[N];vector<LL> we[N];
LL dep[N],g[N],f[N]; // g: x itself not included
vector<LL> son[N],pre[N],suf[N];
void dfs1(int x,int fa){
f[x]=dep[x]-c[x],g[x]=INF;
son[x].resize(to[x].size()); // edit 1: fa
for(rg int i=0;i<(int)to[x].size();++i){
int y=to[x][i],w=we[x][i];
if(y==fa){
son[x][i]=INF;
continue;
}
dep[y]=dep[x]+w;
dfs1(y,x);
g[x]=min(g[x],f[y]);
son[x][i]=f[y];
}
f[x]=min(f[x],g[x]);
pre[x].resize(son[x].size()),suf[x].resize(son[x].size());
for(rg int i=0;i<(int)son[x].size();++i){
pre[x][i]=son[x][i];
if(i>0) pre[x][i]=min(pre[x][i],pre[x][i-1]);
}
for(rg int i=(int)son[x].size()-1;i>=0;--i){
suf[x][i]=son[x][i];
if(i<(int)son[x].size()-1) suf[x][i]=min(suf[x][i],suf[x][i+1]);
}
}
LL ans[N];
void dfs2(int x,int fa,LL val){
ans[x]=dep[x]+c[x]+min(g[x]-2LL*dep[x],val);
for(rg int i=0;i<(int)to[x].size();++i){
int y=to[x][i];
if(y==fa) continue;
LL nval=dep[x]-c[x]-2LL*dep[x];
if(i>0) nval=min(nval,pre[x][i-1]-2LL*dep[x]);
if(i<(int)to[x].size()-1) nval=min(nval,suf[x][i+1]-2LL*dep[x]);
dfs2(y,x,min(val,nval));
}
}
int main(){
freopen("skylines.in","r",stdin),freopen("skylines.out","w",stdout);
read(n);
for(rg int i=1;i<=n;++i) read(c[i]);
for(rg int i=1;i<n;++i){
int u=read<int>(),v=read<int>();LL w=read<LL>();
to[u].push_back(v),we[u].push_back(w);
to[v].push_back(u),we[v].push_back(w);
}
dfs1(1,0);
dfs2(1,0,INF);
for(rg int q=read<int>();q--;) printf("%lld\n",ans[read<int>()]);
// cerr<<clock()<<endl;
return 0;
}
其他做法
刘老爷的点分治:还是那个式子,点分暴力做得了。时间复杂度\(O(n\log n+q)\)。
黄学长的换根+线段树:线段树可以支持相邻两个点换根。时间复杂度\(O(n\log n+q)\)。
空之轨迹(kiseki)
【题目描述】
Iri 近日沉迷游戏《英雄传说 VI 空之轨迹》。该游戏共有 n 个章节,第 i 章中有 ai 次战斗。当 第 i 章通关后,游戏会自动存档,然后自动进入第 i + 1 章,且不允许回到之前的章节(除非读档)。
由于 Iri 的游戏设备年久失修,这天早上 Iri 进入游戏时,发现他的所有存档都消失了,只留下初始的一个序章存档(加载后会开始第 1 章)。不幸的是,游戏的章节切换系统也出现了 Bug,在每一章结束的自动存档之后,游戏会从已有的所有存档(包括序章存档)中等概率随机选取一个加载。由于 Iri 的耐心与精力有限,加载序章存档后,他只会连续玩 m 个章节,然后更换新的设备。 需要注意的是,游戏的存档系统没有损坏,即当第 i 章结束后的自动存档被加载后,一定会开始第 i + 1 章。
现在 Iri 想知道,这 m 章内能进行的战斗总次数的期望值。Iri 觉得这个问题太简单了,所以就 把它交给了你。由于 Iri 的游戏技术同样出神入化,你可以认为所有章节他都会一次通关。
【输入格式】
从文件 kiseki.in 中读入数据。 第一行包含两个正整数 n, m. 第二行 n 个非负整数 ai .
【输出格式】
输出到文件 kiseki.out 中。
输出一行一个整数,表示 Iri 进行的战斗总次数的期望值在模 998244353 意义下的值。 即设答案化为最简分式后的形式为 \(\frac{a}{b}\) 其中 a 和 b 互质。输出整数 x 使得 bx ≡ a (mod 998244353) 且 0 ≤ x < 998244353。可以证明这样的整数 x 是唯一的。
【样例 1 输入】
3 2
1 2 1
【样例 1 输出】
499122179
【样例 1 解释】
答案是 \(\frac 52\) 。由于 499122179 × 2 mod 998244353 = 5,所以你输出 499122179。
【样例 2 输入】
3 3
5 5 1
【样例 2 输出】
332748132
【样例 3 输入】
15 10
2 8 6 3 2 6 3 5 9 2 3 4 5 1 6
【样例 3 输出】
653958763
【样例 4】
见选手目录下的 kiseki/kiseki4.in 与 kiseki/kiseki4.ans。
【子任务】
测试点 | n | m | 性质 1 |
---|---|---|---|
1 | ≤ 10 | ≤ 10 | 否 |
2 | |||
3 | ≤ 105 | ≤ 11 | |
4 | |||
5 | |||
6 | |||
7 | ≤ 21 | 是 | |
8 | |||
9 | |||
10 | |||
11 | ≤ 18 | 否 | |
12 | |||
13 | |||
14 | |||
15 | ≤ 21 | ||
16 | |||
17 | |||
18 | |||
19 | |||
20 |
对于具有性质 1 的数据,保证所有的 ai 都相等。
对于所有数据,保证 1 ≤ m ≤ 21, m ≤ n ≤ 105 , 0 ≤ ai ≤ 10.
【提示】
xp−1 ≡ 1 (mod p),其中 p 是质数,x 是 [1, p) 上的整数。
题解
首先说一下题意了。如果玩到之前玩过的关卡,那么会对下一个关卡又建一个存档,也就是说选到下一个关卡的概率会增大。看一下简化版的题意:
随机生成一个 m + 1 个数的数列,第一个数为 0, 生成第 i 个数时,在前 i − 1 个数中等概率选择一个数 k, 则第 i 个数为 k + 1。每个数均有一个对应的权值,求数列权值和的期望。
一个数列的权值和与数列中数的顺序无关,只与每种数的个数有关。
考虑 DP,状态中需要记录每种数的个数。可以发现,将得到的数列排序后,相邻两数的差只能为 \(0\) 或 \(1\)。
用二进制序列维护原数列的差分数组,即可表示数列中每种数的个数。
\(f(i, S)\) 表示生成了 \(i\) 个数,已有数列的差分为 \(S\) 的方案数。
\(S\)会如何变化呢?只能往\(S\)的末尾添\(1\),但是\(0\)可以随便加。所以\(S\)变化与它的加入位置有关。
则每种数列出现的概率 \(P(S) = \frac {f(m,S)}{m!}\)。
事先预处理出 \(tot_S\) 表示状态 \(S\) 所表示的数列的权值和, 最后的答案即为\(\sum_S P(S) tot_S\)。
复杂度:\(O(m · 2^m)\)。注意做除法时用乘法逆元计算。
说下状态的意思,状态的前两位是固定的01,表示序章存档和第一关存档,我们只需要记录后面的状态就行了。每次的转移的意思是跟简化版题意是一样。然后DP直接算概率,不是方案数。
#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
T x=0;char c=getchar();
while(!isdigit(c)) c=getchar();
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}
template<class T> il T read(T&x){
return x=read<T>();
}
using namespace std;
typedef long long LL;
co int mod=998244353;
il int add(int a,int b){
return (a+=b)>=mod?a-mod:a;
}
il int mul(int a,int b){
return (LL)a*b%mod;
}
co int N=30,S=1<<22;
int n,a[N],inv[N];
int f[S],g[S],c[N],p[N];
int main(){
freopen("kiseki.in","r",stdin),freopen("kiseki.out","w",stdout);
read<int>(),read(n);
for(int i=1;i<=n;++i) read(a[i]);
inv[0]=inv[1]=1;
for(int i=2;i<=n;++i) inv[i]=mul(mod-mod/i,inv[mod%i]);
f[0]=1,c[0]=1;
for(int i=2;i<=n;++i){
for(int s=0;s<1<<(i-2);++s){
int cur=1;
c[1]=1;
for(int j=0;j<i-2;++j){
if(s>>j&1) c[++cur]=1,p[cur]=j;
else ++c[cur];
}
for(int j=0;j<=cur;++j){
int t;
if(j==0) t=s<<1;
else if(j==cur) t=s|1<<(i-2);
else{
int h=s&((2<<p[j+1])-1);
t=(s^h)<<1|h;
}
g[t]=add(g[t],mul(c[j],mul(inv[i],f[s])));
}
f[s]=0;
}
swap(f,g);
}
int ans=0;
for(int s=0;s<1<<(n-1);++s){
int cur=1,sum=a[1];
for(int j=0;j<n-1;++j){
if(s>>j&1) ++cur;
sum+=a[cur];
}
ans=add(ans,mul(sum,f[s]));
}
printf("%d\n",ans);
return 0;
}
那个 n 的范围还有这个模数肯定是骗人去想多项式。