NFLSOJ #917 -「lych_cys模拟题2018」橘子树(树剖+ODT+莫反统计贡献的思想+动态开点线段树)

题面传送门

sb 出题人不在题面里写 (b_i=0) 导致我挂成零蛋/fn/fn

首先考虑树链剖分将路径问题转化为序列上的问题,因此下文中简称“位置 (i)”表示 DFS 序为 (i) 的点,(a_i,b_i) 分别借指 DFS 为 (i) 的点的 (a_i,b_i)。那么每次操作可以看作将一段区间上的位置清空并加上这段区间上所有位置的贡献。注意到这个清空形式单一,具体来说如果设 (t_i) 表示上一次位置 (i) 被清空的时刻,那么对于一个时刻 (T)(i) 节点上橘子个数显然是 (min(a_i·(T-t_i),b_i))。而清空操作只是改变了一段区间内的点上一次被清空的时间 (t_i)。因此我们考虑用类似于珂朵莉树的思想,开一个 set 维护所有 (t_i) 相同的连续段,然后每次修改相当于将 ([l,r]) 中所有连续段从 set 中删除并加入新的连续段,对于删除的每个形如 ((l,r,t)) 表示 DFS 序区间为 ([l,r]) 上一次被清空的时间为 (t) 的三元组,我们要统计这段区间内所有点的 (f(min(a_i·(T-t),b_i))) 的和,其中 (T) 为这一次清空的时间,由于是计算贡献的和,我们考虑将这些询问离线并差分成两段前缀的贡献,也就是用一个三元组 ((x,v,coef)) 表示统计 (sumlimits_{i=1}^xf(min(a_i·v,b_i))·coef)

考虑怎样求解这个东西,我们考虑将询问挂在 (x) 处,然后从 (1)(n) 遍历每个元素并依次累加每个询问的贡献,那么我们就要思考加入一个元素会对询问的答案产生怎样的贡献,以及怎样通过累加求得的贡献回答每个询问。首先这个 (min) 很烦人,因此我们考虑怎样去掉这样的 (min)。注意到当 (lfloordfrac{a_i}{b_i} floor<v)(min) 会取到 (b_i),否则 (min) 会取到 (a_i·v),因此考虑对这两部分分别计算,对于 (lfloordfrac{a_i}{b_i} floor<v) 的部分相对来说比较容易:我们建一棵树状数组,下标为 (lfloordfrac{a_i}{b_i} floor),每加入一个元素 (i) 就在 (lfloordfrac{a_i}{b_i} floor) 的位置加上 (f(b_i)),那么统计答案相当于统计 ([0,v-1]) 的前缀和,树状数组解决。值得注意的是此题暴力分解 (f(b_i)) 复杂度 (nsqrt{b_i}),无法通过,因此考虑立方分解质因数求解 (f(b_i)):即,考虑先用 ([1,sqrt[3]{b_i}]) 中的质因子取除 (b_i),然后对于 (>sqrt[3]{b_i}) 的部分,显然这样的质因子次数最多为 (2),因此我们只用检验剩余部分是否是完全平方数,如果是则答案乘上剩余部分即可。比较费劲的是 (lfloordfrac{a_i}{b_i} floorge v) 的部分,首先考虑 (f(xy)) 怎样独立写成 (x,y) 的形式,因为这里的 (f(a_i·v)) 如果直接硬着头皮计算则涉及到每个元素对每个询问的贡献,直接做显然复杂度太高。首先 (f(xy)) 中肯定有 (f(x))(f(y)) 的贡献,但是也有可能会多出来一些贡献。显然多出来的这些贡献就是所有在 (x) 中次数为奇数,在 (y) 中次数也是奇数的质数 (p)(p^2) 之积,因此如果设 (S(x)) 表示在 (x) 中出现次数为奇数的质因子集合,那么有

[f(xy)=f(x)f(y)prodlimits_{pin S(x)cap S(y)}p^2 ]

注意到这里 (S(x)cap S(y)) 又涉及两个位置的贡献,不过看到集合取交我们可以很自然地与莫比乌斯反演的等于转包含的套路联系在一起,具体来说,根据

[prodlimits_{xin S}(1+x)=sumlimits_{S'subseteqq S}prodlimits_{xin S'}x ]

(S={p^2-1|pin S(x)cap S(y)}) 可得

[f(xy)=f(x)f(y)sumlimits_{S'subseteqq S(x),S'subseteqq S(y)}prodlimits_{pin S'}(p^2-1) ]

有了这个式子之后,答案统计就相对来说变得容易许多了。先不考虑 (lfloordfrac{a_i}{b_i} floorge v) 这个限制,我们每加入一个 (a_i),就枚举 (S'subseteqq S(a_i)),然后在 (S') 的位置上加上 (prodlimits_{pin S'}(p^2-1)·f(a_i)),注意到 (S'subseteqq S(a_i)) 这个包含关系可以视作整除关系,因此我们也可将 (S') 视作一个自然数 (prodlimits_{xin S'}x),这样就不用 map 等东西维护了,一个数组即可搞定。然后回答询问时就枚举 (S'subseteqq S(v)),然后答案加上 (S') 对应的数的位置上的值 ( imes f(v))。显然这个与我们暴力统计贡献的过程是等价的。那么加上 (lfloordfrac{a_i}{b_i} floorge v) 这个限制怎么办呢?此时简简单单用一个数组维护就不可取了。还是考虑将 (lfloordfrac{a_i}{b_i} floor) 作为下标,然后咱们建立 (3 imes 10^5) 棵动态开点线段树,这样加入一个元素的贡献时,我们还是枚举 (S'subseteqq S(a_i)),然后(S') 对应的数的动态开点线段树上下标为 (lfloordfrac{a_i}{b_i} floor) 的位置加上 (prodlimits_{pin S'}(p^2-1)·f(a_i)),这样查询相当于在待查询位置的动态开点线段树上做后缀和 instead of 直接查询待查询位置上的值。

总复杂度 (mlog^2n·2^{omega(t)}+nsqrt[3]{b_i}),比标算少一个 (nsqrt{T}log T),并且支持计算每个询问的答案。

所以还是建议把标算卡掉(bushi

const int MAXN=1e5;
const int MAXV=3e5;
const int MAXP=MAXV<<8;
int pr[MAXV/6+5],prcnt=0,vis[MAXV+5],mnp[MAXV+5];
vector<int> fac[MAXV+5];
void sieve(int n){
	for(int i=2;i<=n;i++){
		if(!vis[i]) pr[++prcnt]=i,mnp[i]=i;
		for(int j=1;j<=prcnt&&pr[j]*i<=n;j++){
			vis[pr[j]*i]=1;mnp[pr[j]*i]=pr[j];
			if(i%pr[j]==0) break;
		}
	}
	for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=i) fac[j].pb(i);
}
int f[MAXV+5];
ll calc(ll x,int mx){
	if(!x) return 0;
	ll res=1;
	for(int i=1;i<=prcnt;i++){
		if(pr[i]>mx) break;
		int cnt=0;
		while(x%pr[i]==0){cnt++;x/=pr[i];}
		for(int j=1;j<=cnt/2*2;j++) res*=pr[i];
	} int lim=(int)sqrt(x+0.05);
	if(1ll*lim*lim==x) res*=lim,res*=lim;
	return res;
}
int n,m,a[MAXN+5],lim[MAXN+5];ll b[MAXN+5],c[MAXN+5];
link_list<int,MAXN,MAXN*2> g;
int fa[MAXN+5],dep[MAXN+5],siz[MAXN+5],wson[MAXN+5];
int dfn[MAXN+5],top[MAXN+5],tim,rid[MAXN+5];
void dfs1(int x,int f){
	fa[x]=f;siz[x]=1;
	for(int e=g.hd[x];e;e=g.nxt[e]){
		int y=g.val[e];if(y==f) continue;
		dep[y]=dep[x]+1;dfs1(y,x);siz[x]+=siz[y];
		if(siz[y]>siz[wson[x]]) wson[x]=y;
	}
}
void dfs2(int x,int tp){
	top[x]=tp;rid[dfn[x]=++tim]=x;
	if(wson[x]) dfs2(wson[x],tp);
	for(int e=g.hd[x];e;e=g.nxt[e]){
		int y=g.val[e];if(y==fa[x]||y==wson[x]) continue;
		dfs2(y,y);
	}
}
struct itvl{
	int l,r,t;
	itvl(int _l=0,int _r=0,int _t=0):l(_l),r(_r),t(_t){}
	bool operator <(const itvl &rhs) const{
		return l<rhs.l;
	}
};
set<itvl> st;
vector<pii> qv[MAXN+5];
ll res;
void add_qry(int l,int r,int v){
//	printf("add_qry %d %d %d
",l,r,v);
//	for(int i=l;i<=r;i++) res+=calc(min(1ll*a[rid[i]]*v,b[rid[i]]),1259);
	qv[r].pb(mp(v,1));
	if(l^1) qv[l-1].pb(mp(v,-1));
}
void split(int p){
	if(st.empty()) return;
	set<itvl>::iterator it=st.upper_bound(itvl(p,0,0));
//	printf("split %d %d
",x,p);
	if(it==st.begin()) return;--it;
	itvl t=*it;//printf("%d %d
",t.l,t.r);
	if(t.r<=p) return;
	st.erase(st.find(t));
	st.insert(itvl(t.l,p,t.t));
	st.insert(itvl(p+1,t.r,t.t));
}
void pushall(int l,int r,int t){
//	printf("pushall %d %d %d
",l,r,t);
	split(l-1);split(r);
	vector<itvl> del;
	set<itvl>::iterator it=st.lower_bound(itvl(l,0,0));
	while(it!=st.end()&&(it->r)<=r){
		del.pb(*it);it++;
//		printf("{%d,%d,%d}
",it->l,it->r,it->t);
	}
	for(int i=0;i<del.size();i++){
		st.erase(st.find(del[i]));
		add_qry(del[i].l,del[i].r,t-del[i].t);
	} st.insert(itvl(l,r,t));
}
void jumpath(int u,int v,int t){
	while(top[u]^top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		pushall(dfn[top[u]],dfn[u],t);u=fa[top[u]];
	} if(dep[u]<dep[v]) swap(u,v);
	pushall(dfn[v],dfn[u],t);
}
namespace fenwick{
	ll tr[MAXV+5];
	void add(int x,ll v){++x;for(int i=x;i<=MAXV+2;i+=(i&(-i))) tr[i]+=v;}
	ll query(int x){++x;ll ret=0;for(int i=x;i;i&=(i-1)) ret+=tr[i];return ret;}
}
int rt[MAXV+5],ncnt=0,ch[MAXP+5][2];ll val[MAXP+5];
void insert(int &k,int l,int r,int p,ll v){
	if(!k) k=++ncnt;val[k]+=v;if(l==r) return;
	int mid=l+r>>1;
	if(p<=mid) insert(ch[k][0],l,mid,p,v);
	else insert(ch[k][1],mid+1,r,p,v);
}
ll query(int k,int l,int r,int ql,int qr){
	if(!k) return 0;if(ql<=l&&r<=qr) return val[k];
	int mid=l+r>>1;
	if(qr<=mid) return query(ch[k][0],l,mid,ql,qr);
	else if(ql>mid) return query(ch[k][1],mid+1,r,ql,qr);
	else return query(ch[k][0],l,mid,ql,mid)+query(ch[k][1],mid+1,r,mid+1,qr);
}
int main(){
	freopen("d.in","r",stdin);
	freopen("d.out","w",stdout);
	scanf("%d%d",&n,&m);sieve(MAXV);
	for(int i=1;i<=MAXV;i++) f[i]=calc(i,100);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%lld",&b[i]),c[i]=calc(b[i],2000);
//	for(int i=1;i<=n;i++) printf("%lld
",c[i]);
	for(int i=1;i<=n;i++) lim[i]=(!a[i])?(MAXV+1):min(b[i]/a[i],1ll*(MAXV+1));
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),g.ins(u,v),g.ins(v,u);
	dfs1(1,0);dfs2(1,1);st.insert(itvl(1,n,0));
	for(int i=1,u,v,t;i<=m;i++){
		scanf("%d%d%d",&u,&v,&t);
		jumpath(u,v,t);
	}
	for(int i=1;i<=n;i++){
		if(a[rid[i]]){
			fenwick::add(lim[rid[i]],c[rid[i]]);
			int val=f[a[rid[i]]],rst=a[rid[i]]/val;
			vector<int> pr;
			while(rst^1){
				int p=mnp[rst];rst/=p;
				assert(rst%p!=0);pr.pb(p);
			}
			for(int j=0;j<(1<<pr.size());j++){
				ll mul=val,d=1;
				for(int k=0;k<pr.size();k++) if(j>>k&1) mul*=(1ll*pr[k]*pr[k]-1),d*=pr[k];
				insert(rt[d],0,MAXV+1,lim[rid[i]],mul);
			}
		}
		for(pii p:qv[i]){
			int v=p.fi,coef=p.se,ff=f[v];
			int r=v/ff;res+=coef*fenwick::query(v-1);
			for(int fc:fac[r])
				res+=1ll*ff*query(rt[fc],0,MAXV+1,v,MAXV+1)*coef;
		}
	}
	printf("%lld
",res);
	return 0;
}
/*
4 1
2 3 4 27
1000000000 1000000000 1000000000 1000000000
1 2
2 3
3 4
1 4 6
*/
原文地址:https://www.cnblogs.com/ET2006/p/NFLSOJ-917.html