专栏文章

蜜月日记 Part10

休闲·娱乐参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miqd0scb
此快照首次捕获于
2025/12/04 02:48
3 个月前
此快照最后确认于
2025/12/04 02:48
3 个月前
查看原文

前言

Day 100 最后冲刺!

Day 91

由于原本这一天的题过于简单了且没有技巧所以换掉。
神秘线代科技。

题意

给定一个长为 nn 的序列 a1,,ana_1,\cdots,a_n。有 nn 个点,第 ii 个点和第 jj 个点之间连了 gcd(ai,aj)\gcd(a_i,a_j) 条边,求这张图的生成树个数对 109+710^9+7 取模的值。

思路

设值域为 mm
先回忆一下无向图矩阵树定理。设 DD 为度数矩阵去掉最后一行最后一列,GG 为邻接矩阵去掉最后一行最后一列,则生成树个数为 det(DG)\det(D-G)
直接计算就 O(n3)O(n^3) 了显然是跑不过的。我们需要用行列式的性质化简。
事实上,我们有:
【矩阵行列式引理】 对于 n×nn\times n 的可逆矩阵 AAn×mn\times m 的矩阵 U,VU,V,有 det(A+UV)=detAdet(I+VA1U)\det(A+UV^{\top})=\det A\det(I+V^{\top}A^{-1}U)
如果 AA 的行列式好算(比如对角矩阵),且 det(I+VA1U)\det(I+V^{\top}A^{-1}U) 更好算(比如 m<<nm<<n 时)可以用矩阵行列式引理来算。
回到矩阵树定理。发现这个定理的形式就是给我们用这个的。我们要求 det(DG)\det(D-G),其中 DD 是对角矩阵行列式好算,只需要找到合适的 U,VU,V 即可。
观察 GG 的结构,我们有 Gi,j=gcd(ai,aj)=dai,dajφ(d)=d=1m[dai]φ(d)[daj]G_{i,j}=\gcd(a_i,a_j)=\sum\limits_{d|a_i,d|a_j}\varphi(d)=\sum\limits_{d=1}^m[d|a_i]\varphi(d)[d|a_j]
于是构造 (n1)×m(n-1)\times m 的矩阵 U,VU,V,其中 Ui,d=[dai]φ(d),Vi,d=[daj]U_{i,d}=[d|a_i]\varphi(d),V_{i,d}=[d|a_j],则 G=UVG=UV^{\top}
那么 (IVD1U)i,j=[i=j]φ(j)t=1n1[iat][jat](I-V^{\top}D^{-1}U)_{i,j}=[i=j]-\varphi(j)\sum\limits_{t=1}^{n-1}[i|a_t][j|a_t]。这个只在 lcm(i,j)m\operatorname{lcm}(i,j)\le m 的时候有值。
每次只消元有值的位置,复杂度能过。不能过把矩阵转置一下就过了。

代码

CPP
#include<bits/stdc++.h>
#define F(i,a,b) for(int i(a),i##i##end(b);i<=i##i##end;++i)
#define R(i,a,b) for(int i(a),i##i##end(b);i>=i##i##end;--i)
#define ll long long
#define File(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
using namespace std;
const int MAXN=5001,MOD=1e9+7;
inline ll qpow(ll b,int e){ll res=1;while(e)(e&1)&&(res=res*b%MOD),b=b*b%MOD,e>>=1;return res;}
int n,m,a[MAXN];
int vis[MAXN],phi[MAXN];
vector<int>pr;
inline void init(){
	phi[1]=1;
	F(i,2,m){
		if(!vis[i]) vis[i]=i,phi[i]=i-1,pr.emplace_back(i);
		for(int j:pr){
			int k=i*j;
			if(k>m) break;
			vis[k]=j;
			if(vis[i]==j){phi[k]=phi[i]*j;break;}
			else phi[k]=phi[i]*(j-1);
		}
	}
}
int D[MAXN],G[MAXN][MAXN];
inline ll det(){
	bool inv=0;
	ll res=1;
	F(i,1,m){
		int t=-1;
		F(j,i,m) if(G[j][i]){t=j;break;}
		if(t==-1) return 0;
		if(t!=i) swap(G[t],G[i]),inv^=1;
		res=res*G[i][i]%MOD;
		vector<int>pos;
		F(j,i,m) if(G[i][j]) pos.emplace_back(j);
		ll inv=qpow(MOD-G[i][i],MOD-2)%MOD;
		F(j,i+1,m) if(G[j][i]){
			ll coef=G[j][i]*inv%MOD;
			for(int k:pos) G[j][k]=(G[j][k]+G[i][k]*coef)%MOD;
		}
	}
	return inv?MOD-res:res;
}
signed main(){ 
	ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
	cin>>n;
	F(i,1,n) cin>>a[i],m=max(m,a[i]);
	init();
	F(i,1,n) F(j,1,n) D[i]+=__gcd(a[i],a[j]);
	ll coef=1;
	F(i,1,n-1) coef=coef*D[i]%MOD,D[i]=qpow(D[i],MOD-2);
	F(i,1,m) F(j,1,m) if(i*j/__gcd(i,j)<=m){
		ll qwq=0;
		F(k,1,n-1) if(a[k]%i==0&&a[k]%j==0) (qwq+=D[k])>=MOD&&(qwq-=MOD);
		(G[m-i+1][m-j+1]=((i==j)-phi[j]*qwq)%MOD)<0&&(G[m-i+1][m-j+1]+=MOD);
	}
	cout<<coef*det()%MOD;
	return 0;
}

Day 92

生成子群相关完全不会啊。

题意

给定 kk 个长为 nn 的置换,求它们生成子群的逆序对的平均数对 109+710^9+7 取模的值。

思路

生成子群最重要的性质是,假设这个生成子群的所有置换中的第 ii 位有 kk 种取值,那么每种取值的置换个数相同。
这个性质的推论是,选出若干位组成一个向量,这个向量的每种取值对应的置换个数也是相等的。
在本题中,我们可以考虑对所有 iji\neq j 的二元组 (i,j)(i,j) 建点,(i,j)(i,j) 和所有 (pi,pj)(p_i,p_j) 连双向边,则一个连通块中的所有元素就是这两位可能的值。维护连通块中 i<ji<ji>ji>j 的点数,设分别为 a,ba,b,则贡献为 aba+b\dfrac{ab}{a+b}
直接维护是 O(n2k)O(n^2k) 的,跑不过去。
前面的 O(n2)O(n^2) 基本上没法再优化了。我们试图优化一下 O(k)O(k)
有一个确定性做法是 Schreier–Sims 算法,但是我不会。你感觉一下,我们随机选出 O(logn)O(\log n) 个给出置换的子集复合起来,得到的所有置换大概率覆盖了所有上面图的边。
所以复杂度变成了 O(n(n+k)logn)O(n(n+k)\log n)

代码

CPP
#include<bits/stdc++.h>
#define File(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
#define ll long long
#define F(i,a,b) for(int i(a),i##i##end(b);i<=i##i##end;++i)
#define R(i,a,b) for(int i(a),i##i##end(b);i>=i##i##end;--i)
using namespace std;
const int MAXN=3001,MOD=1e9+7;
int n,k,a[MAXN][MAXN],dsu[MAXN*MAXN];
inline int id(int i,int j){
	return (i-1)*n+j;
}
int find(int x){
	return x==dsu[x]?x:dsu[x]=find(dsu[x]);
}
mt19937 gen(time(0)^*new int);
ll le[MAXN*MAXN],ge[MAXN*MAXN];
inline ll qpow(ll base,int expo=MOD-2){
	ll res=1;
	while(expo){
		(expo&1)&&(res=res*base%MOD);
		base=base*base%MOD,expo>>=1;
	}
	return res;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>k;
	F(i,1,k) F(j,1,n) cin>>a[i][j];
	iota(dsu+1,dsu+n*n+1,1);
	F(T,1,40){
		static int p[MAXN],pp[MAXN];
		iota(p+1,p+n+1,1);
		F(i,1,k) if(gen()&1){
			F(j,1,n) pp[p[j]]=a[i][j];
			swap(p,pp);
		}
		F(i,1,n) F(j,1,n) if(i!=j) dsu[find(id(p[i],p[j]))]=find(id(i,j));
	}
	F(i,1,n) F(j,1,n) if(i!=j){
		if(i<j) ++le[find(id(i,j))];
		else ++ge[find(id(i,j))];
	}
	int ans=0;
	F(i,1,n) F(j,1,n) if(i!=j&&find(id(i,j))==id(i,j)) ans=(ans+le[id(i,j)]*ge[id(i,j)]%MOD*qpow(le[id(i,j)]+ge[id(i,j)]))%MOD;
	cout<<ans;
	return 0;
}

Day 93

太久没写了,随便找了一道,结果是金牌题……
和数学过情人节。

题意

求把 (a1,a2)(a_1,a_2) 变成 (b1,b2)(b_1,b_2) 的最小操作次数,一次操作定义为选择一个正实数 xx,执行 (a1,a2)(a1x,a2x)(a_1,a_2)\leftarrow (\lfloor a_1x\rfloor,\lfloor a_2x\rfloor),或报告无解。

思路

把有序数对看成坐标,扔到平面直角坐标系上研究。下面记点 A(a1,a2),B(b1,b2)A(a_1,a_2),B(b_1,b_2)。不加说明时,我们均指第一象限。记值域为 VV
发现点 P(x,y)P(x,y) 可以变成形如 (i,yxi),(xyi,i),iZ(i,\lfloor\dfrac{y}{x}i\rfloor),(\lfloor\dfrac {x}{y}i\rfloor,i),i\in\mathbb Z 的点。不过还是有向下取整,不好描述。
那么倒过来看,对于 Q(p,q)Q(p,q),能够到达它的 PP 的集合是什么。
设直线 OP:y=kxOP:y=kx,如果 PP 能到达 QQ,则 OPOP 必须穿过以 QQ 为左下角、边长为 11 的正方形,穿过是指 OPOP 在正方形内的部分的长度 >0>0
Qu(p,q+1),Qr(p+1,q)Q_u(p,q+1),Q_r(p+1,q),则只要 OPOPQrOQu\angle Q_rOQ_u 内部(不包括 OQu,OQrOQ_u,OQ_r),其就会穿过这个正方形。
那么就有 k(kOQr,kOQu)k\in(k_{OQ_r},k_{OQ_u}),即能够到达 Q(p,q)Q(p,q)P{(x,y)qp+1<yx<q+1p}P\in\{(x,y)|\dfrac{q}{p+1}<\dfrac y x<\dfrac{q+1}p\}
l1=b2b1+1,r1=b2+1b1l_1=\dfrac{b_2}{b_1+1},r_1=\dfrac{b_2+1}{b_1},我们就可以算出经过 1\le 1 次操作到达 BB 的点的集合为 S1={(x,y)l1<yx<r1}S_1=\{(x,y)|l_1<\dfrac y x<r_1\}
同理,设 l2=min{yx+1(x,y)S1},r2=max{y+1x(x,y)S1}l_2=\min\{\dfrac y{x+1}|(x,y)\in S_1\},r_2=\max\{\dfrac{y+1}x|(x,y)\in S_1\},则经过 2\le 2 次操作到达 BB 的点的集合为 S2={(x,y)l2<yx<r2}S_2=\{(x,y)|l_2<\dfrac y x<r_2\}
更一般地,对于 t2t\ge 2,设 lt=min{yx+1(x,y)St1},rt=max{y+1x(x,y)St1}l_t=\min\{\dfrac y{x+1}|(x,y)\in S_{t-1}\},r_t=\max\{\dfrac{y+1}x|(x,y)\in S_{t-1}\},则经过 t\le t 次操作到达 BB 的点的集合为 St={(x,y)lt<yx<rt}S_t=\{(x,y)|l_t<\dfrac y x<r_t\}
先来研究已知 lt1,rt1l_{t-1},r_{t-1} 如何求 lt,rtl_t,r_t。下面只讨论 ltl_trtr_t 是类似的。
问题相当于求最小的 yx+1\dfrac{y}{x+1},满足 lt1<yx<rt1l_{t-1}<\dfrac{y}{x}<r_{t-1}。考虑到 yx+1=1xy+1y\dfrac{y}{x+1}=\dfrac{1}{\frac{x}{y}+\frac{1}{y}},即我们要 yy 尽量小 xy\dfrac{x}{y} 尽量大。在 yy 最小的同时取 xx 最大即可,可以用反证法和一些缩放证明。
同理,求 rtr_t 时找满足 xx 最小的最大 yy 即可。
找到这样的 x,yx,y 可以在 SBT 上二分实现。
接下来就是要求第一次满足 AStA\in S_ttt。打表发现 lt,rtl_t,r_t 在迭代若干轮之后就不变了,所以可以暴力求每一轮的 lt,rtl_t,r_t 是多少。考虑到 l,rl,r 在 SBT 上移动的过程,得到 x=1x=1y=1y=1 后就不会变了,所以迭代次数是 O(logV)O(\log V) 的。
注意有点在坐标轴、y=xy=x 上以及不在 y=xy=x 同一侧的时候判无解。可以用关于 y=xy=x 对称简化讨论。
总的复杂度是 O(Tlog2V)O(T\log^2 V)

代码

CPP
#include<bits/stdc++.h>
#define File(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
#define ll long long
#define F(i,a,b) for(int i(a),i##i##end(b);i<=i##i##end;++i)
#define R(i,a,b) for(int i(a),i##i##end(b);i>=i##i##end;--i)
using namespace std;
struct Frac{
	ll x,y;
	Frac(const ll&a=0,const ll&b=0):x(a),y(b){}
	inline void reduce(){ll qwq=__gcd(x,y);x/=qwq,y/=qwq;}
	inline Frac inv(){return Frac(y,x);}
	bool operator<(const Frac&qwq)const{return x*qwq.y<y*qwq.x;}
	bool operator==(const Frac&qwq)const{return x==qwq.x&&y==qwq.y;}
};
Frac find(Frac ql,Frac qr){
	Frac l=Frac(0,1),r=Frac(1,0),now=Frac(1,1);
	while(!(ql<now&&now<qr)){
		if(!(ql<now)){
			ll fac=(ql.x*now.y-ql.y*now.x)/(ql.y*r.x-ql.x*r.y)+1;
			now=Frac(now.x+fac*r.x,now.y+fac*r.y),l=Frac(now.x-r.x,now.y-r.y);
		}else{
			ll fac=(qr.y*now.x-qr.x*now.y)/(qr.x*l.y-qr.y*l.x)+1;
			now=Frac(now.x+fac*l.x,now.y+fac*l.y),r=Frac(now.x-l.x,now.y-l.y);
		}
	}
	if(now.y==1) now.x=(qr.x-1)/qr.y;
    now.reduce();
    return now;
}
ll a1,a2,b1,b2;
inline int solve(){
	if(a1>a2) swap(a1,a2),swap(b1,b2);
	Frac a=Frac(a1,a2),b=Frac(b1,b2);
	if(a==b) return 0;
	if(!a1&&!a2) return -1;
	if(!a1) return b1?-1:1;
	if(!b1&&!b2) return 1;
	if(a1==a2) return b1==b2?1:-1;
	if(b1>b2) return -1;
	if(!b1) return 1+(a2<=a1*b2);
	Frac l(b1,b2+1),r(b1+1,b2);
	l.reduce(),r.reduce(),a.reduce();
	if(Frac(1,b2/b1+1)<a){
		int t=1;
		while(!(l<a&&a<r)){
			++t;
			Frac nl=l.x>1?find(r.inv(),l.inv()).inv():Frac(l.x,l.y-1),nr=r.y>1?find(l,r):Frac(r.x-1,r.y);
			l=nl,r=nr;
			++l.y,++r.x;
			l.reduce(),r.reduce();
		}
		return t;
	}
	return -1;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int T;
	for(cin>>T;T;--T){
		cin>>a1>>a2>>b1>>b2;
		cout<<solve()<<"\n";
	}
	return 0;
}

Day 94

看过的没写代码的题,回旋镖正中靶心。

题意

给定 n,kn,k,求有多少个长为 nn 的序列 a1,,ana_1,\cdots,a_n,满足 liairi,gcd(ai,ai+1)k,gcd(ai,ai+1,ai+2)=kl_i\le a_i\le r_i,\gcd(a_i,a_{i+1})\neq k,\gcd(a_i,a_{i+1},a_{i+2})=k

思路

记值域为 mm
由于 gcd(ai,ai+1,ai+2)=k\gcd(a_i,a_{i+1},a_{i+2})=k,所以有 kaik|a_i。不妨令 aiaik,lilik,ririka_i\leftarrow\dfrac{a_i}{k},l_i\leftarrow\lceil\dfrac{l_i}{k}\rceil,r_i\leftarrow\lfloor\dfrac{r_i}{k}\rfloor。则原限制转化为 liairi,gcd(ai,ai+1)1,gcd(ai,ai+1,ai+2)=1l_i\le a_i\le r_i,\gcd(a_i,a_{i+1})\neq 1,\gcd(a_i,a_{i+1},a_{i+2})=1。接下来的 kk 和题目里的没有关系。
设一个状态转移会比较抽象。设 f(i,j,k)f(i,j,k) 表示考虑到第 ii 个数,ai=ka_i=kgcd(ai,ai1)=j\gcd(a_i,a_{i-1})=j 的方案数;设 g(i,j,k)g(i,j,k) 表示考虑到第 ii 个数,ai=ka_i=kgcd(ai,ai+1)=j\gcd(a_i,a_{i+1})=j 的方案数。
首先是 ggff 的转移为 f(i,j,k)=v=li1ri1g(i1,j,v)[gcd(v,k)=j]f(i,j,k)=\sum\limits_{v=l_{i-1}}^{r_{i-1}}g(i-1,j,v)[\gcd(v,k)=j]
除掉 jj 后反演得到 f(i,j,k)=v=li1jri1jg(i1,j,vj)dv,dkjμ(d)f(i,j,k)=\sum\limits_{v=\lceil\frac{l_{i-1}}{j}\rceil}^{\lfloor\frac{r_{i-1}}{j}\rfloor}g(i-1,j,vj)\sum\limits_{d|v,d|\frac{k}{j}}\mu(d)
即:
f(i,j,k)=dkjμ(d)v=li1djri1djg(i1,j,vdj)f(i,j,k)=\sum\limits_{d|\frac{k}{j}}\mu(d)\sum\limits_{v=\lceil\frac{l_{i-1}}{dj}\rceil}^{\lfloor\frac{r_{i-1}}{dj}\rfloor}g(i-1,j,vdj)
不妨 kkjk\leftarrow \dfrac k j,对第三维做狄利克雷后缀和,对位乘 μ\mu 后再做狄利克雷前缀和即可转移,时间复杂度 O(nmlogmloglogm)O(nm\log m\log\log m),前一个 logm\log m 是枚举因数的调和级数,后一个 loglogm\log\log m 是狄利克雷前后缀和。
然后再是 ffgg 的转移为:
g(i,j,k)=xkf(i,x,k)[gcd(x,j)=1]g(i,j,k)=\sum\limits_{x|k}f(i,x,k)[\gcd(x,j)=1]
枚举 kk,把 f(i,x,k)f(i,x,k) 贡献到 xx 的质因数集合的位置然后求高维后缀和,那么 g(i,j,k)g(i,j,k) 就是 jj 的质因数集合的补集的位置。这一部分复杂度是 kmd(k)ω(k)=kmω(k)ik1=ikmω(ik)ikm(ω(i)+ω(k))=2imkmiω(i)=2imO(miloglogvi)=O(mlogmloglogm)\sum\limits_{k\le m}d(k)\omega(k)=\sum\limits_{k\le m}\omega(k)\sum\limits_{i|k}1=\sum\limits_{ik\le m}\omega(ik)\le\sum\limits_{ik\le m}(\omega(i)+\omega(k))=2\sum\limits_{i\le m}\sum\limits_{k\le\frac m i}\omega(i)=2\sum\limits_{i\le m}O(\dfrac m i\log\log\dfrac v i)=O(m\log m\log\log m)。加上枚举第一维,总的复杂度是 O(nmlogmloglogm)O(nm\log m\log\log m)
然后就做完了,时间复杂度 O(nmlogmloglogm)O(nm\log m\log\log m)

代码

CPP
#include<bits/stdc++.h>
#define File(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
#define ll long long
#define F(i,a,b) for(int i(a),i##i##end(b);i<=i##i##end;++i)
#define R(i,a,b) for(int i(a),i##i##end(b);i>=i##i##end;--i)
using namespace std;
const int MAXN=2001,MAXV=5001,MOD=998244353;
inline void add(int&x,int y){(x+=y)>=MOD&&(x-=MOD);}
int n,m,l[MAXN],r[MAXN],tot;
int pr[MAXV],cnt,vis[MAXV],mu[MAXV],omega[MAXV];
inline void init(){
	mu[1]=1;
	F(i,2,m){
		!vis[i]&&(pr[++cnt]=i,vis[i]=i,mu[i]=-1);
		F(j,1,cnt){
			int t=i*pr[j];
			if(t>m) break;
			vis[t]=pr[j];
			if(vis[i]==pr[j]) break;
			else mu[t]=-mu[i];
		}
	}
	return;
}
vector<int>fac[MAXV];
int id[MAXV][MAXV];
int f[50000],g[50000],tmp[50000],pf[50000];
int sum[130];
signed main(){ 
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int k=0;
	cin>>n>>k;
	F(i,1,n) cin>>l[i]>>r[i],l[i]=(l[i]-1)/k+1,r[i]=r[i]/k,m=max(m,r[i]);
	init();
	F(i,1,m) for(int j=i;j<=m;j+=i) fac[j].push_back(i),id[j][i]=++tot;
	F(i,2,m){
		static int idx[MAXV];
		int now=0,qwq=i,qaq=-1;
		while(qwq!=1){
			if(now!=vis[qwq]) now=vis[qwq],idx[now]=++qaq;
			qwq/=vis[qwq];
		}
		omega[i]=qaq+1;
		for(int j:fac[i]){
			qwq=j;
			while(qwq!=1) pf[id[i][j]]|=(1<<idx[vis[qwq]]),qwq/=vis[qwq];
		}
	}
	F(i,2,m) for(int j=((l[1]-1)/i+1)*i;j<=r[1];j+=i) g[id[j][i]]=1;
	F(i,2,n){
		F(j,2,m){
			for(int k=j,qwq=1;k<=m;k+=j,++qwq) tmp[qwq]=g[id[k][j]];
			int lim=m/j;
			F(k,1,cnt){
				if(pr[k]>lim) break;
				for(int t=lim/pr[k]*pr[k];t>=pr[k];t-=pr[k]) add(tmp[t/pr[k]],tmp[t]);
			}
			F(k,1,lim) (tmp[k]=tmp[k]*mu[k])<0&&(tmp[k]+=MOD);
			F(k,1,cnt){
				if(pr[k]>lim) break;
				for(int t=pr[k];t<=lim;t+=pr[k]) add(tmp[t],tmp[t/pr[k]]);
			}
			for(int k=j,qwq=1;k<=m;k+=j,++qwq) f[id[k][j]]=tmp[qwq];
		}
		F(j,2,m) for(int k=j;k<=m;k+=j) if(k<l[i]||k>r[i]) f[id[k][j]]=0;
		F(k,2,m){
			F(j,0,(1<<omega[k])-1) sum[j]=0;
			for(int j:fac[k]) if(j!=1) add(sum[pf[id[k][j]]],f[id[k][j]]);
			F(j,0,omega[k]-1) F(s,0,(1<<omega[k])-1) if(s>>j&1) add(sum[s],sum[s^(1<<j)]);
			for(int j:fac[k]) if(j!=1) g[id[k][j]]=sum[((1<<omega[k])-1)^pf[id[k][j]]];
		}
	}
	int ans=0;
	F(i,2,m) for(int j=i;j<=m;j+=i) add(ans,f[id[j][i]]);
	cout<<ans;
	return 0;
}

Day 95

一个很厉害的技巧。

题意

有一个以 11 为根的有 nn 个节点的树,每一个点有权值值 BiB_i、重量 WiW_i 和颜色 Ci{0,1}C_i\in\{0,1\}。要求对于每一个点 uu 的子树 TuT_u 回答以下问题:
可以对 TuT_u 进行若干次操作,每一次选择一个非根节点,将这个点的所有儿子转移给其父亲,然后删去该节点。操作后应满足 TuT_u 中不存在两端点同色的边,所有点的重量和小于 XX。问操作之后的 TuT_u 的权值之和的最大值。

思路

先讨论求以 11 为根子树的答案。
删除顺序是不重要的,所以可以当成选几个点满足重量和 X\le X、与选中的点里最近的祖先颜色不同,使得权值和最大。也就是背包。
于是设 dp(i,j,k)dp(i,j,k) 表示当前在节点 ii 子树中、重量和为 jj、最浅的点的颜色为 kk 的最大权值和,转移为对于 uu 的儿子 vvdp(u,,Cu)dp(u,*,C_u)dp(u,,Cu)dp(u,*,C_u)dp(v,,1Cu)dp(v,*,1-C_u)max\max 卷积。
然后你发现完蛋了,max\max 卷积是 O(X2)O(X^2) 的。但是一个插入数是 O(X)O(X) 的,我们只要想办法把 max\max 卷积变成插入一个数就好了。
怎么办呢?你发现 max\max 卷积具有交换律和结合律,所以我们可以随便改变运算顺序。再加上每次我们初始的时候 dpdp 数组都为空,这个很浪费。不如每次递归子树时传入现在的“半成品”dpdp 数组,回溯的时候把当前节点的贡献再加进去。由于 kk 有两种取值,我们要 dfs 两次子树,每个点被访问了 O(祖先个数次)O(\text{祖先个数次}),总的复杂度变成了 O(n2X)O(n^2X)。已经比 O(nX2)O(nX^2) 好了!
在下一步我们使用多次递归子树的经典 Trick:轻重子树分治。你考虑第一次递归进去的时候 dp(u,,0),dp(u,,1)dp(u,*,0),dp(u,*,1) 都是 max\max 卷积的幺元,这两个没有区分,只需要递归一次。所以先递归一次重儿子,轻儿子再递归两次。
分析一下复杂度。设 nn 个点的树复杂度为 T(n)T(n),重儿子子树大小为 hh,其他子树大小为 y1,,yty_1,\cdots,y_t,则 T(n)=T(h)+O(C)+2i=1tT(yi)T(n)=T(h)+O(C)+2\sum\limits_{i=1}^t T(y_i)。显然复杂度高于 O(n)O(n),此时根据调整法取 y1=n1h,y2==yt=0y_1=n-1-h,y_2=\cdots=y_t=0 取到最大 T(h)+2T(n1h)T(h)+2T(n-1-h)。进一步,由于 n1hhn-1-h\le h,取 h=n12h=\dfrac{n-1}{2} 最优,即 T(n)3T(n2)+O(C)T(n)\le 3T(\dfrac n 2)+O(C)。根据主定理,得到复杂度上界 O(nlog23C)O(n^{\log_2 3}C)
然后是对每个子树求。你发现上面这个东西一次求了一条重链的答案,所以再遍历一遍轻子树即可,复杂度仍然是 O(nlog23C)O(n^{\log_2 3}C),其中 log231.582\log_2 3\approx 1.582

代码

CPP
#include<bits/stdc++.h>
#define File(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
#define ll long long
#define F(i,a,b) for(int i(a),i##i##end(b);i<=i##i##end;++i)
#define R(i,a,b) for(int i(a),i##i##end(b);i>=i##i##end;--i)
#define poly vector<ll>
#define fi first
#define se second
using namespace std;
const int MAXN=201,MAXV=50001;
int n,X,W[MAXN],C[MAXN],fa[MAXN],siz[MAXN],son[MAXN];
ll ans[MAXN],B[MAXN];
vector<int>g[MAXN];
void dfs1(int now){
	siz[now]=1;
	for(int i:g[now]) dfs1(i),siz[now]+=siz[i],siz[son[now]]<siz[i]&&(son[now]=i);
	return;
}
poly zero;
vector<poly> dfs2(int now,poly dp,bool heavy){
	if(!heavy) for(int i:g[now]) if(i!=son[now]) dfs2(i,dp,0);
	vector<poly>qwq(2,zero);
	if(son[now]){
		qwq=dfs2(son[now],dp,heavy);
		for(int i:g[now]) if(i!=son[now]) F(j,0,1) qwq[j]=dfs2(i,qwq[j],1)[j];
	}else qwq[0]=qwq[1]=dp;
	R(i,X,W[now]){
		qwq[C[now]][i]=max(qwq[C[now]][i],qwq[C[now]^1][i-W[now]]+B[now]);
		if(!heavy) ans[now]=max(ans[now],qwq[C[now]^1][i-W[now]]+B[now]);
	}
	return qwq;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>X;
	F(i,2,n) cin>>fa[i],g[fa[i]].push_back(i);
	F(i,1,n) cin>>B[i]>>W[i]>>C[i];
	dfs1(1);
	zero=poly(X+1,-5e17);
	zero[0]=0;
	dfs2(1,zero,0);
	F(i,1,n) cout<<ans[i]<<"\n";
	return 0;
}

Day 96

五合一。

题意

rrcc 列的棋盘上,qq 组询问兵、车、后、象、王从 (1,c1)(1,c_1)(r,cr)(r,c_r) 的最小步数和达到最小步数的方案数,或判断到达不了。保证 rcr\ge c

思路

c1crc_1\neq c_r 时到不了,否则最小步数为 rr,方案数为 11

c1crc_1\neq c_r 时最小步数为 22,方案数为 22;否则最小步数为 11,方案数为 11

先特判一步可以到的情况,这种情况都只有 11 种方案。
剩下的情况不超过两步,枚举每一种情况即可。
以上都是 O(1)O(1) 的。

先判如果坐标之和奇偶性不同就无解。
否则必然是像踢墙跳一样来回走直到走到 (x,cr)(x,c_r) 满足 xrx\ge r,最优步数 yy 就求出来了。
方案数的话,就是在每次转弯的地方少走几步,用隔板法可以算出来是 (y1+xr21xr2)\dbinom{y-1+\frac{x-r}{2}-1}{\frac{x-r}{2}}
由于 ycy\le c,复杂度 O(c)O(c)

显然走 r1r-1 步。下面设 c1=a,cr=bc_1=a,c_r=b,我们要求从 (1,a)(1,a)(r,b)(r,b) 的方案数,每步可以从 (x,y)(x,y) 走到 (x+1,y+1),(x+1,y),(x+1,y1)(x+1,y+1),(x+1,y),(x+1,y-1),且不能碰 (x,0),(x,c+1)(x,0),(x,c+1) 这两条线。
这是一个经典问题。设 fif_i 表示不考虑不能碰到的方案数,使用反射容斥,得到答案为 kba(mod2c+2)fkkab(mod2c+2)fk\sum\limits_{k\equiv b-a\pmod{2c+2}}f_k-\sum\limits_{k\equiv -a-b\pmod{2c+2}}f_k
注意到 fk=[xk](x+1+x1)r1f_k=[x^k](x+1+x^{-1})^{r-1},做循环卷积即可。预处理复杂度 O(c2logr)O(c^2\log r)O(clogclogr)O(c\log c\log r),询问复杂度 O(1)O(1)

代码

CPP
#include<bits/stdc++.h>
#define File(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
#define ll long long
#define F(i,a,b) for(int i(a),i##i##end(b);i<=i##i##end;++i)
#define R(i,a,b) for(int i(a),i##i##end(b);i>=i##i##end;--i)
using namespace std;
const int MAXN=4010,MOD=1e9+7;
int r,c,q,len;
ll fact[MAXN],inv[MAXN];
inline ll C(ll x,ll y){
	ll res=inv[y];
	F(i,0,y-1) res=res*(x-i)%MOD;
	return res;
}
struct Poly{
	ll v[MAXN]={};
	Poly operator*(const Poly&x)const{
		Poly res;
		F(i,0,len-1) F(j,0,len-1) res.v[(i+j)%(2*c+2)]=(res.v[(i+j)%(2*c+2)]+v[i]*x.v[j])%MOD;
		return res;
	}
}base,f;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>r>>c>>q;
	len=2*c+2,fact[0]=fact[1]=inv[0]=inv[1]=1;
	F(i,2,len) fact[i]=fact[i-1]*i%MOD,inv[i]=MOD-MOD/i*inv[MOD%i]%MOD;
	F(i,2,len) inv[i]=inv[i-1]*inv[i]%MOD;
	base.v[0]=base.v[1]=base.v[2*c+1]=1;
	f.v[0]=1;
	int expo=r-1;
	while(expo){
		(expo&1)&&(f=f*base,1);
		base=base*base,expo>>=1;
	}
	for(;q;--q){
		char T;
		int a,b;
		cin>>T>>a>>b;
		switch(T){
			case 'P':{
				if(a!=b) cout<<"0 0\n";
				else cout<<r-1<<" 1\n";
				break;
			}case 'R':{
				if(a!=b) cout<<"2 2\n";
				else cout<<"1 1\n";
				break;
			}case 'Q':{
				if(a==b||abs(b-a)==r-1){
					cout<<"1 1\n";
					continue;
				}
				int ans=4;
				if(r==c){
					if(a==1||a==c) ++ans;
					if(b==1||b==c) ++ans;
				}
				if(((1+a)&1)==((r+b)&1)){
					if(a+b+r-1<=2*c) ++ans;
					if(a+b-r+1>=2) ++ans;
				}
				cout<<"2 "<<ans<<"\n";
				break;
			}case 'B':{
				if(((1+a)&1)!=((r+b)&1)){
					cout<<"0 0\n";
					break;
				}
				int step=0x7fffffff,pos,x,ans=0;
				F(O,0,1){
					if(O) pos=a,x=1;
					else pos=c-a+1,x=c;
					int qaq=(r-pos)/(2*c-2),now=qaq*2;
					pos+=(2*c-2)*qaq;
					while(pos<r||x!=b){
						++now;
						if(x==1){
							if(pos+b-1>=r){
								pos+=b-1;
								break;
							}
							pos+=c-1,x=c;
						}else if(x==c){
							if(pos+c-b>=r){
								pos+=c-b;
								break;
							}
							pos+=c-1,x=1;
							continue;
						}
					}
					if(step>now) step=now,ans=0;
					if(step==now){
						int d=(pos-r)/2;
						(ans+=C(d+now-1,d))>=MOD&&(ans-=MOD);
					}
				}
				cout<<step+1<<" "<<ans<<"\n";
				break;
			}case 'K':{
				cout<<r-1<<" "<<(f.v[(b-a+len)%len]-f.v[(-b-a+len)%len]+MOD)%MOD<<"\n";
				break;
			}
		} 
	}
	return 0;
}

Day 97

在省队名单出之前,赶快写几篇吧。

题意

aa 个好人和 bb 个坏人。你可以向 ii 询问 jj 是不是好人。好人永远说真话,坏人会用某种策略回答。用不超过 2(a+b)2(a+b) 次询问出每个人是好人还是坏人,或判断不可能问出来。

思路

首先如果 aba\le b,那么坏人中挑 aa 个出来互相指认是好人并说其他人都是坏人,这样永远无法区分好坏。
否则 a>ba>b。考虑维护一个栈,满足栈里存在一个分界点,往栈顶都是好人,往栈底都是坏人。从小到达枚举 ii,如果栈空直接入栈,否则向栈顶询问 ii 是不是好人。
如果栈顶回答是好人,那么无论如何把 ii 入栈都不会破坏栈的性质,直接入栈。
否则,我们不仅不让 ii 入栈,还把栈顶弹掉。这样要么是两个坏人消掉了,要么是一个好人一个坏人消掉了,仍然满足好人更多。这里询问次数 <a+b<a+b
既然好人始终更多,最后的栈顶就是好人。挨个问一遍这个好人即可。这里询问次数 a+b1a+b-1,满足条件。
时间复杂度 O(a+b)O(a+b)

代码

CPP
#include<bits/stdc++.h>
#define F(i,a,b) for(int i(a),i##i##end(b);i<=i##i##end;++i)
#define R(i,a,b) for(int i(a),i##i##end(b);i>=i##i##end;--i)
#define ll long long
#define File(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
using namespace std;
const int MAXN=2e5+2;
int a,b;
inline char ask(int x,int y){
	cout<<"? "<<x<<" "<<y<<endl;
	char ch;
	cin>>ch;
	return ch;
}
int stk[MAXN],tp;
char ans[MAXN];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>a>>b;
	if(a<=b) return cout<<"Impossible",0;
	F(i,0,a+b-1){
		if(!tp){
			stk[++tp]=i;
			continue;
		}
		if(ask(stk[tp],i)=='Y') stk[++tp]=i;
		else --tp;
	}
	F(i,0,a+b-1) ans[i]=int(ask(stk[tp],i)=='Y')+'0';
	cout<<"! "<<ans;
	return 0;
}

Day 98

是少见的条件概率题。

题意

小 R 和小 B 玩了 n 局游戏。第 11 局游戏小 R 获胜的概率是 p1p_1,小 B 获胜的概率是 1p11 −p_1。如果第 i1i − 1 局游戏小 R 获胜,那么第 ii 局游戏小 R 获胜的概率为 pip_i,小 B 获胜的概率为 1pi1 − p_i;如果第 i1i − 1 局游戏小 B 获胜,那么第 ii 局游戏小 R 获胜的概率为 qiq_i,小 B 获胜的概率为 1qi1 − q_i
你现在有若干条信息,每条形如某一局谁赢了。你需要支持加入信息,删除信息,求以当前所有信息为前提时小 R 的期望获胜局数。

思路

根据期望的线性性,把每一局小 R 赢的条件概率加起来就是所求的条件期望。
然后发现影响第 ii 局的信息只有左右两边第一个已知结果的位置 l,rl,r。设事件 L,RL,R 分别表示第 l,rl,r 局给定的获胜情况,事件 XX 为第 ii 局小 R 获胜。所求即为 P(XLR)P(X|LR)
使用条件概率的定义得到 P(XLR)=P(LRX)P(LR)=P(L)P(XL)P(RX)P(L)P(RL)=P(XL)P(RX)P(RL)P(X|LR)=\dfrac{P(LRX)}{P(LR)}=\dfrac{P(L)P(X|L)P(R|X)}{P(L)P(R|L)}=\dfrac{P(X|L)P(R|X)}{P(R|L)}
首先是分母。维护一个二维行向量 [b,r][b,r] 分别表示小 R 输和赢的概率,每次转移就是右乘上矩阵 [1qiqi1pipi]\begin{bmatrix}1-q_i & q_i\\1-p_i & p_i\end{bmatrix}。发现分母的条件概率相当于是钦定第 ll 局的行向量是 [1,0][1,0][0,1][0,1],直接维护矩阵区间积即可。
然后是分子。比起分母,分子相当于是把某一局强行钦定为小 R 赢,即把转移矩阵的第一列都改为 00。使用矩阵乘法的分配律即可维护。
l,rl,r 可以用 set 维护,修改时减去老区间的贡献加上新区间的贡献。为了方便,可以加上第 00 局和第 n+1n+1 局并让小 R 在这两局胜。
时间复杂度 O(nlogn)O(n\log n)

代码

CPP
#include<bits/stdc++.h>
#define F(i,a,b) for(int i(a),i##i##end(b);i<=i##i##end;++i)
#define R(i,a,b) for(int i(a),i##i##end(b);i>=i##i##end;--i)
#define ll long long
#define File(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
using namespace std;
const int MAXN=2e5+2;
int n,m;
double p[MAXN],q[MAXN];
struct Mat{
	double v[2][2];
	Mat(const double&a=0,const double&b=0,const double&c=0,const double&d=0){v[0][0]=a,v[0][1]=b,v[1][0]=c,v[1][1]=d;}
	Mat operator+(const Mat&x)const{return Mat(v[0][0]+x.v[0][0],v[0][1]+x.v[0][1],v[1][0]+x.v[1][0],v[1][1]+x.v[1][1]);}
	Mat operator*(const Mat&x)const{return Mat(v[0][0]*x.v[0][0]+v[0][1]*x.v[1][0],v[0][0]*x.v[0][1]+v[0][1]*x.v[1][1],v[1][0]*x.v[0][0]+v[1][1]*x.v[1][0],v[1][0]*x.v[0][1]+v[1][1]*x.v[1][1]);}
}; 
struct Node{
	Mat ch,mo;
	Node operator*(const Node&x)const{return (Node){ch*x.mo+mo*x.ch,mo*x.mo};}
}segt[MAXN<<2];
#define lc (now<<1)
#define rc (now<<1|1)
#define mid ((l+r)>>1)
#define ls lc,l,mid
#define rs rc,mid+1,r
void build(int now,int l,int r){
	if(l==r){
		segt[now].mo=Mat(1-q[l],q[l],1-p[l],p[l]);
		segt[now].ch=Mat(0,0,1-p[l],p[l]);
		return;
	}
	build(ls),build(rs);
	segt[now]=segt[lc]*segt[rc];
	return;
}
Node qry(int now,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr) return segt[now];
	if(qr<=mid) return qry(ls,ql,qr);
	else if(ql>mid) return qry(rs,ql,qr);
	else return qry(ls,ql,qr)*qry(rs,ql,qr);
}
set<int>info;
bool win[MAXN];
double ask(int l,int r){
	Node qwq=qry(1,1,n+1,l+1,r);
	return qwq.ch.v[win[l]][win[r]]/qwq.mo.v[win[l]][win[r]];
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cout<<fixed<<setprecision(6);
	char type;
	cin>>n>>m>>type;
	cin>>p[1];
	F(i,2,n) cin>>p[i]>>q[i];
	win[0]=win[n+1]=1;
	p[n+1]=q[n+1]=1;
	build(1,1,n+1);
	info.insert(0),info.insert(n+1);
	double ans=ask(0,n+1);
	for(;m;--m){
		char op[4];
		int pos;
		cin>>op>>pos;
		if(op[0]=='a'){
			cin>>win[pos];
			auto it=info.lower_bound(pos);
			int l=*prev(it),r=*it;
			info.insert(pos);
			ans-=ask(l,r),ans+=ask(l,pos),ans+=ask(pos,r);
		}else{
			info.erase(pos);
			auto it=info.lower_bound(pos);
			int l=*prev(it),r=*it;
			ans-=ask(l,pos),ans-=ask(pos,r),ans+=ask(l,r);
		}
		cout<<ans-1<<"\n";
	}
	return 0;
}

Day 99

完成立下的 flag。

题意

对每个点求出包含这个点的所有独立集中点权最大值的和,对 998244353998244353 取模。

思路

我们称节点 ii 的点权比节点 jj 大当且仅当 vi>vjv_i>v_jvi=vj,i>jv_i=v_j,i>j
考虑转置原理:
【转置原理】 对于一个线性算法,我们可以通过倒序执行所有操作并把操作 xx+kyx\leftarrow x+ky 改为 yy+kxy\leftarrow y+kx 得到转置问题的算法,时间复杂度不变。
ai,ja_{i,j} 表示包含点 ii 且点权最大的点为 jj 的独立集个数,nn 阶方阵 A=(ai,j)A=(a_{i,j}),列向量 a=[v1,v2,,vn]\vec{a}=[v_1,v_2,\cdots,v_n]^{\top},则答案向量为 b=Aa\vec b=A\vec a
它的转置问题即为:对于每个 ii,求满足 ii 是独立集中点权最大的点的所有独立集的点权和。
按点权从小到大加入每个点,加入点 ii 时所有独立集点权和的变化量就是 ii 的答案。在这里,一个点被加入指的是可以在独立集中出现,未被加入指的是不能出现在独立集中。
这是一个经典的 ddp 问题,我们使用静态 top tree 解决。对每个簇设 f(0/1,0/1),g(0/1,0/1)f(0/1,0/1),g(0/1,0/1) 分别表示不选/选上界点、不选/选下界点的独立集方案数和权值和,转移是显然的。值得注意的是,此处 g(1,)g(1,*) 我们不计入上界点的贡献。可以新建一个虚点 00 作为 11 的父亲方便统计答案。
如果把 ff 视为常量(矩阵里的量),则转移关于 gg 是线性的。
需要注意,你可以把 ddp 的每次修改看成持久化的,即我们修改的时候变量所占用的内存都是不同的,需要先赋值一次。
然后转置就行了。时间复杂度 O(nlogn)O(n\log n)。发现我们实际上并不需要持久化,每次直接覆盖就行,所以空间 O(n)O(n)。如果还是不明白可以看代码,注释里标了转置前后的内容。
事实上,用转置原理得到的做法和其他几篇直接用 top tree 得到的做法是一样的。

代码

CPP
#include <bits/stdc++.h>
#define File(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
#define ll long long
#define F(i,a,b) for(int i(a),i##i##end(b);i<=i##i##end;++i)
#define R(i,a,b) for(int i(a),i##i##end(b);i>=i##i##end;--i)
#define fi first
#define se second
using namespace std;
const int MAXN=3e5+2,MOD=998244353;
int n;
pair<int,int>val[MAXN];
int cnt;
struct Node{
	short type;//-1unit,0rake,1compress
	int up,dn,mid;
	int siz,lc,rc,fa;
	ll f[2][2],g[2][2];
	Node(const int&t=-1,const int&d=0,const int&e=0,const int&a=1,const int&b=0,const int&c=0){
		type=t,siz=a,lc=b,rc=c,up=d,dn=e,fa=0;
		memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
		return;
	}
}node[MAXN<<1];
bool isin[MAXN]; 
inline void upd(int now){
	Node&qwq(node[now]),&l(node[qwq.lc]),&r(node[qwq.rc]);
	memset(qwq.f,0,sizeof(qwq.f));
	if(node[now].type) F(i,0,1) F(j,0,1) F(k,0,isin[l.dn]) qwq.f[i][j]=(qwq.f[i][j]+l.f[i][k]*r.f[k][j])%MOD;
	else F(i,0,1) F(j,0,1) F(k,0,isin[r.dn]) qwq.f[i][j]=(qwq.f[i][j]+l.f[i][j]*r.f[i][k])%MOD;
	return;
}
inline void psd(int now){
	Node&qwq(node[now]),&l(node[qwq.lc]),&r(node[qwq.rc]);
	if(node[now].type){
		/*
		转置前 
		F(i,0,1) F(j,0,1) F(k,0,isin[l.dn]){
			qwq.g[i][j]=(qwq.g[i][j]+l.f[i][k]*r.g[k][j])%MOD;
			qwq.g[i][j]=(qwq.g[i][j]+l.g[i][k]*r.f[k][j])%MOD;
		}
		*/ 
		F(i,0,1) F(j,0,1) F(k,0,isin[l.dn]){
			r.g[k][j]=(r.g[k][j]+l.f[i][k]*qwq.g[i][j])%MOD;
			l.g[i][k]=(l.g[i][k]+r.f[k][j]*qwq.g[i][j])%MOD;
		}//倒不倒序不影响 
	}else{
		/*
		转置前 
		F(i,0,1) F(j,0,1) F(k,0,isin[r.dn]){
			qwq.g[i][j]=(qwq.g[i][j]+l.f[i][j]*r.g[i][k])%MOD;
			qwq.g[i][j]=(qwq.g[i][j]+l.g[i][j]*r.f[i][k])%MOD;
		}
		*/ 
		F(i,0,1) F(j,0,1) F(k,0,isin[r.dn]){
			r.g[i][k]=(r.g[i][k]+l.f[i][j]*qwq.g[i][j])%MOD;
			l.g[i][j]=(l.g[i][j]+r.f[i][k]*qwq.g[i][j])%MOD;
		}
	}
	memset(qwq.g,0,sizeof(qwq.g));
	return;
}
inline int merge(int x,int y,int type){
	node[x].fa=node[y].fa=++cnt;
	node[cnt]=Node(type,node[x].up,node[type?y:x].dn,node[x].siz+node[y].siz,x,y);
	return upd(cnt),cnt;
}
#define Poi vector<int>::iterator
int build(Poi l,Poi r,int type){
	if(l==r) return 0;
	if(l+1==r) return *l;
	int sum=0,all=0;
	for(auto it=l;it!=r;++it) all+=node[*it].siz;
	Poi mid=l+1;
	for(auto it=l;it!=r;++it){
		sum+=node[*it].siz;
		if(sum*2<=all) mid=it+1;
		else break;
	}
	return merge(build(l,mid,type),build(mid,r,type),type);
}
int siz[MAXN],fa[MAXN],son[MAXN],rt[MAXN];//根为 cnt 
vector<int>g[MAXN];
void dfs1(int now){
	siz[now]=1;
	node[now]=Node(-1,fa[now],now);
	node[now].f[0][0]=node[now].f[1][0]=node[now].f[0][1]=1;
	isin[now]=1;
	for(int i:g[now]){
		dfs1(i);
		siz[now]+=siz[i];
		if(siz[son[now]]<siz[i]) son[now]=i;
	}
	return;
}
void dfs2(int now,bool heavy){
	if(son[now]) dfs2(son[now],1);
	for(int i:g[now]) if(i!=son[now]) dfs2(i,0);
	if(!heavy){
		vector<int>chain;
		chain.push_back(now);
		for(int i(son[now]);i;i=son[i]){
			vector<int>sub({i});
			for(int j:g[fa[i]]) if(j!=i) sub.push_back(rt[j]);
			chain.push_back(build(sub.begin(),sub.end(),0));
		}
		rt[now]=build(chain.begin(),chain.end(),1);
	}
	return;
}
int ans[MAXN];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	F(i,2,n) cin>>fa[i],g[fa[i]].push_back(i);
	F(i,1,n) cin>>val[i].fi,val[i].se=i;
	sort(val+1,val+n+1);
	dfs1(1),cnt=n,dfs2(1,0);
	R(i,n,1){//倒序执行 
		/*
		转置前 (ans[i]-ans[i-1])+=node[cnt].g[0][0/1] 
		转置后倒序变成 node[cnt].g[0][0/1]+=val[i]-val[i+1] 
		*/
		node[cnt].g[0][0]=(node[cnt].g[0][0]+val[i].fi-val[i+1].fi+MOD)%MOD;
		if(isin[node[cnt].dn]) node[cnt].g[0][1]=(node[cnt].g[0][1]+val[i].fi-val[i+1].fi+MOD)%MOD;
		int now=val[i].se,tp=0;
		static int path[MAXN];
		while(node[now].fa) path[++tp]=node[now].fa,now=path[tp];
		R(j,tp,1) psd(path[j]);//从上到下倒序执行 
		now=val[i].se;
		ans[now]=node[now].g[0][1],node[now].g[0][1]=0;//node[now].g[0][1]=val[now]的转置 
		isin[now]=0,node[now].f[0][1]=0; 
		while(node[now].fa) upd(node[now].fa),now=node[now].fa;//按原顺序更新常量(矩阵里的量) 
	}
	F(i,1,n) cout<<ans[i]<<" "; 
	return 0;
}

Day 100

现在不能公开。

后记

100 天蜜月日记完结撒花!

评论

0 条评论,欢迎与作者交流。

正在加载评论...