4.3 提高组模拟赛题解

天气之子 题解

人均过,无需题解。

马拉松 题解

题意

有一棵 \(n\) 个点的树,对于每条边,问树上包含这条边的最长路径长度。

5 分做法

观察第一组数据的性质,告诉我们这棵树呈星型,也就是说这棵树有一个中心节点,剩余所有节点都连在这个中心节点上。因此我们无论选择哪条边,包含这条边的最长路径就是这条边和另一条边,长度为 \(2\)。因此输出 \(n-1\)\(2\) 即可获得这 5 分。

10 分做法

观察第二组数据的性质,告诉我们这棵树呈链型。因此我们无论选择哪条边,包含这条边的最长路径就是这条链,长度为 \(n-1\)。因此输出 \(n-1\)\(n-1\) 即可获得这 \(5\) 分。

结合上一个解法,即可获得 10 分。

30 分做法

观察第三组数据,\(n\le 10^3\) 告诉我们可以实现一个 \(\mathcal{O}(n^2)\) 的做法。由于最长路径必须包含这条边,考虑在树上将这条边断开,这样会形成两棵树。以断开的这条边连接的两个节点为这两棵树的根节点,最长路径长度就是这两棵树的最大深度加 \(1\),考虑分别求取这两棵树的最大深度,使用 dfs 即可。每次搜索的时间复杂度为 \(\mathcal{O}(n)\),对于每条边都做一次,时间复杂度即为 \(\mathcal{O}(n^2)\)

结合 10 分做法,即可获得 30 分。

60 分做法

事实上我们可以将这个问题抽象为一个动态直径问题,初始时所有边的权值均为 \(1\),如果这条边需要包含在最长路径中,则将这条边的权值变为 \(n\),然后求这棵树的带权直径,带权直径减 \(n-1\) 即为答案。动态直径问题有单次修改与查询均为 \(\mathcal{O}(\log n)\) 的方法,因此对于树上每条边均修改两次(改为 \(n\) 一次,改回去一次)查询一次,时间复杂度为 \(\mathcal{O}(n\log n)\)

结合 10 分做法,即可获得 60 分。而由于此做法的空间常数过大,并不能通过全部数据。

满分做法

考虑 30 分做法,对于每条边,如果我们确定了这条边的方向(也就是知道了这条边两端的节点哪个是父节点,哪个是子节点),就相当于统计这条边以子节点为根的子树的最大深度加上以父节点为根的子树的最大深度再加 \(1\)。考虑换根 DP,首先统计以每个节点为根的子树的最大深度和次大深度,并记录这个最大深度经过哪个子节点。然后再 dfs 一遍,求出每个节点经过父节点到其他子树的深度最大值,利用之前统计的最大深度,次大深度和最大深度经过哪个子节点即可求出。时间复杂度 \(\mathcal{O}(n)\),足以通过全部数据。

#include <cstdio>
#include <vector>
#include <functional>

int main()
{
	int n;
	scanf("%d", &n);
	std::vector<int> fa(n);
	std::vector<std::pair<int, int>> edges(n - 1);
	std::vector<std::vector<int>> g(n);
	for (int i = 1; i < n; i++)
	{
		int a, b; scanf("%d %d", &a, &b); --a; --b;
		edges[i - 1] = {a, b};
		g[a].push_back(b); g[b].push_back(a);
	}
	std::vector<int> max_dep(n, 0), smax_dep(n, 0), fr(n);
	std::function<void(int, int)> dfs1 = [&](int x, int f)
	{
		fa[x] = f;
		for (int v : g[x])
			if (v != f)
			{
				dfs1(v, x);
				if (max_dep[v] + 1 >= max_dep[x])
				{
					smax_dep[x] = max_dep[x];
					max_dep[x] = max_dep[v] + 1;
					fr[x] = v;
				}
				else if (max_dep[v] + 1 > smax_dep[x])
					smax_dep[x] = max_dep[v] + 1;
			}
	};
	dfs1(0, -1);
	std::vector<int> max_dep_from_fa(n, 0);
	std::function<void(int, int)> dfs2 = [&](int x, int f)
	{
		for (int v : g[x])
			if (v != f)
			{
				if (fr[x] == v)
					max_dep_from_fa[v] = std::max(max_dep_from_fa[x], smax_dep[x]) + 1;
				else
					max_dep_from_fa[v] = std::max(max_dep_from_fa[x], max_dep[x]) + 1;
				dfs2(v, x);
			}
	};
	dfs2(0, -1);
	for (auto e : edges)
	{
		int a, b; std::tie(a, b) = e;
		if (fa[b] != a) std::swap(a, b);
		printf("%d\n", max_dep[b] + max_dep_from_fa[b]);
	}
	return 0;
}

maxmex 题解

显然 \(f(k)\) 是单调不减的, 我们对每个 \(v\) 考虑如何找到最大的 \(k\) 使得 \(f(k)<v\). 对 \(v\) 从小到大扫描, 考虑 \(v\) 所在的所有位置将序列分割成若干区间, 如果一个 区间不包含 \(v\), 也即它落在一个区间里.

那么 \(f(k)<v\) 就等价于说, 把所有 \(<v\) 的值所构成的这些区间拿出来, 对于 任何长度为 \(k\) 的区间, 都在某个区间之中.

在几何上看, 我们加入一个区间就是加一个矩形, 我们希望知道目前矩形的 并里可以放的最高的一条斜率为 1 的直线的高度, 那么我们只需要维护这些矩形 的并构成的 “边角”, 用一个堆来存, 就可以做到总时间 \(O(n \log n)\) 了.

#include<bits/stdc++.h>
using namespace std;
template <typename T> inline void read(T &x)
{
	x=0;short f=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
	for(;c>='0'&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	x*=f;return;
}
const int N=1e5+5;
int n,a[N];
vector<int>v[N];
struct node{
	int l,r;
	int val;
	int minx;
	int tag;
}tr[N*4];
void pushup(int u)
{
	tr[u].minx=min(tr[u<<1].minx,tr[u<<1|1].minx);
}
void pushtag(int u,int v)
{
	tr[u].tag=v;
	tr[u].val=v;
	tr[u].minx=tr[u].l-v+1;
} 
void pushdown(int u)
{
	if(tr[u].tag)
	{
		pushtag(u<<1,tr[u].tag);
		pushtag(u<<1|1,tr[u].tag);
		tr[u].tag=0;
	}
}
void build(int u,int l,int r)
{
	tr[u].l=l,tr[u].r=r,tr[u].minx=1,tr[u].tag=0;
	if(l==r) return tr[u].val=l,void();
	int mid=l+r>>1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
void modify(int u,int l,int r,int v)
{
	if(l<=tr[u].l&&tr[u].r<=r) return pushtag(u,v);
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(l<=mid) modify(u<<1,l,r,v);
	if(r>mid) modify(u<<1|1,l,r,v);
	pushup(u);
}
int query(int u,int l,int r)
{
	if(l<=tr[u].l&&tr[u].r<=r) return tr[u].minx;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1,res=n+1;
	if(l<=mid) res=min(res,query(u<<1,l,r));
	if(r>mid) res=min(res,query(u<<1|1,l,r));
	return res;
}
int querypos(int u,int pos)
{
	if(tr[u].l==tr[u].r) return tr[u].val;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(pos<=mid) return querypos(u<<1,pos);
	else return querypos(u<<1|1,pos); 
}
void modify(int l,int r)
{
	int L=l,R=r,ans=r+1;
	while(L<=R)
	{
		int mid=L+R>>1;
		if(querypos(1,mid)>=l) R=mid-1,ans=mid;
		else L=mid+1;
	}
	if(ans<=r) modify(1,ans,r,l);
}
int ans[N];
int main()
{
	read(n);
	for(int i=1;i<=n;++i) read(a[i]),v[a[i]].push_back(i);
	build(1,1,n);
	int lst=1;
	for(int i=0;i<=n;++i)
	{
		auto p=v[i];
		if(!p.size()) break;
		for(int i=1;i<p.size();++i) modify(p[i-1],p[i]-1);
		modify(p.back(),n);
		lst=max(lst,p[0]);
		ans[query(1,lst,n)]=i+1;
	}
	for(int i=1;i<=n;++i) ans[i]=max(ans[i],ans[i-1]);
	int q;read(q);
	while(q--)
	{
		int x;read(x);
		printf("%d\n",ans[x]);
	}
	return 0;
}

卷王 题解

卷王 题解

题意

\(n\times n\)矩阵,有两种操作,一个是把某个位置左下所有位置覆盖,一个是把右上所有位置覆盖,不能某个位置同时被两种操作覆盖,某些位置不能操作,求将矩阵覆盖的方案数

10 分做法

爆搜即可。

50 分做法

可以发现最后一定是这样一个形状

image-20241010084107870

于是可以考虑对这样一个折线 \(dp\)
\(f[i][j][k]\) 表示第\(i\)列,横折线在第 \(i\) 行,操作了 \(k\)
考虑转移即枚举第 \(i-1\) 列折线,可以仍横在第 \(i\) 行,也可能在 \(1\dots i-1\) 行,然后往下走到第 \(i\) 行再向右。
做个前缀和统计答案即可。

满分做法

发现 \(k\) 其实只影响最后统计答案即除了拐角外的 \(tot-k\) 个操作地方都可以操作或不操作,乘\(2^{tot-k}\) 的方案数。

于是可以算在 \(dp\) 过程中,\(k\) 每增加 \(dp\) 值除 \(2\) ,就可以消去 \(k\) 这维,复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
#define cs const
#define re register
#define pb push_back
#define pii pair<int,int>
#define ll long long
#define y1 shinkle
#define fi first
#define se second
#define bg begin
cs int RLEN=1<<20|1;
inline char gc(){return getchar();
}
inline int read(){
    char ch=gc();
    int res=0;bool f=1;
    while(!isdigit(ch))f^=ch=='-',ch=gc();
    while(isdigit(ch))res=(res+(res<<2)<<1)+(ch^48),ch=gc();
    return f?res:-res;
}
inline ll readll(){
    char ch=gc();
    ll res=0;bool f=1;
    while(!isdigit(ch))f^=ch=='-',ch=gc();
    while(isdigit(ch))res=(res+(res<<2)<<1)+(ch^48),ch=gc();
    return f?res:-res;
}
inline int readstring(char *s){
	int top=0;char ch=gc();
	while(isspace(ch))ch=gc();
	while(!isspace(ch)&&ch!=EOF)s[++top]=ch,ch=gc();
	s[top+1]='\0';return top;
}
template<typename tp>inline void chemx(tp &a,tp b){a=max(a,b);}
template<typename tp>inline void chemn(tp &a,tp b){a=min(a,b);}
cs int mod=998244353;
inline int add(int a,int b){return (a+b)>=mod?(a+b-mod):(a+b);}
inline int dec(int a,int b){return (a<b)?(a-b+mod):(a-b);}
inline int mul(int a,int b){static ll r;r=(ll)a*b;return (r>=mod)?(r%mod):r;}
inline void Add(int &a,int b){a=(a+b)>=mod?(a+b-mod):(a+b);}
inline void Dec(int &a,int b){a=(a<b)?(a-b+mod):(a-b);}
inline void Mul(int &a,int b){static ll r;r=(ll)a*b;a=(r>=mod)?(r%mod):r;}
inline int ksm(int a,int b,int res=1){for(;b;b>>=1,Mul(a,a))(b&1)&&(Mul(res,a),1);return res;}
inline int Inv(int x){return ksm(x,mod-2);}
inline int fix(ll x){x%=mod;return (x<0)?x+mod:x;}
cs int N=2005,iv=Inv(2),pv=mul(iv,iv);
int f[N],g[N];
char str[N][N];
int n,cnt;
int main(){
	n=read();
	for(int i=n;i;i--)readstring(str[i]);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)cnt+=(str[i][j]!='W');
	int v=ksm(2,cnt);
	for(int i=0;i<n;i++)if(str[i+1][1]!='W')f[i]=mul(v,iv);
	f[n]=v;
	for(int t=2;t<=n;t++){
		memcpy(g,f,sizeof(f));
		int s=0;
		for(int i=n;~i;i--){
			if(str[i+1][t]!='W'&&(t!=n||str[i][t]!='W'))Add(f[i],s);
			if(str[i][t-1]!='W')Add(s,mul(pv,g[i]));
		}
	}
	int res=0;
	for(int i=0;i<=n;i++)if(str[i][n]!='W'){
		Add(res,mul(f[i],(i==0)?1:iv));
	}cout<<res<<'\n';
	return 0;
}
posted @ 2025-04-03 22:43  sapo1o  阅读(51)  评论(0)    收藏  举报