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 的奇数段的数量是一定的。