2024.8 做题记录

2024.8.1

QOJ5017 相等树链

题意

给定 $2$ 棵点集均为 $\{1,2,⋯,n\}$ 的树 $T_1,T_2$。问有多少个点的非空子集在 $T_1,T_2$ 上均为一条链。

一个点的子集 $S$ 在 $T_i$ 上为一条链,当且仅当存在 $1≤x≤y≤n$,在 $T_i$ 上从 $x$ 到 $y$ 的最短路径经过的点集恰好为 $S$。

\(n\le 2\times 10^5\)

题解

容易用异或哈希判断两条链是否合法。

链的问题,考虑点分治处理。对第一棵树点分治。设 \(a_x\) 为第一棵树上从分治中心到 \(x\) 经过的链在第二棵树上深度最大的在 \(x\) 子树中的值,\(b_x\) 第二棵树上深度最大的不在 \(x\) 子树中的点。\(a_x\)\(b_x\) 都可以用线段树求出。那么容易发现。第二棵树上的端点一定是在 \(a_x,b_x,a_y,b_y\) 中选的两个。可以用哈希表维护异或哈希的值,统计有多少条链。注意特判好 \(b_x\) 为分治中心的情况。

但是这样会算错,原因在于如果链在第二棵树上没经过分治中心,就会算错。所以要特判 \(a_x\)\(a_y\) 在第二棵树上同属分治中心同一个子树的情况,这个数量也是可以用哈希表维护的。

哈希表视作 \(O(1)\) 的话,复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
const int N=2e5+5,M=2e9;
#define fi first
#define se second
#define mkp make_pair
#define psb push_back
typedef pair<int,int> pii;
typedef unsigned long ULL;
mt19937_64 gen(0);
gp_hash_table<ULL,int>mp0,mp1,mp2,mp[N];
int n,vs[N],vv[N],sz[N],rt,idx=0,k,d[N],a[N],b[N],mn,st[N],tp,ss[N],in[N],fr[N],id[N];
pii tr[N<<2];
ULL h1[N],vl[N],h2[N],ans,sum;
vector<int>g[N],h[N];
void upd(int x,pii y)
{
	tr[x+=k]=y;
	for(x>>=1;x;x>>=1)
		tr[x]=max(tr[x<<1],tr[x<<1|1]);
}
pii ask(int l,int r)
{
	pii mx=mkp(-1,-1);
	if(l>r)
		return mx;
	for(l+=k-1,r+=k+1;l^r^1;l>>=1,r>>=1)
	{
		if(~l&1)
			mx=max(mx,tr[l^1]);
		if(r&1)
			mx=max(mx,tr[r^1]);
	}
	return mx;
}
void find(int x,int y,int n)
{
	sz[x]=1;
	int mx=0;
	for(int v:g[x])
		if(!vs[v]&&v^y)
			find(v,x,n),mx=max(mx,sz[v]),sz[x]+=sz[v];
	mx=max(mx,n-sz[x]);
	if(mx<mn)
		mn=mx,rt=x;
}
int findrt(int x,int n)
{
	mn=M;
	find(x,0,n);
	return rt;
}
void dfs(int x,int y)
{
	h1[x]=h1[y]^vl[x];
	vv[st[++tp]=x]=sz[x]=1;
	for(int v:g[x])
		if(v^y&&!vs[v])
			dfs(v,x),sz[x]+=sz[v];
}
void sou(int x,int y,int tp)
{
	fr[x]=tp;
	h2[x]=h2[y]^vl[x];
	ss[x]=1,in[x]=++idx,d[x]=d[y]+1;
	vv[x]=2;
	for(int v:h[x])
		if(vv[v]&&v^y)
			sou(v,x,tp),ss[x]+=ss[v];
}
void qry(int x,int y,int tp)
{
	if(vv[x]==1)
		return;
	upd(in[x],mkp(d[x],x));
	int k=fr[x];
	a[x]=max(ask(1,in[k]-1),ask(in[k]+ss[k],idx)).se,b[x]=ask(in[k],in[k]+ss[k]-1).se;
	ans+=(vl[tp]^h1[x])==(h2[a[x]]^h2[b[x]]);
	if(a[x]^tp)
		ans+=mp1[h1[x]^h2[a[x]]];
	ans+=mp0[h1[x]^h2[a[x]]^h2[b[x]]];
	ans+=mp1[h1[x]^h2[b[x]]];
	ans+=mp2[h1[x]];
	ans-=mp[id[k]][h1[x]^h2[b[x]]];
	for(int v:g[x])
		if(v^y&&!vs[v])
			qry(v,x,tp);
	upd(in[x],mkp(-1,-1));
}
void add(int x,int y,int tp)
{
	if(!vv[x])
		return;
	mp0[h1[x]]++;
	mp1[h1[x]^h2[b[x]]]++;
	mp[id[fr[x]]][h1[x]^h2[b[x]]]++;
	if(a[x]^tp)
		mp1[h1[x]^h2[a[x]]]++;
	mp2[h1[x]^h2[a[x]]^h2[b[x]]]++;
	for(int v:g[x])
		if(v^y&&!vs[v])
			add(v,x,tp);
}
void solve(int x)
{
	dfs(x,tp=idx=0);
	d[x]=0,h2[x]=vl[x],in[x]=idx=ss[x]=1;
	int s=0;
	for(int v:h[x])
		if(vv[v])
			sou(v,x,v),id[v]=s++;
	for(int i=0;i<h[x].size();i++)
		mp[i].clear();
	k=1;
	while(k<=idx)
		k<<=1;
	mp0.clear(),mp1.clear(),mp2.clear();
	upd(1,mkp(0,x));
	for(int v:g[x])
	{
		if(vs[v])
			continue;
		qry(v,x,x);
		add(v,x,x);
	}
	upd(1,mkp(-1,-1));
	for(int i=1;i<=tp;i++)
		vv[st[i]]=0;
	vs[x]=1;
	for(int v:g[x])
		if(!vs[v])
			solve(findrt(v,sz[v]));
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		vl[i]=gen(),sum^=vl[i];
	for(int i=2,u;i<=n;i++)
		scanf("%d",&u),g[u].psb(i),g[i].psb(u);
	for(int i=2,u;i<=n;i++)
		scanf("%d",&u),h[u].psb(i),h[i].psb(u);
	solve(findrt(1,n));
	cerr<<1.0*clock()/CLOCKS_PER_SEC<<endl;
	printf("%llu",ans+n);
}

2024.8.2

Luogu P6109 rprmq1

题意

有一个 \(n \times n\) 的矩阵 \(a\),初始全是 \(0\),有 \(m\) 次修改操作和 \(q\) 次查询操作,先进行所有修改操作,然后进行所有查询操作。

一次修改操作会给出 \(l_1,l_2,r_1,r_2,x\),代表把所有满足 \(l_1 \le i \le r_1\)\(l_2 \le j \le r_2\)\(a_{i,j}\) 元素加上一个值 \(x\)

一次查询操作会给出 \(l_1,l_2,r_1,r_2\),代表查询所有满足 \(l_1 \le i \le r_1\)\(l_2 \le j \le r_2\)\(a_{i,j}\) 元素的最大值。

\(1\leq n,m\leq 5\times 10^4\)\(1\leq q \leq 5\times 10^5\)\(1\leq x\leq 2147483647\)\(1\leq l_1\leq r_1\leq n\)\(1\leq l_2\leq r_2\leq n\)

题解

差分,把一次操作查分成在 \(l_1\) 处加入 \((l_2,r_2,x)\),在 \(r_1+1\) 处加入 \((l_2,r_2,-x)\)

考虑分治,找到一个分治中心,处理经过中心的所有询问。

对于覆盖这整个区间的所有区间,把他永久加入线段树里面。在进行扫描线,将询问劈成了左右两部分,发现按顺序进行操作的话,需要支持的操作是区间加和历史最大值,容易用历史最大值线段树维护。

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mkp make_pair
#define psb push_back
typedef pair<int,int> pii;
typedef long long LL;
const int N=5e4+5,K=5e5+5;
const LL M=1e18;
struct node{
	int x,y,k;
};
struct matrix{
	LL a00,a01,a11;
	matrix operator*(matrix&m)const{
		return (matrix){a00+m.a00,max({-M,a00+m.a01,a01+m.a11}),max(a11+m.a11,-M)};
	}
}tr[N<<2],tg[N<<2];
vector<node>g[N];
vector<int>h[N<<2],p[N],q[N];
int n,m,T;
LL ans[K];
pii a[K],b[K];
void psd(int o)
{
	tr[o<<1]=tr[o<<1]*tg[o];
	tr[o<<1|1]=tr[o<<1|1]*tg[o];
	tg[o<<1]=tg[o<<1]*tg[o];
	tg[o<<1|1]=tg[o<<1|1]*tg[o];
	tg[o]=(matrix){0,-M,0};
}
void psup(int o)
{
	tr[o].a00=max(tr[o<<1].a00,tr[o<<1|1].a00);
	tr[o].a01=max(tr[o<<1].a01,tr[o<<1|1].a01);
}
LL ask(int o,int l,int r,int x,int y,int op)
{
	if(x<=l&&r<=y)
		return op? tr[o].a01:tr[o].a00;
	psd(o);
	int md=l+r>>1;
	LL mx=0;
	if(md>=x)
		mx=ask(o<<1,l,md,x,y,op);
	if(md<y)
		mx=max(mx,ask(o<<1|1,md+1,r,x,y,op));
	return mx;
}
void upd(int o,int l,int r,int x,int y,matrix k)
{
	if(x<=l&&r<=y)
		return tr[o]=tr[o]*k,tg[o]=tg[o]*k,void();
	int md=l+r>>1;
	psd(o);
	if(md>=x)
		upd(o<<1,l,md,x,y,k);
	if(md<y)
		upd(o<<1|1,md+1,r,x,y,k);
	psup(o);
}
void solve(int o,int l,int r)
{
	static int qu[K],tp;
	if(l==r)
	{
		for(node j:g[l])
			upd(1,1,n,j.x,j.y,(matrix){j.k,-M,0});
		for(int j:h[o])
			ans[j]=ask(1,1,n,b[j].fi,b[j].se,0);
		return;
	}
	int md=l+r>>1;
	for(int j:h[o])
	{
		if(a[j].se<=md)
			h[o<<1].psb(j);
		if(a[j].fi>md)
			h[o<<1|1].psb(j);
	}
	solve(o<<1,l,md);
	tp=0;
	for(int j:h[o])
		if(a[j].fi<=md&&a[j].se>md)
			qu[++tp]=j;
	for(int i=l;i<=r;i++)
		p[i].clear(),q[i].clear();
	if(tp)
	{
		for(int i=1;i<=tp;i++)
		{
			p[a[qu[i]].fi].psb(qu[i]);
			q[a[qu[i]].se].psb(qu[i]);
		}
		matrix k;
		for(int i=md;i>=l;i--)
		{
			k=(matrix){0,0,0};
			tr[1]=tr[1]*k,tg[1]=tg[1]*k;
			for(int j:p[i])
				ans[j]=ask(1,1,n,b[j].fi,b[j].se,1);
			for(node j:g[i])
				upd(1,1,n,j.x,j.y,(matrix){-j.k,-M,0});
		}
		k=(matrix){0,-M,-M};
		for(int i=l;i<=md;i++)
			for(node j:g[i])
				upd(1,1,n,j.x,j.y,(matrix){j.k,-M,0});
		tr[1]=tr[1]*k,tg[1]=tg[1]*k;
		for(int i=md+1;i<=r;i++)
		{
			for(node j:g[i])
				upd(1,1,n,j.x,j.y,(matrix){j.k,-M,0});
			k=(matrix){0,0,0};
			tr[1]=tr[1]*k,tg[1]=tg[1]*k;
			for(int j:q[i])
				ans[j]=max(ans[j],ask(1,1,n,b[j].fi,b[j].se,1));
		}
		k=(matrix){0,-M,-M};
		for(int i=md+1;i<=r;i++)
			for(node j:g[i])
				upd(1,1,n,j.x,j.y,(matrix){-j.k,-M,0});
		tr[1]=tr[1]*k,tg[1]=tg[1]*k;
	}
	solve(o<<1|1,md+1,r);
}
int main()
{
	scanf("%d%d%d",&n,&m,&T);
	for(int i=1,l,r,x,y,k;i<=m;i++)
	{
		scanf("%d%d%d%d%d",&l,&x,&r,&y,&k);
		g[l].push_back((node){x,y,k});
		g[r+1].push_back((node){x,y,-k});
	}
	for(int i=1;i<=T;i++)
		scanf("%d%d%d%d",&a[i].fi,&b[i].fi,&a[i].se,&b[i].se),h[1].push_back(i);
	fill(tr+1,tr+4*n+1,(matrix){0,-M,0});
	fill(tg+1,tg+4*n+1,(matrix){0,-M,0});
	solve(1,1,n);
	for(int i=1;i<=T;i++)
		printf("%lld\n",ans[i]);
}

AGC039F

题意

  • 有一个大小为 \(N \times M\) 的矩阵。矩阵中每个数的取值都是 \([1, K]\)
  • 对于一个矩阵,定义函数 \(f(x,y)\) 为:第 \(x\) 行和第 \(y\) 列的一共 \(N + M - 1\) 个数中的最小值。
  • 对于一个矩阵,定义其权值为 \(\prod_{x=1}^{N}\prod_{y=1}^{M}f(x,y)\)
  • 你需要求出,对于所有 \(K^{NM}\) 种矩阵,每个矩阵的权值和对 \(D\) 取模的结果。
  • \(1 \leq N, M, K \leq 100\)\(10^8 \leq D \leq 10^9\),保证 \(D\) 为质数。

题解

填矩阵太麻烦了,我们考虑直接填出序列 \(A\)\(B\) 表示每行每列的最小值。

怎么求序列对应的矩阵数量呢?可以考虑从小到大填数,那么每次在剩下的位置里面随便选。如果知道了 \(A\)\(B\),这是可以维护的。

但是这样并没有保证每行每列的最小值为 \(A\)\(B\),只是保证里不超过 \(A\)\(B\),所以还需要容斥掉最小值小于 \(A\) 的或者小于 \(B\) 的。

现在要计数,也考虑从小到大填进去。填进去的时候统计贡献。枚举填进去那个数,定义 \(dp_{i,j}\) 表示有 填了 \(i\)\(j\) 列,分成4步,第一步填入最小值为 \(k\) 的有多少行,第二部填最小值为 \(k\) 的有多少列,然后再填容斥的最小值为 \(k\) 的行列。

#include<bits/stdc++.h>
using namespace std;
const int N=205;
int n,m,a,P,dp[2][N][N],C[N][N],pw[N][N],op;
void clr(int op)
{
	memset(dp[op],0,sizeof(dp[op]));
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&a,&P);
	for(int i=0;i<N;i++)
	{
		pw[i][0]=C[i][0]=C[i][i]=1;
		for(int j=1;j<i;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
		for(int j=1;j<N;j++)
			pw[i][j]=1LL*pw[i][j-1]*i%P;
	}
	dp[0][0][0]=1;
	for(int i=1;i<=a;i++)
	{
		clr(op^=1);
		for(int j=0;j<=n;j++)
		{
			for(int k=0;k<=m;k++)
			{
				int x=pw[i][m-k]*1LL*pw[a-i+1][k]%P;
				for(int c=0,p=1;c+j<=n;c++,p=1LL*p*x%P)
					(dp[op][j+c][k]+=C[n-j][c]*1LL*dp[op^1][j][k]%P*p%P)%=P;
			}
		}
		clr(op^=1);
		for(int j=0;j<=n;j++)
		{
			for(int k=0;k<=m;k++)
			{
				int x=pw[i][n-j]*1LL*pw[a-i+1][j]%P;
				for(int c=0,p=1;c+k<=m;c++,p=1LL*p*x%P)
					(dp[op][j][k+c]+=C[m-k][c]*1LL*dp[op^1][j][k]%P*p%P)%=P;
			}
		}
		clr(op^=1);
		for(int j=0;j<=n;j++)
		{
			for(int k=0;k<=m;k++)
			{
				int x=P-pw[i][m-k]*1LL*pw[a-i][k]%P;
				for(int c=0,p=1;c+j<=n;c++,p=1LL*p*x%P)
					(dp[op][j+c][k]+=C[n-j][c]*1LL*dp[op^1][j][k]%P*p%P)%=P;
			}
		}
		clr(op^=1);
		for(int j=0;j<=n;j++)
		{
			for(int k=0;k<=m;k++)
			{
				int x=P-pw[i][n-j]*1LL*pw[a-i][j]%P;
				for(int c=0,p=1;c+k<=m;c++,p=1LL*p*x%P)
					(dp[op][j][k+c]+=C[m-k][c]*1LL*dp[op^1][j][k]%P*p%P)%=P;
			}
		}
	}
	printf("%d\n",dp[op][n][m]);
}

2024.8.3

AGC036F Square Constraints

题意

给你一个整数\(N\),求有多少\(0,1,2...2N-1\)(\(2N\)个数)的排列\(P\),满足:

对于任意\(i(0<=i<=2N-1)\),有\(N^2<=i^2+P_{i}^2<=(2N)^2\)

输出答案对\(M\)取模的结果(\(M\)是输入的)\(N\le 250,M\le 10^9\)

题解

考虑只有上界的时候,可以把所有上界排序后,按照上界从小往大填。

有下界的话,考虑把下界容斥掉。

问题在于怎么排序。把所有位置分成三类。

  1. \(N<i\le 2N\) 的,这一段没有下界

  2. \(1\le i\le N\) 并没有容斥的,这一段一定是排序后最后的一段

  3. \(1\le i\le N\) 并钦定了容斥的,这一段会和第一段进行归并。

所以有了一个思路,枚举总共容斥了多少个数,定义 \(dp_{i,j}\) 表示现在选了 \(j\) 个数容斥,考虑 \(i\) 个数往后。转移的时候先转移第2和第3类,然后加入需要加入的第1类。

#include<bits/stdc++.h>
using namespace std;
const int N=255;
int n,P,l[N<<1],r[N<<1],ans,dp[2][N],op,m;
void clr(int op)
{
	memset(dp[op],0,sizeof(dp[op]));
}
int main()
{
	scanf("%d%d",&n,&P),m=2*n-1;
	for(int i=0;i<2*n;i++)
	{
		if(i<n)
			l[i]=sqrt(n*n-i*i-1)+1;
		r[i]=min((int)sqrt(4*n*n-i*i),m)+1;
	}
	for(int i=0,x,s;i<=n;i++)
	{
		memset(dp[0],0,sizeof(dp[0]));
		dp[op=0][0]=1;
		x=2*n-1;
		for(int j=n-1,ls;~j;j--)
		{
			clr(op^=1);
			ls=x;
			while(x>=n&&r[x]<=l[j])
				--x;
			for(int k=0;k<=i;k++)
			{
				dp[op][k]=dp[op^1][k]*1LL*max(0,r[j]-(m+1-(j+1-(i-k))))%P;
				for(int y=x+1;y<=ls;y++)
					dp[op][k]=1LL*dp[op][k]*max(0,r[y]-(k+m-y))%P;
				if(k)
				{
					int s=dp[op^1][k-1]*1LL*max(0,l[j]-(m-x+k-1))%P;
					for(int y=x+1;y<=ls;y++)
						s=1LL*s*max(0,r[y]-(k-1+m-y))%P;
					(dp[op][k]+=s)%=P;
				}
			}
		}
		s=i&1? P-dp[op][i]:dp[op][i];
		while(x>=n)
			s=1LL*s*(r[x]-(i+m-x))%P,--x;
		(ans+=s)%=P;
	}
	printf("%d",ans);
}

2024.8.4

LOJ6222 幂数!(加强版)

题意

求不超过 \(N\) 的 Powerful number 的数量和所有 Powerful Number 之和。\(N\le 10^{25}\)

题解

首先有 Powerful Number 是 \(O(\sqrt N)\) 的结论,直接搜索可以过普通版。

考虑所有 Powerful Number 一定可以表示成 \(a^3b^2\) 的形式。如果强制限制 \(\mu^2(a)=1\),那么所有的 Poweful Number 只有一种表示方式。

如果暴力枚举 \(a\),复杂度 \(O(N^{\frac 13})\),过不了。

考虑优化。当 \(a\le B\) 的时候,枚举 \(a\)。当 \(a>B\) 的时候,\(b\) 不超过 \(\sqrt{\frac N{B^3}}\),枚举 \(b\)后,需要快速计算 \(\mu^2\) 的前缀和。

有结论:\(\mu^2(i)=\sum\limits_{d^2|i}\mu(d)\),把这个式子拆开就可以在 \(O(\sqrt N)\) 计算 \(\mu^2\) 的前 \(N\) 项的和。

复杂度不是很会算,但是肯定能过。

#include<bits/stdc++.h>
using namespace std;
const int M=1e7,N=1e6;
typedef __int128 LLL;
typedef long long LL;
LLL n,s2;
int pr[M],pp[M],m,mu[M];
LL s1;
void read(LLL&n)
{
	char ch=getchar();
	while(ch>='0'&&ch<='9')
		n=n*10+ch-48,ch=getchar();
}
void write(LLL n)
{
	if(n)
		write(n/10),putchar(n%10+48);
}
LL mysqrt(LLL k)
{
	LL l=0,r=3162277660200LL,md;
	while(l<=r)
	{
		md=l+r>>1;
		if((LLL)md*md<=k)
			l=md+1;
		else
			r=md-1;
	}
	return r;
}
int mysqrt3(LLL k)
{
	int l=0,r=1000000000,md;
	while(l<=r)
	{
		md=l+r>>1;
		if((LLL)md*md*md<=k)
			l=md+1;
		else
			r=md-1;
	}
	return r;
}
int slv1(int k)
{
	int s1=0;
	for(int i=1;i*i<=k;i++)
		if(mu[i])
			s1+=mu[i]*(k/i/i);
	return s1;
}
LLL S(int t)
{
	return (LLL)t*(t+1)*t*(t+1)/4;
}
LLL slv2(int k)
{
	LLL s2=0;
	for(int i=1;i*i<=k;i++)
		if(mu[i])
			s2+=(LLL)mu[i]*i*i*i*i*i*i*S(k/i/i);
	return s2;
}
int main()
{
	read(n);
	mu[1]=1;
	for(int i=2;i<M;i++)
	{
		if(!pr[i])
			mu[pp[++m]=i]=-1;
		for(int j=1;i*pp[j]<M&&j<=m;j++)
		{
			pr[i*pp[j]]=1;
			if(i%pp[j]==0)
				break;
			mu[i*pp[j]]=-mu[i];
		}
	}
	for(int i=1;i<N;i++)
	{
		if(mu[i])
		{
			LL k=mysqrt(n/i/i/i);
			s1+=k;
			s2+=1LL*i*i*i*((LLL)k*(k+1)*(2*k+1)/6);
		}
	}
	for(int i=1;(LLL)i*i*N*N*N<=n;i++)
	{
		int k=mysqrt3(n/i/i);
		s1+=slv1(k)-slv1(N-1);
		s2+=(slv2(k)-slv2(N-1))*i*i;
	}
	printf("%lld\n",s1);
	s2? write(s2),0:puts("0");
}

2024.8.6

QOJ7419 Jiry Matchings

题意

给定一棵 \(n\) 个点带边权的树,对于 \(1\le k<n\),求树上选 \(k\) 个点的匹配,最大权值为多少。

\(n\le 2\times 10^5\)

题解

好像有高妙的模拟费用流做法,但是我不会。

但是可以费用流,最大匹配是有 dp 做法的。所以考虑闵可夫斯基优化 dp。

直接优化肯定优化不了,考虑树剖。先考虑把轻子树合并起来,定义 \(dp_{u,0/1}\) 表示轻子树所连的哪个重链上的点是否有连到轻子树上,方案的数组。合并可以把所有轻子树按照哈夫曼树的合并顺序闵可夫斯基合并起来,这里复杂度是均摊 \(O(n\log n)\) 的。

现在考虑重链的情况,也是使用分治,定义 \(dp_{u,0/1,0/1}\) 表示现在在点 \(u\) ,左端点有没有被选,右端点有没有被选。这个也是可以用分治合并的。如果分治直接取中点,复杂度是 \(O(n\log^2n)\) 的,但是如果取带权中点复杂度就是 \(O(n\log n)\) 的。

所以总复杂度可以做到 \(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mkp make_pair
typedef long long LL;
typedef pair<int,int> pii;
const int N=6e5+5;
const LL M=1e18;
int n,fa[N],son[N],sz[N],st[N],w[N],idx,tp,sum;
vector<pii>g[N];
vector<LL>h[N][2],p[N][2][2];
void sou(int x,int y)
{
	sz[x]=1,fa[x]=y;
	for(pii v:g[x])
	{
		if(v.fi^y)
		{
			sou(v.fi,x),sz[x]+=sz[v.fi];
			if(sz[v.fi]>sz[son[x]])
				son[x]=v.fi;
		}
	}
}
void tomx(vector<LL>&a,vector<LL>b,int op=0)
{
	a.resize(max(a.size(),b.size()+op),-M);
	for(int i=0;i<b.size();i++)
		a[i+op]=max(a[i+op],b[i]);
}
int cmp(LL x,LL y)
{
	return x>y;
}
void doit(vector<LL> a,vector<LL> b,vector<LL>&c,LL k=0,int op=0)
{
	if(a.empty()||b.empty())
		return;
	vector<LL>p(a.size()+b.size()-1);
	for(int i=a.size()-1;i;--i)
		a[i]-=a[i-1];
	for(int i=b.size()-1;i;--i)
		b[i]-=b[i-1];
	merge(a.begin()+1,a.end(),b.begin()+1,b.end(),p.begin()+1,cmp);
	p[0]=a[0]+b[0];
	for(int i=1;i<p.size();i++)
		p[i]+=p[i-1];
	if(k)
		for(int i=0;i<p.size();i++)
			p[i]+=k;
	tomx(c,p,op);
}
void mgeson(int u,int a,int b)
{
	h[u][0].clear(),h[u][1].clear();
	doit(h[a][0],h[b][0],h[u][0]);
	doit(h[a][0],h[b][1],h[u][1]);
	doit(h[a][1],h[b][0],h[u][1]);
}
int slv(int l,int r)
{
	if(l==r)
	{
		++idx;
		p[idx][0][1]=p[idx][1][0]=p[idx][0][0]=h[st[l]][0];
		p[idx][1][1]=h[st[l]][1];
		tomx(p[idx][1][1],p[idx][1][0]);
		return idx;
	}
	int u=++idx,md,mn=N,s=0,t=0,x,y;
	for(int i=l;i<=r;i++)
		s+=sz[st[i]]-sz[son[st[i]]];
	for(int i=l;i<r;i++)
	{
		t+=sz[st[i]]-sz[son[st[i]]];
		if(max(t,s-t)<mn)
			mn=max(t,s-t),md=i;
	}
	x=slv(l,md),y=slv(md+1,r);
	p[u][0][0].clear(),p[u][0][1].clear(),p[u][1][0].clear(),p[u][1][1].clear();
	for(int a:{0,1})
	{
		for(int b:{0,1})
		{
			doit(p[x][a][0],p[y][0][b],p[u][a||(l==md)][b||(md+1==r)],w[st[md+1]],1);
			doit(p[x][a][1],p[y][1][b],p[u][a][b]);
		}
	}
	return u;
}
void dfs(int x)
{
	sum+=sz[x];
	priority_queue<pii>q;
	int k=x;
	while(k)
	{
		for(pii j:g[k])
		{
			if(j.fi==fa[k])
				continue;
			if(j.fi==son[k])
			{
				w[son[k]]=j.se;
				continue;
			}
			dfs(j.fi);
			q.push(mkp(-sz[j.fi],j.fi));
			h[j.fi][1].swap(h[j.fi][0]);
			h[j.fi][1].push_back(0);
			for(int i=h[j.fi][1].size()-1;i;--i)
				h[j.fi][1][i]=h[j.fi][1][i-1]+j.se;
			h[j.fi][1][0]=-M;
		}
		idx=n;
		while(q.size()>1)
		{
			int u=++idx,a=q.top().se,b;
			q.pop();
			b=q.top().se;
			q.pop();
			mgeson(u,a,b);
			sum+=sz[a]+sz[b];
			q.push(mkp(-(sz[u]=sz[a]+sz[b]),u));
		}
		if(q.size())
		{
			h[k][0]=h[q.top().se][0],h[k][1]=h[q.top().se][1];
			q.pop();
		}
		else
			h[k][0]=h[k][1]={0};
		k=son[k];
	}
	tp=0;
	k=x;
	while(k)
		st[++tp]=k,k=son[k];
	idx=0;
	sum+=sz[x];
	int t=slv(1,tp);
	h[x][0]=p[t][0][0],tomx(h[x][0],p[t][0][1]);
	h[x][1]=p[t][1][0],tomx(h[x][1],p[t][1][1]);
	tomx(h[x][1],h[x][0]);
}
int main()
{
	scanf("%d",&n);
	for(int i=1,u,v,w;i<n;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
		g[u].emplace_back(v,w);
		g[v].emplace_back(u,w);
	}
	sou(1,0);
	dfs(1);
	for(int i=1;i<h[1][1].size();i++)
		printf("%lld ",h[1][1][i]);
	for(int i=h[1][1].size();i<n;i++)
		printf("? ");
}

QOJ5039

题意

有一个 n 个格子的的转盘,格子上按照任意顺序编号\(\{1, 2,\cdots, n\}\),每个时刻你只能读到转盘最上方位置的编号。为
了找到格子数量 \(n\),你可以执行至多 \(10000\) 次操作,每次可以将转
盘顺时针旋转 \(k\) 个格子(\(k\) 由你指定,但是你看不到转动过程)。

题解

首先有个 \(O(\sqrt n)\) 的做法。考虑 BSGS,先走 \(B\) 次,每次只走一格。然后每次走 \(B\) 个,一定会在某一时刻走回一开始走的那 \(B\) 个数里面,走回去的时候就可以求答案了。询问次数是 \(O(\sqrt n)\) 的。

考虑加入随机化,一开始先随机走 \(B\) 次,那么走出来的数的最大值 \(m\) 期望是 \(\frac B{B+1}n\) 的。然后从再跑 BSGS 的时候,先走 \(B\) 次一样的,此时我们知道 \(n\) 至少为 \(m\),所以可以跳 \(m-B\) 个,然后从这个地方开始 BSGS,询问次数下降到了 \(O(n^{\frac 13})\)

#include<bits/stdc++.h>
using namespace std;
const int M=1e9+1,B=3000,N=1e7+5;
int mx,x,p,n,q[N];
long long s;
map<int,long long>vs;
mt19937 gen(time(0));
int ask(int k)
{
	s+=k;
	printf("walk %d\n",k);
	fflush(stdout);
	scanf("%d",&x);
	return x;
}
int main()
{
	for(int i=1;i<=B;i++)
		mx=max(mx,ask(gen()%M));
	for(int i=1;i<=B;i++)
	{
		int k=ask(1);
		if(vs[k])
			return printf("guess %d\n",s-vs[k]),0;
		vs[k]=s;
	}
	if(mx>B)
		p=ask(mx-B);
	while(!vs[p])
		p=ask(B);
	printf("guess %d\n",s-vs[p]);
}

2024.8.13

QOJ4808 Great Party

题意

有$n$堆石子,第$i$堆有$a_i$个石子。

Alice和Bob在玩游戏,Alice先手轮流操作,定义一次合法的操作包含两个步骤:

  1. 选一个非空石子堆,去掉一些石子(至少1个,至多总个数)
  2. 保留这个石子堆 或者合并进另一堆。

如果一个人没法操作了则输了。

$q$次询问,每次询问给定$L,R$,求有多少个$L\leq l\leq r\leq R$,使得只保留第$[l,r]$堆石子后,Alice必胜。

题解

打表发现, Alice 必胜当且仅当 \(a_i-1\) 的异或和不等于0或者长度为奇数。

有这个结论可以直接莫队算答案。

结论是容易归纳证明的。长度为奇数操作最大一堆归纳变成偶数且异或和等于0,长度为偶数就是 \(a_i-1\) 的 nim 游戏,谁先操作成奇数谁就输。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,B=350;
int n,T,a[N],x=1,y,l[N],r[N],p[1<<20][2],q[2],s[1<<20][2],t[2],id[N];//a,b:l-1 s,t:r
long long ans[N],nw;
long long C(int x)
{
	return x*(x+1LL)/2;
}
int cmp(int x,int y)
{
	if(l[x]/B^l[y]/B)
		return l[x]<l[y];
	return l[x]/B&1? r[x]<r[y]:r[x]>r[y];
}
void addl(int k,int op)
{
	s[a[k]][k&1]+=op;
	t[k&1]+=op;
	nw+=op*(t[k&1^1]-s[a[k-1]][k&1^1]);
	p[a[k-1]][k&1]+=op,q[k&1]+=op;
}
void addr(int k,int op)
{
	s[a[k]][k&1]+=op;
	t[k&1]+=op;
	nw+=op*(q[k&1^1]-p[a[k]][k&1^1]);
	p[a[k-1]][k&1]+=op,q[k&1]+=op;
}
int main()
{
	scanf("%d%d",&n,&T);
	for(int i=1;i<=n;i++)
		scanf("%d",a+i),a[i]=a[i]-1^a[i-1];
	for(int i=1;i<=T;i++)
		scanf("%d%d",l+i,r+i),id[i]=i;
	sort(id+1,id+T+1,cmp);
	for(int i=1;i<=T;i++)
	{
		while(y<r[id[i]])
			addr(++y,1);
		while(x>l[id[i]])
			addl(--x,1);
		while(y>r[id[i]])
			addr(y--,-1);
		while(x<l[id[i]])
			addl(x++,-1);
		ans[id[i]]=nw;
	}
	for(int i=1;i<=T;i++)
		printf("%lld\n",ans[i]+C(r[i]/2-(l[i]-1)/2)+C((r[i]+1)/2-l[i]/2));
}

2024.8.14

QOJ5100 卡牌游戏

题意

你的面前有 $n$ 张卡牌,从左往右数第 $i$ 张上面写着一个正整数 $A_i$。

你将会拿出其中的若干张,保持它们的相对顺序不变,形成一个新的卡牌序列。

不妨设这个新的卡牌序列有 $m$ 张牌,第 $i$ 张上写的数是 $B_i$。

定义这个卡牌序列的价值是:

$$ \sum \limits_{i=1}^{m-1}\lfloor \frac{S}{\textbf{LCM}(B_i,B_{i + 1})}\rfloor \times \textbf{LCM}(B_i,B_{i + 1}) $$

其中 $\textbf{LCM}(x,y)$ 表示 $x$ 和 $y$ 的最小公倍数。

请最大化价值。

对于一些数据,你还需要解决如下问题:

增加禁用规则,如果第 $i$ 张牌被禁用,你拿出的卡牌序列不可以包含它。

你想知道:对于 $i=1...n$,它被禁用后,你可以拿出的卡牌序列的价值最大是多少,设为 $ans_i$。

为了减少输出量,你只需输出 $\bigoplus\limits_{i=1}^ni\times ans_i$。

题解

这东西看起来只能 dp 求了,定义 \(dp_i\) 表示第 \(i\) 个位置选择了,最大价值。暴力可以在 \(O(nS+n^2)\) 的复杂度内求出答案。当 tp=1 的情况下,正反都跑一次 dp,枚举跨过这个位置的两个地方,用 ST 表把这个这两个区间内部的地方取 max,后面再下传下去。

考虑根号分治,假设现在有相邻两个位置的值为 \(a,b\),那么如果 \(a,b\) 当中存在一个位置的值不超过 \(\sqrt S\),那么可以在另一个位置暴力枚举。如果两个都超过 \(\sqrt S\),dp 的时候处理一个数组 \(a\)\(a_i\) 表示 i 的所有超过 \(\sqrt S\) 的因数的dp最大值。\(a\) 数组可以暴力修改。这个做法可以在 \(O(n\sqrt S)\) 的复杂度内解决 \(tp=0\) 的问题。

现在考虑 \(tp=1\) 的情况。如果跨过这个点两边存在一个不超过 \(\sqrt S\),可以直接暴力枚举那一边。如果都超过 \(\sqrt S\),那么记录 \(b_i\) 表示 \(i\) 的因数的上一次出现,\(c_i\) 表示 \(i\) 超过 \(\sqrt S\) 因数的上上次出现。也用 ST 表修改即可。总复杂度 \(O(n\sqrt S)\),常数很大。

考虑优化,注意到 \(\lfloor\frac S{LCM(x,y)}\rfloor LCM(X,Y)\ge \frac S2\),那么如果选择的相邻两个位置中间有超过三个小于 \(\sqrt S\) 的数,一定不是最优的。所以对于 dp 过程中有一边小于 \(\sqrt S\) 的情况,可以往前后找三个不超过 \(\sqrt S\) 的数更新 dp 值。后面部分同理。

复杂度不变,常数变小,可以过了。

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
typedef long long LL;
int n,m,cnt,vs[N],gcd[800][800],a[N],B,ls[N],nx[N],c[N],op;
LL s[N],ans,f[N],g[N],st[20][N];
inline void upd(int l,int r,LL k)
{
	if(l<=r)
	{
		int x=__lg(r-l+1);
		st[x][l]=max(st[x][l],k);
		st[x][r-(1<<x)+1]=max(st[x][r-(1<<x)+1],k);
	}
}
mt19937 gen(time(0));
int main()
{
	scanf("%d%d%d%*d",&n,&m,&op);
	B=sqrt(m);
	for(int i=1;i<B;i++)
	{
		gcd[i][0]=i;
		for(int j=1;j<i;j++)
			gcd[i][j]=gcd[j][i%j];
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",a+i);
		ls[i]=ls[i-1];
		if(a[i-1]<B)
			ls[i]=i-1;
	}
	for(int i=n;i;i--)
	{
		nx[i]=nx[i+1];
		if(a[i+1]<B)
			nx[i]=i+1;
	}
	memset(s,-1,sizeof(s));
	for(int i=1;i<=n;i++)
	{
		f[i]=max(f[i],f[i-1]);
		if(a[i]>m)
			continue;
		if(a[i]>=B)
		{
			int k=i,T=3;
			while(T--&&ls[k])
				k=ls[k],f[i]=max(f[i],f[k]+m-m%(a[k]*a[i]/gcd[a[k]][a[i]%a[k]]));
			for(int j=m/2/a[i]*a[i]+a[i];j<=m;j+=a[i])
				if(~s[j])
					f[i]=max(f[i],s[j]+j);
			for(int j=m/2/a[i]*a[i]+a[i];j<=m;j+=a[i])
				s[j]=max(s[j],f[i]);
		}
		int k=i,T=3;
		while(T--&&nx[k]<=n)
			k=nx[k],f[k]=max(f[k],f[i]+m-m%(a[k]*a[i]/gcd[a[k]][a[i]%a[k]]));
	}
	memset(s,-1,sizeof(s));
	for(int i=n;i;i--)
	{
		g[i]=max(g[i],g[i+1]);
		if(a[i]>m)
			continue;
		if(a[i]>=B)
		{
			int k=i,T=3;
			while(T--&&nx[k]<=n)
				k=nx[k],g[i]=max(g[i],g[k]+m-m%(a[k]*a[i]/gcd[a[k]][a[i]%a[k]]));
			for(int j=m/2/a[i]*a[i]+a[i];j<=m;j+=a[i])
				if(~s[j])
					g[i]=max(g[i],s[j]+j);
			for(int j=m/2/a[i]*a[i]+a[i];j<=m;j+=a[i])
				s[j]=max(s[j],g[i]);
		}
		int k=i,T=3;
		while(T--&&ls[k])
			k=ls[k],g[k]=max(g[k],g[i]+m-m%(a[k]*a[i]/gcd[a[k]][a[i]%a[k]]));
	}
	memset(s,0,sizeof(s));
	for(int i=1;i<=n;i++)
	{
		if(a[i]>m)
			continue;
		int k=i,T=4;
		while(T--&&ls[k])
			k=ls[k],upd(k+1,i-1,f[k]+g[i]+m-m%(a[k]*a[i]/gcd[a[k]][a[i]%a[k]]));
		k=i,T=4;
		while(T--&&nx[k]<=n)
			k=nx[k],upd(i+1,k-1,f[i]+g[k]+m-m%(a[k]*a[i]/gcd[a[k]][a[i]%a[k]]));
		if(a[i]>=B)
		{
			for(int j=m/2/a[i]*a[i]+a[i];j<=m;j+=a[i])
			{
				if(s[j])
					upd(s[j]+1,i-1,f[s[j]]+g[i]+j);
				if(c[j])
					st[0][s[j]]=max(st[0][s[j]],f[c[j]]+g[i]+j);
			}
			for(int j=a[i];j<=m;j+=a[i])
				c[j]=s[j],s[j]=i;
		}
	}
	for(int i=19;i;--i)
		for(int j=1;j+(1<<i)-1<=n;j++)
			st[i-1][j]=max(st[i-1][j],st[i][j]),
				st[i-1][j+(1<<i-1)]=max(st[i-1][j+(1<<i-1)],st[i][j]);
	for(int i=1;i<=n;i++)
		ans^=i*max(st[0][i],f[i-1]+g[i+1]);
	printf("%lld ",g[1]);
	if(op)
		printf("%lld",ans);
}

2024.8.25

QOJ141 8染色

题意

这是一道通信题。

一场考试正在进行。

这次考试的内容非常简单——给定一张 $N$ 个点 $M$ 条边的无向图 $G=(V,E)$,第 $i$ 条边($0 \leq i < M$)的两端点为 $(U_i, V_i)$($0 \leq U_i, V_i < N$)。保证图中不含有重边与自环。考试的任务是给每个点赋一个 $0 \sim 7$ 中的颜色 $C_i$,满足任意一条边的两个端点的颜色不同。

Alice 通过她的神力很快得到了一组合法的染色方案通过了考试。

Bob 也想要得到这样一个方案,于是它决定偷偷与 Alice 进行交流。Alice 可以向 Bob 发送一个长为 $L$ 的 0/1 串 $X$,向 Bob 提示一些信息。随后,Bob 需要构造出任意一个合法的 8 - 染色方案。

\(N\le 2\times 10^5,M\le 5\times10^5,l\le 2.5\times 10^5\)

题解

首先只用传颜色 mod 4 的结果。因为传完这个之后是一个二分图染色问题,是可以做的。

然后只用传度数超过 \(8\) 的点,因为度数小于8的点一定能填。

然后 \(L\) 就不超过 \(2M/8*2\) 了,可以过。

Alice

#include "Alice.h"
#include<bits/stdc++.h>
#define vci vector<int>
using namespace std;
namespace A{
	int in[200005];
	vci slv(int N,int M,vci U,vci V,vci C)
	{
		vci a;
		for(int i:U)
			in[i]++;
		for(int i:V)
			in[i]++;
		for(int i=0;i<N;i++)
			if(in[i]>7)
				a.push_back(C[i]&1),
					a.push_back(C[i]/2&1);
		return a;
	}
}
vci Alice(int N,int M,vci U,vci V,vci C)
{
	return A::slv(N,M,U,V,C);
}

Bob

#include "Bob.h"
#include<bits/stdc++.h>
#define vci vector<int>
using namespace std;
namespace B{
	const int N=2e5+5,M=1e6+5;
	int hd[N],nx[M],v[M],m,in[N];
	vci c;
	void dfs(int x)
	{
		if(in[x]<8)
			return;
		for(int j=hd[x];j;j=nx[j])
			if(c[v[j]]==c[x])
				c[v[j]]^=1,dfs(v[j]);
	}
	void add(int x,int y)
	{
		nx[++m]=hd[x],v[m]=y;
		hd[x]=m,++in[y];
	}
	vci slv(int N,int M,vci U,vci V,vci X)
	{
		c.resize(N);
		for(int i=0;i<M;i++)
			add(U[i],V[i]),add(V[i],U[i]);
		for(int i=0,m=0;i<N;i++)
			if(in[i]>7)
				c[i]=X[m]*2+X[m+1]<<1,m+=2;
		for(int i=0;i<N;i++)
			dfs(i);
		for(int i=0;i<N;i++)
		{
			if(in[i]<8)
			{
				int s=0;
				for(int j=hd[i];j;j=nx[j])
					s|=1<<c[v[j]];
				c[i]=__builtin_ctz(~s);
			}
		}
		return c;
	}
}
vci Bob(int N,int M,vci U,vci V,vci X)
{
	return B::slv(N,M,U,V,X);
}

2024.8.28

UR#13 Sanrd

题意

\(f(x)\) 表示 \(x\) 的次大质因子(不严格),求 \(f(x)\) 的前 \(N\) 项之和

\(N\le 10^{11}\)

题解

先用 min25 筛筛出所有 \(M=N/i\),前 \(M\) 有多少个质数。

考虑min25第二部分,搜索 \(S(x,p)\) 表示考虑前 \(x\) 项,最小质数大于 \(p\) 的答案之和。

枚举下一个选的质数和数量,分两种情况,如果选完这个质数就只剩一个质数了,那就直接统计答案。否则继续递归计算。

复杂度和 min25 筛相同。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+5;
int m,p[N],pr[N],f[N],g[N],k;
LL n,l,r,sp[N],a[N],s2[N];
LL F(LL k,int x)
{
	LL ans=0;
	for(int i=x;1LL*p[i]*p[i]<=k;i++)
		for(LL pw=p[i];pw*p[i]<=k;pw*=p[i])
			ans+=F(k/pw,i+1)+(sp[k/pw<N? f[k/pw]:g[n/k*pw]]-i+1)*p[i];
	return ans;
}
LL S(LL n)
{
	::n=n,m=0;
	for(LL i=1,j;i<=n;i=j+1)
	{
		j=n/(n/i);
		a[++m]=n/i;
		if(n/i<N)
			f[n/i]=m;
		else
			g[j]=m;
		sp[m]=n/i-1;
	}
	for(int i=1;1LL*p[i]*p[i]<=n;i++)
		for(int j=1;j<=m&&a[j]>=1LL*p[i]*p[i];j++)
			sp[j]-=sp[a[j]/p[i]<N? f[a[j]/p[i]]:g[n/a[j]*p[i]]]-i+1;
	return F(n,1);
}
int main()
{
	for(int i=2;i<N;i++)
	{
		if(!pr[i])
			p[++m]=i;
		for(int j=1;i*p[j]<N&&j<=m;j++)
		{
			pr[i*p[j]]=1;
			if(i%p[j]==0)
				break;
		}
	}
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",S(r)-S(l-1));
}

[CTT2021] 小明的树

题意

小明有一棵以 \(1\) 为根的 \(n\) 个节点的树,树上每一个非根节点上有一盏灯,他有一个 \(2 \thicksim n\) 的排列 \(a_1,a_2,\dots,a_{n-1}\)。他还有一个计数器,初始为 \(0\)

他会按照排列依次点亮这 \(n-1\) 盏灯,每进行一次点灯操作后,他会检查整个树是否是美丽的,如果是美丽的,计数器会加上此时点灯的节点形成的连通块的个数。

\(n-1\) 次点灯后计数器的值,记为这棵树的答案。

一个树是美丽的当前仅当对于每一个被点亮的节点,这个节点子树内的节点都是点亮的。

小明认为这个问题太简单了,他觉得应该让树动起来。

在初始查询后,他会删掉树中一条边并加上一条边,保证修改后还是一棵树,他想知道每一次修改后将计数器清零后重新点灯并计数,这棵树的答案是多少。

\(n,m\le 5\times 10^5\)

题解

考虑点边容斥。森林点-边等于连通块数量。

一棵树是美丽的,当且仅当所有没被点亮的节点形成一个连通块,也就是点-边等于1.

点亮的连通块数量是一边黑一边白的边的数量。

所以可以在连边过程中维护每个时间没被点亮的点的点-边数量,连通块数量。要求的就是所有满足点-边等于1的时刻连通块数量之和。

注意点点-边 \(\ge 1\),所以可以直接用线段树维护最小值,最小值数量和最小值权值之和。这个信息是可以打 tag 和维护的。

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
typedef long long LL;
int n,m,u[N],v[N],a[N],p,q,c,d,s1[N<<2],s2[N<<2];
struct node{
	int mn,s;
	LL c;
	node operator+(const node&n)const{
		if(mn<n.mn)
			return (node){mn,s,c};
		if(mn>n.mn)
			return n;
		return (node){mn,s+n.s,c+n.c};
	}
}tr[N<<2];
void psd(int o)
{
	if(s1[o])
	{
		tr[o<<1].mn+=s1[o];
		tr[o<<1|1].mn+=s1[o];
		s1[o<<1]+=s1[o];
		s1[o<<1|1]+=s1[o];
		s1[o]=0;
	}
	if(s2[o])
	{
		tr[o<<1].c+=1LL*s2[o]*tr[o<<1].s;
		tr[o<<1|1].c+=1LL*s2[o]*tr[o<<1|1].s;
		s2[o<<1]+=s2[o];
		s2[o<<1|1]+=s2[o];
		s2[o]=0;
	}
}
void upd1(int o,int l,int r,int x,int y,int z)
{
	if(x>y)
		return;
	if(x<=l&&r<=y)
		return tr[o].mn+=z,s1[o]+=z,void();
	psd(o);
	int md=l+r>>1;
	if(md>=x)
		upd1(o<<1,l,md,x,y,z);
	if(md<y)
		upd1(o<<1|1,md+1,r,x,y,z);
	tr[o]=tr[o<<1]+tr[o<<1|1];
}
void upd2(int o,int l,int r,int x,int y,int z)
{
	if(x>y)
		return;
	if(x<=l&&r<=y)
	{
		tr[o].c+=1LL*z*tr[o].s,s2[o]+=z;
		return;
	}
	psd(o);
	int md=l+r>>1;
	if(md>=x)
		upd2(o<<1,l,md,x,y,z);
	if(md<y)
		upd2(o<<1|1,md+1,r,x,y,z);
	tr[o]=tr[o<<1]+tr[o<<1|1];
}
void build(int o,int l,int r)
{
	tr[o].mn=n-r,tr[o].s=1;
	if(l^r)
	{
		int md=l+r>>1;
		build(o<<1,l,md);
		build(o<<1|1,md+1,r);
	}
}
void add(int x,int y,int op)
{
	if(a[x]>a[y])
		swap(x,y);
	upd1(1,1,n-1,1,a[x]-1,-op);
	upd2(1,1,n-1,a[x],a[y]-1,op);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)
		scanf("%d%d",u+i,v+i);
	a[1]=n;
	for(int i=1,x;i<n;i++)
		scanf("%d",&x),a[x]=i;
	build(1,1,n-1);
	for(int i=1;i<n;i++)
		add(u[i],v[i],1);
	printf("%lld\n",tr[1].c);
	while(m--)
	{
		scanf("%d%d%d%d",&p,&q,&c,&d);
		add(p,q,-1),add(c,d,1);
		printf("%lld\n",tr[1].c);
	}
}
  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值