NOIP2024 题解

NOIP 2024 题解

各题的 AC 代码放在了文末。

T1

首先对于两个串都不能动的位置,直接统计是否相等。

对于连续的一段能动的位置,这一段的数可以随便交换,可以预处理每个位置属于哪一段,以及这一段中 0 和 1 的个数。

我们贪心地考虑,优先匹配一个串能动,另一个串不能动的位置。可以感受到,先把不能动的位置匹配掉后,剩下的位置是两个串都可以随便移动的,剩下的位置限制会更松,于是这样是比较优的。

最后处理都能动的位置,我们贪心地能匹配就匹配,因为匹配和失配的贡献都是 1,所以与匹配的顺序无关。

时间复杂度 O ( T n ) O(Tn) O(Tn)

T2

按照确定的点分段,考虑到每一段都是独立的。

考虑求每一段合法的方案数,正难则反,用总数减去不合法的方案数。

不合法的方案数就是设左边值为 x x x,右边为 y y y,构造一种条件形如当左边为 x x x 时右边不为 y y y

这就要求这一段前后连起来,且最后一个值不为 y y y,大概就是:
V l e n × ( V − 1 ) V^{len}\times (V-1) Vlen×(V1)
再考虑开头的和结尾的两段,发现怎么取都是合法的。

我们需要快速幂,时间复杂度 O ( T m log ⁡ n ) O(Tm\log n) O(Tmlogn)

T3

前言

怎么了呢?是不敢面对自己的分数吗,到了元旦才想起来要改这道题。

赛时想到了对每个叶子间的路径计数,如果路径上有关键边那么就是可计数的,对于路径上的方案计数,同时对每个挂在路径上的树的方案计数。可惜我的式子是一个 DP 的形式,我并没有想到式子是可以直接根据各节点的度数计算。


言归正传。

首先如果 k = 1 k=1 k=1,对于一个点它相邻的所有边,有一条初始边是确定了的,剩下的边连成了链,我们不用 DP 就可以计算答案其实是
∏ ( d i − 1 ) ! \prod (d_i-1)! (di1)!
其中 d i d_i di 表示点 i i i 的度数。

而如果 k > 1 k>1 k>1 这样做是会算重的。考虑怎样的生成树是合法的,我们考虑一个点相邻的所有边一定是连成一条链,而关键边一定在链的首尾所对的子树内。

我们称使生成树合法的关键边为有用的关键边,我们把有用的关键边取出来,那么它的两端生成树上连的边,会一直延伸到两个原树上的叶子节点。

我们可以对包含关键边的叶子节点间的路径计数,对于这条链上挂着的子树,和 k = 1 k=1 k=1 的分析是一致的。而对于链上的点,我们已经知道了与之相邻的边连成的链的首尾,而其他边还是任意排列的。所以对于一条两个叶子的路径,答案就是
∏ ( d i − 1 ) ! ∏ ( d v − 2 ) ! \prod (d_i-1)!\prod(d_v-2)! (di1)!(dv2)!
其中 i i i除路径外的点 v v v 为路径上的点,这等价于
∏ ( d i − 1 ) ! ∏ ( d v − 1 ) − 1 \prod (d_i-1)!\prod(d_v-1)^{-1} (di1)!(dv1)1
其中 i i i所有节点 v v v 是路径上的点。

对于 n = 2 n=2 n=2 特判,否则取一个非叶子节点作为根,每个子树内记录有关键边和没有关键边的乘积。

时间复杂度 O ( T n log ⁡ V ) O(Tn\log V) O(TnlogV),因为有快速幂。

T4

O ( Q n log ⁡ 2 n ) ∼ O ( Q n log ⁡ n ) O(Qn\log ^2n)\sim O(Qn\log n) O(Qnlog2n)O(Qnlogn)

考虑枚举长为 k k k 的段,求 LCA。
一段的 LCA 就是每次加一个点求 LCA,也可以是合并两个子区间的 LCA。

可以考虑线段树上合并区间 LCA,每次合并用倍增实现 O ( log ⁡ n ) O(\log n) O(logn),也可以在欧拉序上 O ( 1 ) O(1) O(1) 求 LCA。

O ( Q n + n log ⁡ 2 n ) ∼ O ( Q n + n log ⁡ n ) O(Qn+n\log ^2n)\sim O(Qn+n\log n) O(Qn+nlog2n)O(Qn+nlogn)

可以用 ST 表代替线段树,预处理 ST 表合并时可以倍增 O ( log ⁡ n ) O(\log n) O(logn) 也可以 O ( 1 ) O(1) O(1) 求 LCA。

另外,也可以求区间欧拉序的最小最大值,类似欧拉序两个点的 LCA,求 RMQ,这样也能做到 O ( n Q + n log ⁡ n ) O(nQ+n\log n) O(nQ+nlogn)

性质 B

上面的做法可以直接通过性质 B。

性质 A

考虑整体二分,就要实现一个数据结构,支持加入或删除一个点,然后查询区间内最长连续段的长度是否大于等于 K K K

可以用线段树实现,时间复杂度 O ( ( Q + n ) log ⁡ 2 n ) O((Q+n)\log ^2n) O((Q+n)log2n)

O ( ( Q + n ) log ⁡ 2 n ) O((Q+n)\log^2 n) O((Q+n)log2n)

如果有强大的观察能力或联想能力,可以发现一段区间的 LCA 的深度就是相邻两个点的 LCA 深度的最小值,即
min ⁡ { dep LCA ( i , i + 1 ) } \min\{\text{dep}_{\text{LCA}(i,i+1)}\} min{depLCA(i,i+1)}
证明就是,考虑这一段区间的 LCA,这段区间的点分布在 LCA 的不同子树内,则一定有 i , i + 1 i,i+1 i,i+1 跨在不同子树内。

于是就转化为了性质 A。

O ( ( Q + n ) log ⁡ n ) O((Q+n)\log n) O((Q+n)logn)

考虑对于每个点 i i i,设 s i = dep LCA ( i , i + 1 ) s_i=\text{dep}_{\text{LCA}(i,i+1)} si=depLCA(i,i+1),求出 min ⁡ l i ≤ j ≤ r i { s j } = s i \min_{l_i\le j\le r_i}\{s_j\}=s_i minlijri{sj}=si 的极大区间 ( l i , r i , s i ) (l_i,r_i,s_i) (li,ri,si)

设询问区间为 ( L , R ) (L,R) (L,R),就是求 ( l r , r i , s i ) (l_r,r_i,s_i) (lr,ri,si) ( L , R ) (L,R) (L,R) 的交大于等于 K K K 的区间的最大 s i s_i si

分类讨论,当 r i ≤ R r_i\le R riR 时,要满足
L + K − 1 ≤ r i ≤ R ∧ r i − l i + 1 ≥ K L+K-1\le r_i\le R \land r_i-l_i+1\ge K L+K1riRrili+1K
r i ≥ R r_i\ge R riR 时,要满足
l i ≤ R − K + 1 ∧ r i ≥ R l_i\le R-K+1\land r_i\ge R liRK+1riR
前者对 r i − l i + 1 , K r_i-l_i+1,K rili+1,K 扫描线,后者对 r i , R r_i,R ri,R 扫描线。

具体地,把做扫描线的量排序,然后双指针把另一个量加入数据结构中或在数据结构中查询。

前者 L + K − 1 ≤ r i ≤ R L+K-1\le r_i\le R L+K1riR 可以线段树,后者 l i ≤ R − K l_i\le R-K liRK 可以树状数组。

K = 1 K=1 K=1 时,需要特殊处理,直接用数据结构查区间最值。可选 ST 表。

时间复杂度 O ( ( Q + n ) log ⁡ n ) O((Q+n)\log n) O((Q+n)logn)


T1:

const int N=1e5+5;
char a[N],b[N],c[N],d[N];
int m1[N],m2[N];
int t1,t2;
int c1[N],c2[N],c3[N],c4[N];
signed main(){
// 	freopen("edit.in","r",stdin);
// 	freopen("edit.out","w",stdout);
	read(T);
	while(T--){
		read(n);
		scanf("%s%s%s%s",a+1,b+1,c+1,d+1);
		int ans=0;
		t1=1,t2=1;
		c1[1]=c2[1]=c3[1]=c4[1]=0;
		fo(i,1,n){
			if(c[i]=='1')m1[i]=t1,c1[t1]+=a[i]=='1',c2[t1]+=a[i]=='0';
			else ++t1,m1[i]=c1[t1]=c2[t1]=0;
			if(d[i]=='1')m2[i]=t2,c3[t2]+=b[i]=='1',c4[t2]+=b[i]=='0';
			else ++t2,m2[i]=c3[t2]=c4[t2]=0;
		}
		fo(i,1,n){
			int j=m1[i],k=m2[i];
			if(c[i]=='0'&&d[i]=='0')ans+=a[i]==b[i];
			else if(c[i]=='0'){
				if(a[i]=='0'&&c4[k])c4[k]--,ans++;
				if(a[i]=='1'&&c3[k])c3[k]--,ans++;
			}
			else if(d[i]=='0'){
				if(b[i]=='1'&&c1[j])c1[j]--,ans++;
				if(b[i]=='0'&&c2[j])c2[j]--,ans++;
			}
		}
		fo(i,1,n){
			int j=m1[i],k=m2[i];
			if(c[i]=='1'&&d[i]=='1'){
				if(c1[j]&&c3[k])c1[j]--,c3[k]--,ans++;
				else if(c2[j]&&c4[k])c2[j]--,c4[k]--,ans++;
			}
		}
		write(ans,'\n');
	}
	return 0;
}

T2:

signed main(){
// 	freopen("assign.in","r",stdin);
// 	freopen("assign.out","w",stdout);
	read(T);
	while(T--){
		read(n,m,V);
		fo(i,1,m){
			read(a[i].first,a[i].second);
		}
		sort(a+1,a+1+m);
		int flag=0;
		fo(i,1,m-1){
			if(a[i].first==a[i+1].first&&a[i].second!=a[i+1].second)flag=1;
		}
		if(flag){
			write("0\n");
			continue;
		}
		m=unique(a+1,a+1+m)-a-1;
		int ans=1;
		fo(i,1,m-1){
			int len=a[i+1].first-a[i].first;
			ans=(ll)ans*(pow1(V,2*len)-(ll)pow1(V,len-1)*(V-1)%mod)%mod;
		}
		ans=(ll)ans*pow1(V,2*(a[1].first-1))%mod*pow1(V,2*(n-a[m].first))%mod;
		write((ans+mod)%mod,'\n');
	}
	return 0;
}

T3:

const int mod=1e9+7;
const int N=1e5+5;
vp g[N];
int cid,T,n,K,key[N],deg[N];
int pow1(int x,int y){ 
	int res=1;
	for(;y;y>>=1,x=(ll)x*x%mod)if(y&1)res=(ll)res*x%mod;
	return res;
}
int sum,f1[N],f2[N];
void dfs(int x,int y) {
	int flag=0;
	f1[x]=f2[x]=0;
	int s1=0,s2=0;
	for(auto v:g[x]) {
		if(v.first==y) continue;
		flag=1;
		dfs(v.first,x);
		int t1=f1[v.first],t2=f2[v.first];
		if(key[v.second])t1=(t1+t2)%mod,t2=0;
		sum=(sum+(ll)s1*(t1+t2)%mod*pow1(deg[x]-1,mod-2))%mod;
		sum=(sum+(ll)s2*(t1)%mod*pow1(deg[x]-1,mod-2))%mod;
		s1=(s1+t1)%mod;
		s2=(s2+t2)%mod;
		f2[x]=(f2[x]+(ll)t2*pow1(deg[x]-1,mod-2))%mod;
		f1[x]=(f1[x]+(ll)t1*pow1(deg[x]-1,mod-2))%mod;
	}
	if(!flag) {
		f1[x]=0,f2[x]=1;
	}
}
int fac[N];
signed main(){
	usefile("traverse");
	read(cid,T);
	while(T--) {
		read(n,K);
		fo(i,1,n)deg[i]=0,g[i].clear();
		fu(i,1,n){
			int u,v; read(u,v);
			g[u].pb({v,i});
			g[v].pb({u,i});
			deg[u]++,deg[v]++;
			key[i]=0;
		}
		fo(i,1,K){
			int e; read(e);
			key[e]=1;
		}
		if(n==2) {write("1\n"); continue;};
		int prod=1;
		fac[0]=1;
		fo(i,1,n)fac[i]=(ll)fac[i-1]*i%mod;
		fo(i,1,n)prod=(ll)prod*fac[deg[i]-1]%mod;
		fo(i,1,n)if(deg[i]!=1) {
			sum=0;
			dfs(i,0);
			write((ll)prod*sum%mod,'\n');
			break;
		}
	}
	return 0;
}

T4:

const int N=5e5+5;
int n,Q;
vector<int> g[N];
int fa[N][19];
int dep[N];
int lca(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=dep[x]-dep[y],j=0;i;i>>=1,j++)if(i&1)x=fa[x][j];
	if(x==y)return  x;
	fd(i,18,0)if(fa[x][i]!=fa[y][i]){
		x=fa[x][i],y=fa[y][i];
	}
	return fa[x][0];
}
void dfs(int x,int y){
	fa[x][0]=y;
	dep[x]=dep[y]+1;
	fo(i,1,18)fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int v:g[x]){
		if(v!=y)dfs(v,x);
	}
}
int ans[N];
int v[N];
struct arr{
	int l,r,k,num;
}a[N],q[N];
stack<pair<int,int> > stk;
struct BIT{
	int lowbit(int x){
		return x&(-x);
	}
	int s[N];
	void update(int x,int y){
		while(x<n)s[x]=max(s[x],y),x+=lowbit(x);
	}
	int query(int x){
		int _s=0;
		while(x)_s=max(_s,s[x]),x-=lowbit(x);
		return _s;
	}
}b;
struct tree{
	#define ls (x<<1)
	#define rs (ls|1)
	#define mid ((l+r)>>1)
	int s[N*4];
	void update(int x,int l,int r,int y,int z){
		if(l==r){
			s[x]=max(s[x],z);
			return;
		}
		if(y<=mid)update(ls,l,mid,y,z);
		else update(rs,mid+1,r,y,z);
		s[x]=max(s[ls],s[rs]);
	}
	int query(int x,int l,int r,int L,int R){
		if(L<=l&&r<=R)return s[x];
		if(R<l||r<L)return 0;
		return max(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
	}
}tr;
struct ST{
	int mx[19][N];
	void make(){
		fo(i,1,n)mx[0][i]=dep[i];
		fo(i,1,__lg(n)){
			fo(j,1,n-(1<<i)+1){
				mx[i][j]=max(mx[i-1][j],mx[i-1][j+(1<<i-1)]);
			}
		}
	}
	int get(int l,int r){
		int len=r-l+1;
		return max(mx[__lg(len)][l],mx[__lg(len)][r-(1<<__lg(len))+1]);
	}
}ST;
signed main(){
// 	freopen("query.in","r",stdin);
// 	freopen("query.out","w",stdout);
	read(n);
	fo(i,1,n-1){
		int u,v;
		read(u,v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs(1,0);
	fo(i,1,n-1){
		v[i]=dep[lca(i,i+1)];
		while(stk.size()&&v[i]<=stk.top().first)stk.pop();
		if(stk.size())a[i].l=stk.top().second+1;
		else a[i].l=1;
		stk.push({v[i],i});
		a[i].k=v[i];
	}
	while(stk.size())stk.pop();
	fd(i,n-1,1){
		while(stk.size()&&v[i]<=stk.top().first)stk.pop();
		if(stk.size())a[i].r=stk.top().second-1;
		else a[i].r=n-1;
		stk.push({v[i],i});
	}
	sort(a+1,a+n,[](arr x,arr y){
		return x.r>y.r;	
	});
	ST.make();
	read(Q);
	fo(i,1,Q){
		read(q[i].l),read(q[i].r),read(q[i].k);
		q[i].num=i;
		if(q[i].k==1){
			ans[i]=ST.get(q[i].l,q[i].r);
		}
		q[i].r--;
		q[i].k--;
	}
	sort(q+1,q+1+Q,[](arr x,arr y){
		return x.r>y.r;
	});
	int st=1;
	fo(i,1,n-1){
		while(st<=Q&&q[st].r>a[i].r){
			if(!q[st].k){
				++st; continue;
			}
			ans[q[st].num]=max(ans[q[st].num],b.query(q[st].r-q[st].k+1));
			++st;
		}
		b.update(a[i].l,a[i].k);
	}
	while(st<=Q){
		if(!q[st].k){
			++st; continue;
		}
		ans[q[st].num]=max(ans[q[st].num],b.query(q[st].r-q[st].k+1));
		++st;
	}
	sort(a+1,a+n,[](arr x,arr y){
		return x.r-x.l+1>y.r-y.l+1;
	});
	st=1;
	sort(q+1,q+1+Q,[](arr x,arr y){
		return x.k>y.k;
	});
	fo(i,1,n-1){
		while(st<=Q&&q[st].k>a[i].r-a[i].l+1){
			if(!q[st].k){
				++st; continue;
			}
			ans[q[st].num]=max(ans[q[st].num],tr.query(1,1,n-1,q[st].l+q[st].k-1,q[st].r));
			++st;
		}
		tr.update(1,1,n-1,a[i].r,a[i].k);
	}
	while(st<=Q){
		if(!q[st].k){
			++st; continue;
		}
		ans[q[st].num]=max(ans[q[st].num],tr.query(1,1,n-1,q[st].l+q[st].k-1,q[st].r));
		++st;
	}
	fo(i,1,Q)write(ans[i],'\n');
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值