【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;
}