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

查看7道真题和解析