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.inphantasm/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 的符合条件的树上路径存在。

【提示】

本题的输入数据可能很大,请避免使用过于缓慢的读入方式。

题解

首先把那个点数化成长度,然后不同的长度拆分就是不同的方案。

那么答案就是有下界的拆分,直接套公式(或者像我一样手动构造)即可。

\[ans=\binom{(n-1)-(m-1)(k-1)-1}{(m-1)-1} \mod 2 \]

使用卢卡斯定理即可。时间复杂度\(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 间最短路径的长度,并且会产生 cucv. . . . (可能为负)。总成本等于运输成本与额外成本的和。

为了降低污染,在接下来的 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.inskylines/skylines2.ans

【样例 3】

见选手目录下的 skylines/skylines3.inskylines/skylines3.ans

【样例 4】

见选手目录下的 skylines/skylines4.inskylines/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, xn, 1 ≤ w ≤ 5000, 1 ≤ ci ≤ 108

题解

显然这是一道离线的问题。

开始像“情报中心”那样推式子

\[ans_u=\min_{v \neq u} \{ dist_{u,v} + c_u - c_v \}\\ =\min_{v \neq u} \{ dep_u+dep_v-2dep_{lca} + c_u -c_v \} \]

确定一个根后,能作为\(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}\) 其中 ab 互质。输出整数 x 使得 bxa (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.inkiseki/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, mn ≤ 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 ​的范围还有这个模数肯定是骗人去想多项式。

posted on 2019-08-17 09:14  autoint  阅读(416)  评论(0编辑  收藏  举报

导航