烟花 题解
Shunpower
·
·
题解
出题人题解。
首先 $\rm Subtask\ 1$ 给了指数级暴搜所有方案然后一一检验的。
实际上真正有启发性的是 $\rm Subtask\ 3,4$,下面我们会提到为什么设置每一个子任务。
下文统一定义 $s_i$ 表示 $i$ 所在子树的大小,${i\choose j}=0$ 如果 $j>i,i<0,j>0$ 三者任占一个。$x_i,y_i$ 的含义同题面所示。
不妨先在最开始算一遍答案。先考虑根是限制点的子树的答案。
下文定义,从某个点向其子树内移动,不用经过别的限制点就能到达的限制点为“顶层限制点”。如果其本身是限制点,不算“顶层限制点”。没有顶层限制点时有 $mul_i=1,cnt_i=0$(这两个变量的具体含义见后文)。
假设这个点是 $i$,首先你需要得到该点为根的子树内所有顶层限制点的答案乘积,显然可以维护,不妨记为 $mul_i$。然后是该点子树内所有顶层限制点的限制 $y$ 之和,显然也很好维护,不妨记为 $cnt_i$。最后是该点子树内所有顶层限制点的子树大小 $siz$ 之和,不妨记为 $ssiz_i$。显然这个点的答案为 $mul_i\times {{s_i-ssiz_i}\choose{y_i-cnt_i}}$。值得注意的是右边的两个减法如果有一个是负值答案都为 $0$。
为了方便,我们下面把点 $i$ 的初始答案称作 $ans_i$。
以上讨论的都是这个根为限制点的情况。对于根为非限制点的,答案即为 $mul_i\times 2^{s_i-ssiz_i}$。而显然,总是存在 $s_i>ssiz_i$。
上文两种情况在没有顶层限制点(整个子树内除了根都没有限制)时仍然正确。
以上的部分主要的就是如何快速处理答案。会这个可以通过 $\rm Subtask\ 1,2$。
下面假设你掌握了处理答案的 $O(siz\log M)$ 以内的做法,其中 $siz$ 是树大小。
现在考虑询问二,容易发现可以在一开始就给每个点都预处理出来询问二的答案。
注意到当点 $i$ 不再是限制点的时候,对于它的父亲,$i$ 的顶层限制点成为 $i$ 父亲的顶层限制点。由于在第一次预处理答案的时候我们对于每一个限制点 $x$ 在算完 $x$ 的答案后要让 $mul_x\leftarrow ans_x,cnt_x\leftarrow y_x,ssiz_i\leftarrow siz_i$ 来方便在树上累积,因此我们考虑存一个 $pmul,pcnt,pssiz$ 表示在改变之前的 $mul,cnt,ssiz$,存留下该点的顶层限制点的各项数据。然后对于预处理询问二答案时,对于限制点儿子我们用 $pmul,pcnt,pssiz$ 去累积、累加(而不是 $mul,cnt,ssiz$),剩余的部分就和预处理普通答案一样。
还可以注意到记忆化询问二的复杂度也是正确的(每个点只会被访问一次)。但出题人十分邪恶,因为询问二的答案可能是 $0$,所以如果记忆化时判断目前答案数组里面是 $0$ 就重算答案会 TLE 在 #21。
会这个可以通过 $\rm Subtask\ 5$。
现在考虑询问一。容易发现我们可以把概率提取出来,也就是 $\frac{1}{c+1}$。然后仍然对于非限制点和限制点分类。
$1'$ 对于非限制点
容易发现其实每次多加一个儿子,就是在给 $k$ 的染色方法数量乘以 $2$,所以甩掉概率后的权值和即为 $ans_k+2^1ans_k+2^2ans_k+2^3ans_k+\cdots+2^{c}ans_k$,直接提取公因数然后做一个等比数列求和,然后乘上概率即可。
做到这里可以通过 $\rm Subtask\ 4$,甚至不需要完全做到这里,处理答案你只需要会非限制点内无限制点的 case 就可以。
$2'$ 对于限制点
容易发现如果一开始 $pmul_k=0$ 或者 $y_k<pcnt_k$ 无论加多少儿子都还会是 $0$。
对于其他情况,可以发现实际上权值和当中的 $pmul_k$ 并不会变,每次变大的只有组合数里面的 $s_k$。为了方便,下面存在 $d_1=y_k-pcnt_k,d_2=s_k-pssiz_k$,所以甩掉概率后的权值和为 $pmul_k\times({{d_2+0}\choose{d_1}}+{C_{d_2+1}\choose{d_1}}+\cdots+{{d_2+c}\choose{d_1}})$。
意思就是说我们要算下面这个式子的值:
$\sum\limits_{i=l}^r {{d_2+i}\choose{d_1}}$,其中 $l$ 我们为了使得组合数不为 $0$,为 $\max (y_k-pcnt_k-s_k+pssiz_k,0)$,$r=c$,特判 $r<l$ 为 $0$。
然而这个式子如果你不会快速算,你可以暴算去通过 $\rm Subtask\ 3$。可以发现,推到这里其实你可以通过 $\rm Subtask\ 1\sim5$,得分可以达到 $80$。下面介绍做这个式子的正解。
根据上指标求和公式有:
$$
\sum\limits_{i=0}^n {{i}\choose{m}}={{n+1}\choose{m+1}}
$$
于是我们可以把这个和式改成 ${{d_2+r+1}\choose{d_1+1}}-{{d_2+l+1-1}\choose{d_1+1}}$,然后就可以在 $O(\log M)$ 以内的时间复杂度算出来了($M$ 为模数)。注意右边的组合数可能出现组合数下面大于上面的情况,特判为 $0$。
把这个东西乘上概率和 $pmul_k$ 即可。
上指标求和公式不知道也可以推出,下面是 @StayAlone 的推导:
首先我们推一个 $\sum\limits_{i=0}^n {i\choose m}$。然后利用前缀和相减。
首先你知道 ${{i}\choose{m}}=\frac{i!}{m!(i-m)!}$,在这里你发现 $m$ 是定值($d_1$),所以先提取 $\frac{1}{m!}$ 到求和外面,然后考虑 $\frac{i!}{(i-m)!}$。然后当你把除法打开变成乘法的时候,你可以发现这是一个极其经典的整数裂项,它的答案为 $\frac{1}{m+1}\prod_{g=n+1-m}^{n+1}g$。
再乘上一个 $\frac{1}{m!}$ 可以得到:
$$
\sum\limits_{0\leqslant i\leqslant n} {i\choose m}=\frac{1}{(m+1)!}\prod_{g=n+1-m}^{n+1}g=\frac{1}{(m+1)!}\times\frac{(n+1)!}{(n+1-m-1)!}={{n+1}\choose{m+1}}
$$
本质上还是上指标求和,但是可以现场推。后面的方法就和前面一样了。
通过线性预处理阶乘逆元和 $2$ 的次幂,最开始计算答案的时间复杂度为 $O(n)$。再加上线性预处理逆元,询问一复杂度可以做到 $O(1)$。询问二复杂度为 $O(1)$。
最终 std 的复杂度为 $O(n+q)$。数据应该足以把绝大多数错误复杂度卡掉(以及一些常数过分大的 $\log$,简单优化就能过了),正确性写错的应该也基本卡了。不过错法太多,具体出题人也不清楚。/kel/kel/kel
------------
下面是 std。
```cpp
//Author:Leftist / Shunpower
//Spade Su & Griffin BAO
//May the force be with you and me.
#include <bits/stdc++.h>
#define ET return 0
#define fi first
#define se second
#define mp make_pair
#define pb emplace_back
#define ll long long
#define ull unsigned long long
#define inf INT_MAX
#define uinf INT_MIN
#define pii pair<int,int>
#define pll pair<ll,ll>
#define debug puts("--------Chery AK IOI--------");
#define Yes cout<<"Yes"<<endl;
#define No cout<<"No"<<endl;
#define pt puts("")
#define fr1(i,a,b) for(int i=a;i<=b;i++)
#define fr2(i,a,b) for(int i=a;i>=b;i--)
#define fv(i,p) for(int i=0;i<p.size();i++)
#define ld long double
#define il inline
#define ptc putchar
using namespace std;
const int N=6e5+100;
const ll P=998244353;
namespace Cream_H{
int lowbit(int x){
return x&-x;
}
template <typename T>
inline void read(T &x){
T s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-'){
w=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';
ch=getchar();
}
x=s*w;
}
template <typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9){
write(x/10);
}
putchar(x%10+'0');
}
}
using namespace Cream_H;
int n,m;
int u,v;
vector <int> p[N];
int x,y;
int lim[N];
bool hlim[N];
ll mul[N],cnt[N],ans[N],siz[N],fac[N<<1];
ll pmul[N],pcnt[N],ans2[N],ssiz[N],pssiz[N];
ll pow2[N],invf[N],dpinv[N];
int q;
char opt;
int k,c;
ll allans;
il ll qpow(ll b,ll p,ll k){
if(!p){
return 1;
}
ll d=qpow(b,p>>1,k);
if(p&1){
return d*d%k*b%k;
}
else{
return d*d%k;
}
}
il void init(int n){
fac[0]=1;
pow2[0]=1;
dpinv[1]=1;
fr1(i,1,n){
pow2[i]=pow2[i-1]*2ll;
pow2[i]%=P;
fac[i]=fac[i-1]*(ll)i;
fac[i]%=P;
if(i!=1){
dpinv[i]=((1ll*(-P/i)*dpinv[P%i])%P+P)%P;
}
}
invf[n]=qpow(fac[n],P-2,P);
fr2(i,n-1,0){
invf[i]=1ll*invf[i+1]*(i+1)%P;
}
}
il ll C(ll n,ll m){//m choose n
if(n<0||m<0||n>m){
return 0;
}
return fac[m]*invf[n]%P*invf[m-n]%P;
}
il void dfs(int x,int fa){
mul[x]=1;
siz[x]=1;
fv(i,p[x]){
if(p[x][i]==fa){
continue;
}
dfs(p[x][i],x);
mul[x]*=mul[p[x][i]];
mul[x]%=P;
cnt[x]+=cnt[p[x][i]];
siz[x]+=siz[p[x][i]];
ssiz[x]+=ssiz[p[x][i]];
}
if(hlim[x]){
ans[x]=mul[x]*C(lim[x]-cnt[x],siz[x]-ssiz[x])%P;
pmul[x]=mul[x];
pcnt[x]=cnt[x];
pssiz[x]=ssiz[x];
mul[x]=ans[x];
cnt[x]=lim[x];
ssiz[x]=siz[x];
}
else{
ans[x]=mul[x]*pow2[siz[x]-ssiz[x]]%P;
pmul[x]=mul[x];
pcnt[x]=cnt[x];
pssiz[x]=ssiz[x];
}
}
il void dfs2(int x,int fa){
ll muld=1,cntd=0,sizd=0;
fv(i,p[x]){
if(p[x][i]==fa){
continue;
}
dfs2(p[x][i],x);
muld*=pmul[p[x][i]];
muld%=P;
cntd+=pcnt[p[x][i]];
sizd+=pssiz[p[x][i]];
}
if(hlim[x]){
ans2[x]=muld*C(lim[x]-cntd,siz[x]-sizd)%P;
}
else{
ans2[x]=muld*pow2[siz[x]-sizd]%P;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin>>n>>m;
init(600050);
fr1(i,1,n-1){
cin>>u>>v;
p[u].pb(v);
p[v].pb(u);
}
fr1(i,1,m){
cin>>x>>y;
lim[x]=y;
hlim[x]=1;
}
dfs(1,0);
dfs2(1,0);
cin>>q;
fr1(i,1,q){
ll qans=0;
cin>>opt>>k;
if(opt=='Z'){
cin>>c;
ll div=dpinv[c+1];
if(!hlim[k]){
ll muls=pow2[c+1]-1;
qans=muls*ans[k]%P*div%P;
}
else{
if(!pmul[k]||lim[k]-pcnt[k]<0){
continue;
}
ll l=max(lim[k]-pcnt[k]-siz[k]+pssiz[k],0ll);
ll r=c;
if(r<l){
continue;
}
ll d1=lim[k]-pcnt[k];
ll d2=siz[k]-pssiz[k];
qans=(((C(d1+1,d2+r+1)-C(d1+1,d2+l))%P+P)%P)*pmul[k]%P*div%P;
}
}
else{
qans=ans2[k];
}
allans^=1ll*i*qans;
}
cout<<allans<<endl;
ET;
}
//ETERNAL LOVE FOR Zhang Junhao, Mu Zhicheng and Zuo Hang.
//ALL FOR Zhang Junhao.
```