2023 OI 集训营提高组第二场题解
T1 集合
从 1 到 中每个数可选可不选,共有
个非空子集。
现在考虑每个非空子集的和的贡献,子集和的取值范围是 ,可以
求每一种子集和的方案数,然后枚举子集和
,我们又知道子集和的方案数
,那么根据题意全部相乘就是
,需要用快速幂解决。
由于 较大的时候
数组会超过 long long 存储范围,而
数组是不能求余 998244353 的(它存储的内容的含义是
中的幂次
)注意到子集和与模数 998244353 一定是互质的,所以考虑欧拉定理或者费马小定理,对
数组求余
。
T2 出租
考虑什么时候是无解的。
当出现任意一段区间 的租户满足它们人数的和比
还多的时候,说明无论如何也无法给
中的所有人都安排房间。
我们对这个式子进行作差,得到 ,
就是当前位置的人数。可以这样理解:
这些人可以被分配到
这些位置,每个位置
个人,那么总共就能够装下
个人。将这个式子拆分成
,其中左边
是变量(因为我们不确定
的值,对本题来说,每一个
都需要满足要求),左边的值和
的已有租户人数作差,看看差值是否超过
,如果超过,则说明无法满足。
综上:用线段树维护最大子段和,然后和 比大小即可。
T3 连通块
80pt
枚举每个连通块在原树上深度最高的点
考虑一定包含深度的点最高为 的连通块,约定对于每个结点,其前戳为该点的 dfs 序,后戳为其子树中最大的 dfs 序,按 dfs 序标号,在
号点上有两种决策,要么选择该点转到
,要么割掉以
为根的子树,转到
的后戳 +1。
对每个点建立入点和出点,在它们之间连接权值为点权的边,将上述转移建图, 的出点连向
的入点,
的入点连向
的后戳 +1 的入点,权值都为 0。这样每一个连通块都对应了图上的一条从
出发的路径。
对于每个限制,约定 。特判掉两点连通时中间有其它点的情况,直接将断开
的出边向外连的边,从
到当前根的所有结点的儿子中,所有没有限制的结点
,从
的出点向
的出点连一条权值为
的边。
由于树是随机生成的,所以总结点数是 级别。
100 pt
表示表示子树的根为
且 dfs 序最后一个的是
的最大值
#include<bits/stdc++.h> #define rep(i,x,y) for(int i=x; i<=y; ++i) #define repd(i,x,y) for(int i=x; i>=y; --i) #define mid ((l+r)>>1) #define lch (rt<<1) #define rch (rt<<1|1) #define pb push_back using namespace std; typedef long long LL; const int N=100005,M=52; const LL INF=1e18; bool vis[N],mp[M][M]; int n,a[N],k,m,id[N]; LL ans,f[N][M]; struct D { int u,v; } dat[M]; vector <int> vt[N]; int getint() { char ch; int f=1; while(!isdigit(ch=getchar())) if(ch=='-') f=-1; int x=ch-48; while(isdigit(ch=getchar())) x=x*10+ch-48; return x*f; } void dfs(int x) { rep(i,0,k) f[x][i]=-INF; f[x][id[x]]=a[x]; for(auto v:vt[x]) { dfs(v); LL mx=-INF; rep(i,0,k) if(!mp[i][id[v]]) mx=max(mx,f[x][i]); rep(i,0,k) f[x][i]=max(f[x][i],f[v][i]+mx); } rep(i,0,k) ans=max(ans,f[x][i]); } int main() { cin >> n >> m; rep(i,1,n) scanf("%d", a + i); ans = a[1]; rep(i, 2, n) ans = max(ans, (LL)a[i]); if(ans<=0) return printf("%lld\n",ans),0; rep(i,1,n) { int x, k; scanf("%d", &k); while(k--) { scanf("%d", &x); vt[i].pb(x); } } rep(i,1,m) { int u, v; scanf("%d%d", &u, &v); dat[i]=(D) { u,v }; vis[u]=vis[v]=1; } rep(i,1,n) if(vis[i]) id[i]=++k; memset(vis,0,sizeof(vis)); rep(i,1,m) { int u = id[dat[i].u], v = id[dat[i].v]; mp[u][v] = mp[v][u] = 1; } dfs(1); printf("%lld\n",ans); }
T4 跳棋 (checkers)
对于 subtask1-2:
- 直接枚举每个
位置是否有棋子,然后记忆化搜索。
对于 subtask3:
对于 subtask4:
- 经过大眼观察法,你发现如果有两个贴贴的
,它们可以一起去往相邻任意空的位置,于是根据这一个变化规律大概可以推出只有一段 1 的变化情况。
对于 subtask5 :
- 经过超大眼观察法,你可以发现
变成
虽然是一个
跳过去,但是其实可以看作
和
换位置。
- 于是就直接
表示已经填了前
位,有
个
,
个
,且
是否可以在后面接一个
变成
。
- 最后对于每种
,把
两种情况加起来乘上
就好了。
- 解释一下
是怎么来的。 你发现一个 0 是无法跨过长度为奇数的 1 的连续段的。所以对于序列中的任意两个 0, 中间的 1 的奇数段的数量是一定的。