【2025.8.18】模拟赛

T1

我的做法:

性质分析:

\(x\) 的二进制拆分中最低位的位数为 \(lowbit(x)\)\(val[1\cdots ki]\)\(x\) 二进制下 \(1\) 的位置。

  • 性质一:以节点 \(i\) 为根的子树的最远距离为 \(lowbit(i)\),即从最低位开始有多少个连续的 \(0\),记为 \(dep(i)\),则 \(dep(i) = val[1]\)
  • 性质二:以节点 \(i\) 为根的子树中,到节点 \(i\) 的距离为 \(s\) 的点的总数为 \(C_{dep(i)}^{s}\)

solution:

对于询问的节点 \(u\),一直往上跳父节点,把父节点作为 lca,每次跳的时候统计贡献。

设已经向上跳了 \(y\) 的距离,则当前父节点为 \(val[1 + y\cdots ki]\),从当前父节点往 \(u\) 的路径上的儿子节点为 \(val[y\cdots ki]\),贡献为到当前父节点距离为 \(di - y\) 的、不在 \(u\) 属于的那个儿子子树中的点(保证这些点分别和 \(u\) 的 lca 为当前父节点)的数量,用性质二计算,贡献为 \(C_{val[1 + y]}^{di - y} - C_{val[y]}^{di - y - 1}\)\(y = 0\) 时要特判)。

有时候当前父节点子树中到当前父节点最远都达不到要求,所以 \(y\) 必须有个下界,满足 \(val[1 + y] + y\ge di\) 的才能计算贡献。

code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e7+5;
const ll mod=998244353;
int n,q;
ll ans=0;
ll fac[N],inv[N];
int ki,val[N],di;
ll ksm(ll x,ll k){
	if(k==0)  return 1;
	if(k==1)  return x%mod;
	ll t=ksm(x,k/2)%mod;
	t=t*t%mod;
	if(k&1)
	  t=t*x%mod;
	return t%mod;
}
ll c(int x,int y){
	if(x<0||y<0||x<y)  return 0;
	return (fac[x]*inv[y]%mod)*inv[x-y]%mod;
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	cin>>n>>q;
	fac[0]=1;
	for(int i=1;i<=n;++i)
	  fac[i]=fac[i-1]*i%mod;
	inv[n]=ksm(fac[n],mod-2)%mod;
	for(int i=n-1;i>=0;--i)
	  inv[i]=inv[i+1]*(i+1)%mod;
	while(q--){
		ans=0;
		scanf("%d",&ki);
		for(int i=1;i<=ki;++i)
		  scanf("%d",&val[i]);
		sort(val+1,val+ki+1);
		val[ki+1]=n;
		scanf("%d",&di);
		int dn=0;
		for(int i=0;i<=ki;++i)
		  if(val[1+i]+i>=di){
		  	dn=i;break;
		  }
		for(int i=dn;i<=min(ki,di);++i){
			ans=(ans+c(val[1+i],di-i))%mod;
			if(i>0)  ans=(ans-c(val[i],di-i-1)+mod)%mod;
		}
		printf("%lld\n",ans);
	}
	fclose(stdin);fclose(stdout);
	return 0;
}

(细节太多了,比赛时也推错式子了,没调出来。)

原题解:

观察题目中的树,可以发现 \(i\) 的父亲是 \(i − lowbit(i)\)

考虑树上两个点 \(i,j\) 的距离,可以看成每次让 \(lowbit\) 小的人向父亲跳(将这个bit变为 0),直到相遇。

那么找到 \(i,j\) 二进制表示下从高到低第一个不同的位,\(i,j\) 之间的距离即为 \(i,j\) 的二进制表示中这一位往下的 \(1\) 的个数和。

此时直接的想法是枚举 \(i\)\(x\) 二进制表示下第一个不同的位,那么 \(x\) 的二进制表示之后的位的 \(1\) 数量是已知的。

此时相当于固定了 \(i\) 在不同的位以及之前的二进制表示,之后的位任意,且需要不同的位与之后的位中的 \(1\) 数量等于一个定值。因此方案是一个组合数。

可以发现答案为 \(\sum_{i = 0}^{n - 1} C_{n - i}^{d_i - 1 - \sum_{j = 0}^k [v_j > i]}\),复杂度 \(O(nq)\)

按照 \(v_j\) 进行分段,则每一段内的上标固定。此时相当于给定 \(l,r,k\),询问 \(\sum_{i = l}^r C_i^k\)。可以发现这等于 \(C_{r + 1}^{k + 1} - C_l^{k + 1}\)

复杂度 \(O(n +\sum k)\),注意预处理。

#include<bits/stdc++.h>
#define N 10000010
#define LL long long
#define mod 998244353 
#define rep(i,l,r) for(int i=l;i<=r;i++) 
using namespace std;
int rd() {
	int res=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
	return res*f;
}
int n,Q,K,b[1000010],D;
namespace p100 {
	LL fac[N],ifac[N];
	LL C(int x,int y) {
		if(x<0||y<0||x<y) return 0;
		return fac[x]*ifac[y]%mod*ifac[x-y]%mod;
	}
	void Main() {
		fac[0]=fac[1]=ifac[0]=ifac[1]=1;
		rep(i,2,n) fac[i]=fac[i-1]*i%mod,ifac[i]=mod-mod/i*ifac[mod%i]%mod;
		rep(i,2,n) ifac[i]=ifac[i-1]*ifac[i]%mod;
	}
	LL work() {
		LL res=0;
		b[++K]=n;
		rep(i,1,K) {
			if(i>1) res=(res+C(b[i],D-(i-1))-C(b[i-1],D-(i-1)-1)+mod)%mod;
			else res=(res+C(b[i],D-(i-1)))%mod;
		}
		return res;
	}
}
int main() {
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n=rd(),Q=rd();
	p100::Main();
	rep(o,1,Q) {
		K=rd();rep(i,1,K) b[i]=rd();D=rd();
		printf("%lld\n",p100::work());
	}
	return 0;
}
posted @ 2025-08-18 16:46  TimeSpacerui  阅读(10)  评论(0)    收藏  举报