【多校训练】2021牛客多校6

【前言】
打的有点乱,可能也给后面发挥爆炸埋下伏笔。
rk35,校2/9

A. Contracting Convex Hull

【题意】

n n n个半平面在匀速向内移动,有 q q q个询问某个时刻的凸包面积。

n ≤ 1 0 3 , q , t ≤ 1 0 5 n\leq 10^3,q,t\leq 10^5 n103,q,t105

【思路】

收缩时凸包的每个顶点都沿着角平分线匀速移动,所以我们可以利用交点求出每个线段的消失时间和每个顶点的速度(即顶点坐标关于时间的参数方程)

利用顶点的速度求出凸包上的每条线段对总面积的贡献(关于时间的二次函数,系数可以通过数次向量叉乘求出)。

注意到相邻两条角平分线的交点即是对应线段的消失位置,利用链表和优先队列即可模拟整个收缩过程并维护该分段二次函数的系数。

这个东西是 O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn)的,当然实际上我们并不需要动态维护,我们只需要求出线段消失的时间,然后在每次线段消失的时候暴力重构一次凸包,再算出它新的函数就行了。这样复杂度应该是 O ( n 2 + q ) O(n^2+q) O(n2+q)的。

【参考代码】

ddl战神的

#include<bits/stdc++.h>
#define db long double
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
int yh(){
	int ret=0;bool f=0;char c=getchar();
	while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
	while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
	return f?-ret:ret;
}
typedef pair<db,int> pdi;
const int maxn=3e3+5;
struct vc{
	db x,y;
	vc(db x=0,db y=0):x(x),y(y){}
	db mo2(){return x*x+y*y;}
	db mo(){return sqrtl(mo2());}
};
ostream&operator<<(ostream&out,const vc&A){out<<'('<<A.x<<','<<A.y<<')';return out;}
vc operator+(const vc&A,const vc&B){return vc(A.x+B.x,A.y+B.y);}
vc operator-(const vc&A,const vc&B){return vc(A.x-B.x,A.y-B.y);}
db operator*(const vc&A,const vc&B){return A.x*B.y-A.y*B.x;}
vc operator*(const db&B,const vc&A){return vc(A.x*B,A.y*B);}
vc operator/(const vc&A,const db&B){return vc(A.x/B,A.y/B);}
db operator^(const vc&A,const vc&B){return A.x*B.x+A.y*B.y;}//dot 
struct line{
	vc p,n;
	line(){p=vc();n=vc();}
	line(vc a,vc b){
		p=a; vc v=b-a;
		n=vc(-v.y,v.x)/v.mo();
	}
	line operator () (const db&t){
		line l;l.p=p+(t*n);l.n=n;return l;
	}
};
ostream&operator<<(ostream&out,const line&A){out<<A.p<<"->"<<vc(A.n.y,-A.n.x);return out;}
vc operator&(const line&A,const line&B){//intersect
	vc v=vc(-A.n.y,A.n.x);
	db t=((B.p-A.p)^B.n)/(v^B.n);
	return A.p+(t*v);
}


vector<int>lines;
line node[maxn];
vc vcs[maxn];
pdi q[100005];
db ans[100005];
int n,m;
int main(){
#ifdef fucker
	freopen("my.in","r",stdin);
#endif
	n=yh();
	int x,y,k,now,nxt,pre,mid;db tm,A,B,C,t;
	vc u,v,du,dv;
	rep(i,0,n-1){
		x=yh(),y=yh();
		vcs[i]=vc(x,y);
	}
	rep(i,0,n-1) node[i]=line(vcs[i],vcs[(i+1)%n]),lines.pb(i);
	m=yh();
	rep(i,0,m-1) scanf("%Lf",&q[i].fi),q[i].se=i;
	sort(q,q+m);
	int cur=0;
	while(lines.size()>=3){
		tm=1e5; k=lines.size(); A=B=C=0;
		rep(i,0,k-1){
			now=lines[i],nxt=lines[(i+1)%k],pre=lines[(i-1+k)%k];
			u=(node[now](0))&(node[pre](0)),
			v=(node[now](0)&node[nxt](0));
			du=((node[now](1))&(node[pre](1)))-u;
			dv=((node[now](1))&(node[nxt](1)))-v;
			A+=u*v;
			B+=(du*v)+(u*dv);
			C+=(du*dv);
			t=((v-u)^(du-dv))/((du-dv).mo2());
			if(t<tm)mid=i,tm=t;
		}
		for(;cur<m&&q[cur].fi<=tm;cur++){
			t=q[cur].fi;
			ans[q[cur].se]=A+B*t+C*t*t;
		}
		lines.erase(lines.begin()+mid);
	}
	rep(i,0,m-1) cout<<fixed<<setprecision(8)<<ans[i]/2.0<<hvie;
	return 0;
}

B. Defend Ponyville

【题目】

给定一张图 G G G,每条边有一个出现概率 p i p_i pi,求生成树个数的期望。

特别地, P i P_i Pi随着时间变化, P i ( t ) = q i + ( p i − q i ) a − ( t − 1 ) P_i(t)=q_i+(p_i-q_i)a^{-(t-1)} Pi(t)=qi+(piqi)a(t1)

求前 T T T天的答案和。

n ≤ 300 , T ≤ 1 0 8 , p i ≠ q i n\leq 300,T\leq 10^8,p_i\neq q_i n300,T108,pi=qi,答案对998244353取模

【思路】

显然我不会

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MNHZJsfc-1628664852730)(C:\Users\22296\AppData\Roaming\Typora\typora-user-images\image-20210810193449189.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CYPb8MAl-1628664852731)(C:\Users\22296\AppData\Roaming\Typora\typora-user-images\image-20210810193511124.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6lgDA6dl-1628664852732)(C:\Users\22296\AppData\Roaming\Typora\typora-user-images\image-20210810193516056.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VHTBVmLb-1628664852733)(C:\Users\22296\AppData\Roaming\Typora\typora-user-images\image-20210810193609269.png)]

【参考代码】

随便扒拉的

#include<bits/stdc++.h>
using namespace std;
int n,m,t,a;
#define MAXN 310
#define P 998244353
int power(int a,int b)
{
	int res = 1;
	while(b > 0)
	{
		if(b & 1)res = 1ll * res * a % P;
		a = 1ll * a * a % P;
		b = b >> 1;
	}
	return res;
}
int inv(int a){return power(a,P - 2);}
void solve1(int a[MAXN][MAXN],int n)
{
	for(int i = 1;i < n;++i)
	{
		for(int j = i + 1;j <= n;++j)if(a[j][i])
		{
			for(int k = 1;k <= n;++k)swap(a[i + 1][k],a[j][k]);
			for(int k = 1;k <= n;++k)swap(a[k][i + 1],a[k][j]);
			break;
		}
		if(!a[i + 1][i])continue;
		for(int j = i + 2;j <= n;++j)
		{
			int delta = 1ll * a[j][i] * inv(a[i + 1][i]) % P;
			for(int k = 1;k <= n;++k)a[j][k] = (a[j][k] - 1ll * delta * a[i + 1][k] % P + P) % P;
			for(int k = 1;k <= n;++k)a[k][i + 1] = (a[k][i + 1] + 1ll * delta * a[k][j] % P) % P;
		}
	}
	/*for(int i = 1;i <= n;++i)
	{
		for(int j = 1;j <= n;++j)cout << a[i][j] << " ";
		cout << endl;
	}*/
	return;
}
int ans[MAXN][MAXN];
int g[MAXN][MAXN];
int f[MAXN];
void solve2(int a[MAXN][MAXN],int n)
{
	ans[0][0] = 1;
	for(int i = 1;i <= n;++i)for(int j = 1;j <= n;++j)a[i][j] = (P - a[i][j]) % P;
	for(int i = 1;i <= n;++i)
	{
		for(int j = 0;j < i;++j)
		{
			ans[i][j] = (ans[i][j] + 1ll * a[i][i] * ans[i - 1][j] % P) % P;
			ans[i][j + 1] = (ans[i][j + 1] + ans[i - 1][j]) % P;
		}
		memset(g,0,sizeof(g));
		g[0][0] = 1;
		for(int j = 1;j < i;++j)
		{
			for(int k = 0;k < j;++k)g[j][k] = 1ll * a[j][i] * ans[j - 1][k] % P;
			for(int k = 0;k < j;++k)g[j][k] = (g[j][k] - 1ll * a[j][j - 1] * g[j - 1][k] % P + P) % P;
		}
		for(int j = 0;j < i;++j)ans[i][j] = (ans[i][j] - 1ll * a[i][i - 1] * g[i - 1][j] % P + P) % P;
	}
	for(int i = 0;i <= n;++i)f[i] = ans[n][i];
	return;
}
int p[MAXN][MAXN],q[MAXN][MAXN];
int K[MAXN][MAXN],B[MAXN][MAXN];
int fa[MAXN];
int find(int x){return (fa[x] == 0 ? x : fa[x] = find(fa[x]));}
int main()
{
	scanf("%d%d%d%d",&n,&m,&t,&a);
	if(n == 1 && m == 0)
	{
		cout << 0 << endl;
		return 0;
	}
	int u,v,a1,b1,a2,b2;
	memset(p,-1,sizeof(p));
	memset(q,-1,sizeof(q));
	for(int i = 1;i <= m;++i)
	{
		scanf("%d%d%d%d%d%d",&u,&v,&a1,&b1,&a2,&b2);
		int p = 1ll * a1 * inv(b1) % P;
		int q = 1ll * a2 * inv(b2) % P;
		int x = find(u),y = find(v);
		if(x != y)fa[x] = y;
		B[u][u] = (B[u][u] + q) % P;
		B[v][v] = (B[v][v] + q) % P;
		B[u][v] = (B[u][v] - q + P) % P;
		B[v][u] = (B[v][u] - q + P) % P;
		K[u][u] = (0ll + K[u][u] + p - q + P) % P;
		K[v][v] = (0ll + K[v][v] + p - q + P) % P;
		K[u][v] = (0ll + K[u][v] - p + q + P) % P;
		K[v][u] = (0ll + K[v][u] - p + q + P) % P;
	}
	int N = n - 1;
	for(int i = 2;i <= n;++i)
	{
		if(find(i) != find(1))
		{
			puts("0");
			return 0;
		}
	}
	int tag = 1;
	for(int i = 1;i <= N;++i)
	{
		if(K[i][i] == 0)
		{
			for(int j = i + 1;j <= N;++j)if(K[j][i])
			{
				for(int k = 1;k <= N;++k)swap(K[i][k],K[j][k]),swap(B[i][k],B[j][k]);
				break;
			}
			tag = (P - tag) % P;
		}
		int val = inv(K[i][i]);
		tag = 1ll * tag * K[i][i] % P;
		for(int k = 1;k <= N;++k)K[i][k] = 1ll * K[i][k] * val % P,B[i][k] = 1ll * B[i][k] * val % P;
		for(int j = 1;j <= N;++j)
		{
			if(j == i)continue;
			int delta = K[j][i];
			for(int k = 1;k <= N;++k)
			{
				K[j][k] = (K[j][k] - 1ll * delta * K[i][k] % P + P) % P;
				B[j][k] = (B[j][k] - 1ll * delta * B[i][k] % P + P) % P;
			}
		}
	}
	for(int i = 1;i <= N;++i)for(int j = 1;j <= N;++j)B[i][j] = (P - B[i][j]) % P;
	solve1(B,N);
	solve2(B,N);
	int res = 0;
	a = inv(a);
	for(int i = 0;i <= N;++i)
	{
		int val = power(a,i);
		int sum = 0;
		if(val == 1)sum = t;
		else sum = 1ll * (power(val,t) - 1 + P) % P * inv(val - 1) % P;
		res = (res + 1ll * sum * tag % P * f[i] % P) % P;
	}
	cout << res << endl;
	return 0;
}
C. Delete Edges

【题目】

一个 n n n个点的完全无向图,现在要求每次删去一个三元环,使得最后剩下的边数不到 n n n

n ≤ 2000 n\leq 2000 n2000

【思路】

比较玄幻的构造题,比赛的时候用打表找规律可以可以搞到比较有规律的解。

std是输出了 x + y + z = 0 ( mod  n ) x+y+z=0(\text{mod }n) x+y+z=0(mod n)的所有解。

这个正确性是显然的,可以通过归纳法证明,但是怎么想的就无从而知了。

D. Gambling Monster

【题目】

一个转盘,每次转动刚得到 0 ∼ n − 1 0\sim n-1 0n1的概率分别给出,开始有一个数 x = 0 x=0 x=0,每次转转盘得到一个数 y y y,令 x = max ⁡ ( x ⊕ y , x ) x=\max(x\oplus y,x) x=max(xy,x)。求使得 x = n − 1 x=n-1 x=n1的期望转动次数。

n = 2 k , k ≤ 16 n=2^k,k\leq 16 n=2k,k16

【思路】

期望题的一个经典做法是逆推,我们考虑设 P P P为概率, E E E为期望, S S S表示转动一次变大的概率。
KaTeX parse error: No such environment: gather* at position 36: …))+\sum_{\begin{̲g̲a̲t̲h̲e̲r̲*̲}̲x<z\\x\oplus y=…
S S S是一个传递闭包的形式,简单DP就可以贡献。 E E E是一个fwt的形式,只是贡献的时候只有位置比它后的才有贡献。我们同样可以使用分治fwt来做。这里一个很有意思的事是这里只限制了 x x x,但是 y y y的位置是任意的,所以我们要找到对应能贡献的区间来做fwt,可以发现这是连续的一段区间,具体可以见代码。

比赛时队友的推导似乎和这个不太一样,不过分治的方式是一样的。

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=1<<17|7,mod=1e9+7,inv2=(mod+1)>>1;
ll dp[maxn],den[maxn],one[maxn],tmp[maxn],pmt[maxn];
ll A[maxn],B[maxn],C[maxn];
int n;ll p[maxn];
void FWT_xor(ll *a,int opt,int N)
{
    for(int i=1;i<N;i<<=1)
        for(int p=i<<1,j=0;j<N;j+=p)
            for(int k=0;k<i;++k)
            {
                ll X=a[j+k],Y=a[i+j+k];
                a[j+k]=(X+Y)%mod;a[i+j+k]=(X+mod-Y)%mod;
                if(opt==-1) a[j+k]=1ll*a[j+k]*inv2%mod,a[i+j+k]=1ll*a[i+j+k]*inv2%mod;
            }
}


bool DEBUG=0;
void mul(ll *a,ll *b,ll *ret,int len){
    rep(i,0,len-1) A[i]=a[i],B[i]=b[i];
    FWT_xor(A,1,len);FWT_xor(B,1,len);
    rep(i,0,len-1) C[i]=A[i]*B[i]%mod;
    FWT_xor(C,-1,len);
    rep(i,0,len-1) ret[i]=C[i];
}
ll ksm(ll a,ll b,ll c=1){
    c%=mod;
    for(;b;b>>=1,a=a*a%mod)if(b&1) c=c*a%mod;
    return c;
}

void cdq(int l,int r){
    // cout<<l<<","<<r<<")"<<hvie;
    if(l==r){
        // cout<<l<<" : "<<dp[l]+1<<" "<<den[l]<<" "<<"\n";
        dp[l]=ksm(den[l],mod-2,dp[l]+1);
        return;
    }
    int mid=(l+r)>>1;
    
    cdq(mid+1,r);
    int up=l^(mid+1);
    // cout<<"update from "<<l<<" "<<r<<" "<<up<<" "<<up+mid-l<<hvie;
    mul(dp+mid+1,p+up,tmp+l,mid-l+1);
    mul(one+mid+1,p+up,pmt+l,mid-l+1);
    
    rep(i,l,mid) dp[i]=(dp[i]+tmp[i])%mod,den[i]=(den[i]+pmt[i])%mod;

    cdq(l,mid);
}

int main(){

    // rep(i,1,100000){
    //  cout<<i*849927851ll%mod<<"/"<<i<<hvie;
    // }
    dwn(_,yh(),1){
        ll sum=0;
        n=yh();rep(i,0,n-1) p[i]=yh(),sum+=p[i];
        memset(dp,0,sizeof(ll)*n);
        memset(den,0,sizeof(ll)*n);
        rep(i,0,n-1) one[i]=1;
        rep(i,0,n-1) p[i]=ksm(sum,mod-2,p[i]);
        cdq(0,n-1);
        cout<<dp[0]<<hvie;
    }
    return 0;
}

E. Growing Tree

【题意】

给出一棵树,最开始只有一个根节点,编号为1,有颜色 c c c,要求在线完成 m m m次操作,有以下两种:

  • 给节点 u u u加一个叶子节点,颜色为 c c c
  • 询问以 u u u为根的子树中颜色为 c c c的节点个数

m ≤ 5 × 1 0 5 m\leq 5\times 10^5 m5×105

【思路】

两种操作实际上都是子树操作,可以转化为序列操作,实际上就是括号序,一种方法是ETT(平衡树维护括号序)

当然简化为DFS序会更好:对每个结点,我们记录以它为根的子树的“结束结点”(即在序列上对应区间的右端点(不包括)),并指定每个新插入的结点都作为其父结点的第一个儿子,则这个新结点的“结束结点”要么是其下一个兄弟,要么等于其父亲的“结束结点”。

现在问题转化为一个带插入的区间某颜色个数查询,直接的想法是对每个颜色出现的位置都开一棵平衡树来维护,但此时我们遇到一个问题:颜色的位置是动态变化的,这导致没有办法对单个颜色进行插入和询问。

考虑到我们不需要知道每个结点的绝对位置,而只需要知道它们的相对顺序。于是可以考虑将绝对位置映射到更大的空间中,给插入结点留下空间。比如,最直接的想法是映射到实数域内,如果我们将新的结点插入到位置为 x x x 的结点后,位置为 y y y 的结点前,就令这个结点的位置是 x + y 2 \frac {x+y} 2 2x+y

但是这样的问题也很明显:如果总是插入到第一个结点后面,则前两个结点的位置差会每次减少一半,即使是 long double 的精度也不足以表示这些位置。

不过这种不均匀的操作有一个经典的数据结构——替罪羊树重构,于是我们就解决了位置的问题,剩下的只需要对每个颜色开一颗平衡树,询问区间内节点个数即可。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【参考代码】(都是扒拉的)

ETT

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define For(i,j,k) for (int i=(int)(j);i<=(int)(k);i++)
#define Rep(i,j,k) for (int i=(int)(j);i>=(int)(k);i--)
using namespace std;

const int N=500005;
int Fa[N][20],dep[N],C[N];
int n,m,sum[N],nd;

//-------------- Tree -----------------

int cmp1(int x,int y){
	if (x==y) return 0;
	int px=x,py=y;
	if (dep[x]<dep[y]) swap(x,y);
	int k=dep[x]-dep[y];
	for (int i=18;i>=0;i--)
		if (k&(1<<i)) x=Fa[x][i];
	for (int i=18;i>=0;i--)
		if (Fa[x][i]!=Fa[y][i])
			x=Fa[x][i],y=Fa[y][i];
	if (dep[px]<dep[py]) swap(x,y);
	int L=(x==y?x:Fa[x][0]);
	if (px==L) return 1;
	if (py==L) return 0;
	return x<y;
}

int cmp2(int x,int y){
	if (x==y) return 1;
	int px=x,py=y;
	if (dep[x]<dep[y]) swap(x,y);
	int k=dep[x]-dep[y];
	for (int i=18;i>=0;i--)
		if (k&(1<<i)) x=Fa[x][i];
	for (int i=18;i>=0;i--)
		if (Fa[x][i]!=Fa[y][i])
			x=Fa[x][i],y=Fa[y][i];
	if (dep[px]<dep[py]) swap(x,y);
	int L=(x==y?x:Fa[x][0]);
	if (px==L) return 1;
	if (py==L) return 1;
	return x<y;
}


//-------------- Splays -----------------

int sz[N],fa[N],ch[N][2];
int v[N],vl[N],vr[N],rt[N];
void pushup(int k){
	sz[k]=sz[ch[k][0]]+sz[ch[k][1]]+1;
	vr[k]=(ch[k][1]?vr[ch[k][1]]:v[k]);
}
void insert(int &k,int x,int f){
	if (!k){
		k=++nd; sz[k]=1;
		fa[k]=f; v[k]=vl[k]=vr[k]=x;
		return;
	}
	if (cmp1(v[k],x))
		insert(ch[k][1],x,k);
	else
		insert(ch[k][0],x,k);
	pushup(k);
}
void rotate(int x){
	int y=fa[x],z=fa[y];
	int l=(ch[y][1]==x),r=l^1;
	if (z) ch[z][ch[z][1]==y]=x;
	fa[x]=z; fa[y]=x; fa[ch[x][r]]=y;
	ch[y][l]=ch[x][r]; ch[x][r]=y;
	pushup(y); pushup(x);
}
void splay(int x){
	for (;fa[x];rotate(x)){
		int y=fa[x],z=fa[y];
		if (z) rotate((ch[z][0]==y)^(ch[y][0]==x)?x:y);
	}
}
void insert(int c,int x){
	insert(rt[c],x,0);
	splay(nd);
	rt[c]=nd;
}
int query_lower2(int (*cmp)(int,int),int k,int x){
	if (cmp(v[k],x))
		return query_lower2(cmp,ch[k][1],x);
	if (!ch[k][0]||cmp(vr[ch[k][0]],x))
		return k;
	return query_lower2(cmp,ch[k][0],x);
}
int query_lower(int (*cmp)(int,int),int c,int x){
	if (cmp(vr[rt[c]],x)) return sz[rt[c]];
	int p=query_lower2(cmp,rt[c],x);
	splay(p); rt[c]=p;
	return sz[ch[p][0]];
}
int query(int c,int x){
	if (!rt[c]) return 0;
	int v1=query_lower(cmp1,c,x);
	int v2=query_lower(cmp2,c,x);
	return v2-v1;
}
void Clear(){
	for (;nd>0;--nd){
		ch[nd][0]=ch[nd][1]=0;
		sz[nd]=v[nd]=fa[nd]=vl[nd]=vr[nd]=0;
	}
}
void solve(){
	n=1; nd=0;
	scanf("%d%d",&C[1],&m);
	Fa[1][0]=0; dep[1]=0;
	insert(C[1],1);
	int lastans=0;
	while (m--){
		int tp,u,c; 
		scanf("%d%d%d",&tp,&u,&c);
		tp^=lastans; u^=lastans; c^=lastans;
		if (tp==1){
			Fa[++n][0]=u;
			for (int j=1;j<=19;j++)
				Fa[n][j]=Fa[Fa[n][j-1]][j-1];
			dep[n]=dep[u]+1; C[n]=c;
			insert(C[n],n);
		}
		if (tp==2){
			lastans=query(c,u);
			printf("%d\n",lastans);
		}
	}
	for (int i=1;i<=n;i++)
		sum[C[i]]=0,rt[C[i]]=0;
	Clear();
}
int main(){
	#ifdef zyy
		freopen("1.in","r",stdin);
	#endif
	int T;
	scanf("%d",&T);
	while (T--) solve();
}

替罪羊

#include<bits/stdc++.h>

using namespace std;
const int BLK = 5000;

template<typename Q>
void inin(Q &ret) {
    ret = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9')ch = getchar();
    while (ch >= '0' && ch <= '9')ret = ret * 10 + ch - '0', ch = getchar();
}

struct wocao {
    int y, pos;

    wocao() {}

    wocao(int y, int pos) : y(y), pos(pos) {}

    inline bool operator<(const wocao &rhs) const {
        return y == rhs.y ? pos < rhs.pos : y < rhs.y;
    }
};

int T, cc, m;
int c[500050], n, N, ans, dfn[500050][2], dfns, dui[500050];
wocao a[500050];
int head[500050], Next[500050], zhi[500050], ed;

inline void add(int a, int b) {
    Next[++ed] = head[a], head[a] = ed, zhi[ed] = b;
}

int sta[500050], top;

inline int dfs(int x, int y) {
    sta[top = 1] = x;
    int ret = 0;
    while (top) {
        int xx = sta[top--];
        ret += c[xx] == y;
        for (int i = head[xx]; i; i = Next[i])
            sta[++top] = zhi[i];
    }
    return ret;
//    int ret = (c[x] == y);
//    for (auto v:e[x])
//        ret += dfs(v, y);
//    return ret;
}

inline void dfs(int x) {
    dfn[x][0] = ++dfns;
    dui[dfns] = x;
    for (int i = head[x]; i; i = Next[i])
        dfs(zhi[i]);
    dfn[x][1] = dfns;
}

inline int ask(int y, int l, int r) {
    return upper_bound(a + 1, a + N + 1, wocao(y, r)) - lower_bound(a + 1, a + N + 1, wocao(y, l));
}

inline void rebuild() {
    dfns = 0;
    dfs(1);
    for (int i = 1; i <= n; i++)a[i] = wocao(c[dui[i]], i);
    sort(a + 1, a + n + 1);
    N = n;
}

inline void print(int x) {
    if (x > 9)print(x / 10);
    putchar(x % 10 + '0');
}

int main() {
    inin(T);
    while (T--) {
        N = ans = ed = 0;
        head[1] = 0;
        inin(cc), inin(m);
        n = 1, c[1] = cc;
        for (int i = 1; i <= m; i++) {
            //if (!(i % BLK))rebuild();
            int op, x, y;
            inin(op), inin(x), inin(y);
            op ^= ans, x ^= ans, y ^= ans;
            if (op == 1) {
                c[++n] = y;
                head[n] = 0;
                dfn[n][0] = dfn[x][0];
                add(x, n);
                if (n - N == BLK)rebuild();
            } else {
                if (x > N)
                    ans = dfs(x, y);
                else {
                    ans = ask(y, dfn[x][0], dfn[x][1]);
                    for (int i = N + 1; i <= n; i++)
                        if (c[i] == y && dfn[i][0] >= dfn[x][0] && dfn[i][0] <= dfn[x][1])
                            ans++;
                }
                print(ans);
                putchar('\n');
            }
        }
    }
    return 0;
}
F. Hamburger Steak

【题目】

n n n个肉 m m m个锅,每个肉需要烤 t i t_i ti的时间,一个肉可以在一个锅烤,也可以分别在两个锅烤一共 t i t_i ti,一个锅同时只能烤一个肉,求一个最早烤完所有肉的方案。

n , m ≤ 1 0 5 n,m\leq 10^5 n,m105

【思路】

众所周知,这个时间可以二分,于是我们考虑已知耗时为 T T T的时候怎么做。

显然我们可以贪心地分配锅,这个锅没烤完再由下一个锅烤就行了。

复杂度 O ( n log ⁡ T ) O(n\log T) O(nlogT)

当然事实上,为了满足题目要求,我们需要保证所有锅的时间和大于等于所有汉堡排的时间和,以及耗时最长的汉堡排不会在同一时刻分到两个锅中(耗时小于它的自然也就满足条件),于是最小耗时为 max ⁡ ( max ⁡ { t i } , ⌈ ∑ t i m ⌉ ) \max(\max\{t_i\},\lceil\frac{\sum t_i} m \rceil) max(max{ti},mti)

复杂度 O ( n ) O(n) O(n)

【参考代码】

1log

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e5+10,M=3e5+10,mod=998244353;

int n,K;
pii a[N];

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
    return f?ret:-ret;
}

int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}
void up(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
void up(ll &x,ll y){x=(x+y>=mod?x+y-mod:x+y);}
int mul(int x,int y){return 1ll*x*y%mod;}
int qpow(int x,int y)
{
    int ret=1;
    for(;y;y>>=1,x=mul(x,x))
        if(y&1) ret=mul(ret,x);
    return ret;
}

bool check(ll tim)
{
    int use=0;ll res=0;
    for(int i=1;i<=n;++i)
    {
        if(res>=a[i].fi) res-=a[i].fi;
        else
        {
            ++use;res=tim-(a[i].fi-res);
        }
    }
    return use<=K;
}

struct node
{
    int id;ll l,r;
    node(int id,ll l,ll r):id(id),l(l),r(r){}
};
vector<node>ans[N];


void solve(ll tim)
{
    int use=0;ll res=0;
    for(int i=1;i<=n;++i)
    {
        if(res>=a[i].fi) 
        {
            ans[a[i].se].pb(node(use,tim-res,tim-(res-a[i].fi)));
            //printf("1 %d %lld %lld\n",use,tim-res,tim-(res-a[i]));
            res-=a[i].fi;
        }
        else
        {
            if(res)
            {
                ans[a[i].se].pb(node(use+1,0ll,(a[i].fi-res)));
                ans[a[i].se].pb(node(use,tim-res,tim));
                //printf("2 %d %lld %lld %d %lld %lld\n",use,tim-res,tim,use+1,0ll,tim-(a[i]-res));
            }
            else 
            {
                ans[a[i].se].pb(node(use+1,0ll,a[i].fi));
                //printf("1 %d %lld %lld\n",use+1,0ll,a[i]);
            }
            ++use;res=tim-(a[i].fi-res);
        }
    }
    for(int i=1;i<=n;++i) 
    {
        if(ans[i].size()==1) printf("1 %d %lld %lld\n",ans[i][0].id,ans[i][0].l,ans[i][0].r);
        else printf("2 %d %lld %lld %d %lld %lld\n",ans[i][0].id,ans[i][0].l,ans[i][0].r,ans[i][1].id,ans[i][1].l,ans[i][1].r);
    }
}

bool cmp(pii a,pii b){return a.fi>b.fi;}

int main()
{
    n=read();K=read();
    for(int i=1;i<=n;++i)
    {
        a[i]=mkp(read(),i);
    }
    sort(a+1,a+n+1,cmp);
    ll l=a[1].fi,r=(ll)1e18,ans=a[1].fi;
    while(l<=r)
    {
        ll mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    solve(ans);
    return 0;
}
G. Hass Diagram

【题意】

对正整数 n n n,定义 H n H_n Hn为它所有因子组成的有向图,其中两个因子之间有变当且仅当 a a a b b b的素数倍,那么有边 a → b a\rightarrow b ab,求 1 ∼ n 1\sim n 1n所有数的吐的边数和。

n ≤ 1 0 10 n\leq 10^{10} n1010

【思路】

H n H_n Hn的边数为 f ( n ) f(n) f(n),考虑 n = ∏ p e n=\prod p^e n=pe,枚举任意一个质因子有 f ( n ) = ( e + 1 ) f ( n / p e ) + e d ( n / p e ) f(n)=(e+1)f(n/p^e)+ed(n/p^e) f(n)=(e+1)f(n/pe)+ed(n/pe),然后就是min25筛了,其中 d ( n ) d(n) d(n)表示 n n n的因子数。

【参考代码】

#include<bits/stdc++.h>
using namespace std;
const int LIM=316228,P=27294;
const long long mod=1145140019ll;
bool vis[LIM];
int lim,p[P],sum[LIM],last[LIM*2],cnt,ans[LIM*2];
long long val[LIM*2],f[LIM*2],n;
inline void sieve() {
	p[0]=0;
    for(int i=2;i<=lim;i++) {
        if(!vis[i]) p[++p[0]]=i;
        sum[i]=sum[i-1]+!vis[i];
        for(int j=1;j<=p[0]&&i*p[j]<=lim;j++) {
            vis[i*p[j]]=true;
            if(i%p[j]==0) break;
        }
    }
}
long long getnum(long long nw)
{
	if (n/nw<=lim) return sum[n/nw];
	return sum[lim]+f[cnt-nw+1]-1+last[cnt-nw+1]-p[0];
}
int getans(long long x)
{
	if (x<=lim&&ans[x]!=-1) return ans[x];
	int Ans=x%mod;
	for (long long r=x,l=x/(x/r+1); r!=1; r=l,l=x/(x/r+1))
		Ans=(Ans+(r-l)%mod*(x/r))%mod;
	if (x<=lim) ans[x]=Ans;
	return Ans;
}
int main() {
    int T; scanf("%d",&T);
    while (T--)
    {
    	scanf("%lld",&n);
    	//n=10000000000ll;
    	if (n<=1) {puts("0"); continue;}
	    lim=sqrt(n);
	    sieve(),cnt=0;
	    for(long long i=1;i<=n;i=n/(n/i)+1) {
	        val[++cnt]=n/i;
	    }
	    std::reverse(&val[1],&val[cnt]+1);
	    std::copy(&val[1],&val[cnt]+1,&f[1]);
	    for (int i=1; i<=cnt; i++) last[i]=0;
	    for(int i=1;i<=p[0];i++) {
	        for(int j=cnt;j;j--) {
	            long long k=val[j]/p[i],pos=k<=lim?k:cnt+1-n/k;
	            if(k<p[i]) break;
	            f[j]-=f[pos]+last[pos]-i+1;
	            last[j]=i;
	        }
	    }
	    long long Ans=0;
	    for (int i=1; i<=lim; i++) ans[i]=-1;·
	    for (int i=1; i<cnt; i++) 
		{
			long long cnt=(getnum(n/val[i+1])-getnum(n/val[i]))%mod;
			if (cnt) Ans=(Ans+cnt*getans(n/val[i+1]))%mod;
		}
	    printf("%lld\n",Ans);
	}
    return 0;
}
H. Hopping Rabbit

【题目】

平面上有 n n n个矩形,找到一个位置 ( x , y ) (x,y) (x,y),使得所有 ( x + k 1 d , y + k 2 d ) (x+k_1d,y+k_2d) (x+k1d,y+k2d)均不落在矩形中。

n , d ≤ 1 0 5 n,d\leq 10^5 n,d105

【思路】

首先,这个这个限制可以转换一下,将所有矩形限制平移到 d × d d\times d d×d的矩阵中,这样一个矩形最多分成了四个矩形。

然后问题就变为了矩形覆盖后找一个空位,因为 d d d不大,所以就是一个经典的扫描线问题了。我们用区间最小值来判断是否有位置可选。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=2e5+5;
int n,d;
vector<pii>st[maxn],ed[maxn];
void ins(int a,int b,int x,int y){
    x=min(x,d);y=min(y,d);
    st[a].pb({b,y});
    ed[x].pb({b,y});
}

#define ls (v<<1)
#define rs (v<<1|1)
#define mid ((l+r)>>1)
// int sum[maxn<<2];
int mn[maxn<<2];
int tag[maxn<<2];
int len[maxn<<2];
void CV(int v,int x){
    // sum[v]+=x*len[v];
    mn[v]+=x;
    tag[v]+=x;
}
void push_down(int v){
    if(tag[v]){
        CV(ls,tag[v]);
        CV(rs,tag[v]);
        tag[v]=0;
    }
}
void push_up(int v){
    // sum[v]=sum[ls]+sum[rs];
    mn[v]=min(mn[ls],mn[rs]);
}
void build(int v,int l,int r){
    tag[v]=0;
    // sum[v]=0;
    mn[v]=0;
    len[v]=r-l+1;
    if(l==r) return;
    build(ls,l,mid);build(rs,mid+1,r);
}
void modify(int v,int l,int r,int al,int ar,int d){
    if(al>ar) return;
    if(al<=l&&ar>=r){
        CV(v,d);return;
    }
    push_down(v);
    if(al<=mid) modify(ls,l,mid,al,ar,d);
    if(ar>mid) modify(rs,mid+1,r,al,ar,d);
    push_up(v);
}

int find(int v,int l,int r){

    if(l==r) return (mn[v])?-1:l;
    // cout<<v<<": "<<mn[v]<<" "<<mn[ls]<<" "<<mn[rs]<<hvie;
    push_down(v);
    if(mn[ls]==0) return find(ls,l,mid);
    return find(rs,mid+1,r);
}
int main(){
    n=yh(),d=yh();
    rep(i,1,n){
        ll a=yh(),b=yh(),x=yh(),y=yh(),k;
        if(a<0){
            k=ceil(-1.0*a/d);
            // k=(-a+d-1)/d;
            a+=k*d; x+=k*d;
        }
        else{
            k=a/d;
            a-=k*d;x-=k*d;
        }
        if(b<0){
            k=ceil(-1.0*b/d);
            b+=k*d;y+=k*d;
        }
        else{
            k=b/d;
            b-=k*d;y-=k*d;
        }
        if(x<=d&&y<=d){
            ins(a,b,x,y);
        }
        else if(x>d&&y>d){
            ins(a,b,d,d);
            ins(a,0,d,y-d);
            ins(0,b,x-d,d);
            ins(0,0,x-d,y-d);
        }
        else if(x>d){
            ins(a,b,d,y);
            ins(0,b,x-d,y);
        }
        else{
            ins(a,b,x,d);
            ins(a,0,x,y-d);
        }
    }
    if(d==1){
        puts("NO");return 0;
    }
    build(1,0,d-1);
    bool ok=0;
    rep(i,0,d-1){
        // cout<<"i"<<i<<" : ";
        // cout<<"st: \n";
        for(auto l:st[i]){
            // cout<<l.fi<<","<<l.se<<hvie;
            modify(1,0,d-1,l.fi,l.se-1,1);
        }
        // cout<<"ed: \n";
        for(auto l:ed[i]){
            // cout<<l.se<<","<<l.se<<hvie;
            modify(1,0,d-1,l.fi,l.se-1,-1);
        }
        // cout<<mn[1]<<hvie;
        int x=find(1,0,d-1);
        if(x!=-1){
            puts("YES");
            cout<<i<<" "<<x<<hvie;
            ok=1;
            break;
        }
    }
    if(!ok) puts("NO");
    return 0;
}
/*
2 5
0 1 5 5
-80000 0 -40000 5

*/
I. Intervals on the Ring

【题目】

一个 n n n个点的环,给定环上 m m m个区间,要求用若干个区间的交表示出这 m m m个区间的并。

n , m ≤ 1000 n,m\leq 1000 n,m1000

【思路】

交只会使得区间总长越来越小,我们考虑如何用交把区间的“缺口”表示出来。

事实上,只需要构造每个区间分别不包含某个“缺口”即可。

【参考代码】

/*
 * @date:2021-08-02 12:56:26
 * @source:
*/
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {
    return y > x ? x = y, 1 : 0;
}
template<class T, class G> bool chkMin(T &x, G y) {
    return y < x ? x = y, 1 : 0;
}

int N, M;
vector<pii> v, v2;

void solve() {
    cin >> N >> M;
    v.clear();
    v2.clear();
    for (int i = 1, l, r; i <= M; ++i) {
        cin >> l >> r;
        if (l <= r) v.pb({l, r});
        else {
            v.pb({l, N});
            v.pb({1, r});
        }
    }
    sort(ALL(v));
    Trav(x, v) {
        if (!v2.empty() && v2.back().sec == x.fir) {
            v2.back().sec = x.sec;
        } else {
            v2.pb(x);
        }
    }
    printf("%d\n", SZ(v2));
    for (int i = 0; i < SZ(v2); ++i) {
        printf("%d %d\n", v[i].fir, v[!i ? SZ(v2) - 1 : i - 1].sec);
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int Case;
    cin >> Case;
    while (Case--) solve();
    return 0;
}
J. Defend Your Country

【题目】

给定一幅 n n n个点 m m m条边的无向连通图,点有点权。现在要删去其中的一些边,删边后,每个奇数大小的连通块点权为负数,偶数大小的连通块点权为正数,求最大可能的权值。

n , m ≤ 1 0 6 n,m\leq 10^6 n,m106

【思路】

首先一个简单的结论是我们最多删去图上的一个节点的所有连边,仅有它是,证明显然。

考虑删去这个节点的位置影响:

  • 若该点不是割点,那没有额外的影响
  • 若该点是割点,那要看它连接的所有连通块大小是不是都是偶数

复杂度 O ( n + m ) O(n+m) O(n+m)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e6+10;
const ll inf=0x3f3f3f3f3f3f3f3f;

int n,m,tim,rt;
int dfn[N],low[N],siz[N],cut[N],fg[N],a[N];
vector<int>G[N],vec;

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
    return f?ret:-ret;
}


void add(int u,int v)
{
    G[u].pb(v);G[v].pb(u);
}

void tarjan(int x)
{
    vec.pb(x);
    dfn[x]=low[x]=++tim;siz[x]=1;
    
    int son=0;
    for(auto v:G[x])
    {
        if(!dfn[v])
        {
            tarjan(v);
            siz[x]+=siz[v];low[x]=min(low[x],low[v]);
            if(low[v]>=dfn[x])
            {
                ++son;
                fg[x]|=siz[v]&1;
                if(x!=rt || son>1) cut[x]=1; 
            }
        }
        else low[x]=min(low[x],dfn[v]);
    }
}

void init()
{
    tim=0;
    for(int i=1;i<=n;++i) 
    {
        fg[i]=siz[i]=low[i]=dfn[i]=cut[i]=0;
        G[i].clear();
    }
}

int main()
{
    for(int T=read();T--;)
    {
        n=read();m=read();

        ll sum=0;
        for(int i=1;i<=n;++i) a[i]=read(),sum+=a[i];
        for(int i=1;i<=m;++i) add(read(),read());
        
        for(int i=1;i<=n;++i)
        {
            if(dfn[i]) continue;
            vec.clear();rt=i;tarjan(i);
            if(!(vec.size()%2)) continue;
 
            ll dec=inf;
            for(auto x:vec)
            {
                if(cut[x])
                {
                    if(!fg[x]) dec=min(dec,2ll*a[x]);
                }
                else dec=min(dec,2ll*a[x]);
            }
            sum-=dec;
        }
        printf("%lld\n",sum);
        init();
    }
    return 0;
}
K. Starch Cat

【题目】

给一颗树,点有点权,每次询问一条链,不能选择相邻的点,问最大点权和。强制在线

n ≤ 5 × 1 0 5 , m ≤ 1 0 7 n\leq 5\times 10^5,m\leq 10^7 n5×105,m107

也就是说要 O ( n log ⁡ n ) O(n\log n) O(nlogn)预处理 O ( 1 ) O(1) O(1)询问

【思路】

不会正解,但是我们可以写暴力。

最大独立集是一个经典的问题,可以树链剖分以后维护转移矩阵,或者倍增也行。

这样复杂度 O ( ( n + m ) log ⁡ n ) O((n+m)\log n) O((n+m)logn)

然后树剖可以在跑的飞快的评测机上过掉,但是倍增不行,也许因为倍增跑的比较满。(注意这里的树剖不是直接用线段树维护矩阵,而是可以预处理出到链顶的转移矩阵,这样可以保证复杂度)

然后一个比较优秀的做法是RMQ-LCA + 树链剖分(长链剖分)+ 猫树。预处理时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn) ,询问时间复杂度 O ( 1 ) O(1) O(1),空间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)。分析一下发现,每次询问最多可能在倍增数组上跳 2 次,然后在猫树上获取 4 个 dp 数组,共计合并 6 个 dp 数组。虽然复杂度正确,但合并次数并不令人满意。

Starch → 淀粉 → 点分。所以题目名称就是正解——猫树在点分树上的扩展(此做法在发明猫树时就已经被提出,但即使是知道猫树的人也不一定知道这个扩展)。

猫树的核心思想为,将区间分为 log 层,每层处理每个点到区间中心点的答案。而点分树正好提供了这样的划分,所以我们只需要在点分治的过程中预处理所有点到子树分治中心(重心)的答案,然后在回答询问时只需要找到对应的点在点分树上的 LCA 即可。

预处理时间复杂度为 O(nlogn),询问复杂度为 O ( 1 ) O(1) O(1),空间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

为了方便合并,我们预处理的答案是不包括分治中心的,因此我们需要进行 3 个 dp 数组的合并。但如果我们将包含分治中心和不包含分治中心的答案都预处理出来,那么就只需要合并2个dp数组。

经过测试,此做法因为空间开销大,对 CPU 缓存机制十分不友好,所以询问所花费的时间是会随着 n 的增大而增大的。其他做法大多也存在同样的问题,所以还是可以认为这是明显优于倍增等暴力的。

另外,也可以按照树上启发式合并的方式划分分治中心,然后选择性地预处理。

据说树分块跑的飞快。

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,n) for(auto i=(a);i<=(n);i++)
#define per(i,a,n) for(auto i=(n);i>=(a);i--)
#define pb push_back
#define mp make_pair
#define FI first
#define SE second
template<class T> bool chmax(T &a, const T &b) {if(a<b) {a=b; return 1;} return 0;}
template<class T> bool chmin(T &a, const T &b) {if(b<a) {a=b; return 1;} return 0;}
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int,int> pii;
typedef vector<int> vi;

const int maxn = 500000;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;

//header

struct Rand
{
    unsigned int n,seed;
    Rand(unsigned int n,unsigned int seed): n(n),seed(seed){}
    int get(long long lastans){
        seed ^= seed << 13;
        seed ^= seed >> 17;
        seed ^= seed << 5;
        return (seed^lastans)%n+1;
    }
};

int val[maxn+5],b[maxn+5];
vi e[maxn+5];

typedef array<array<ll,2>,2> node;

node operator +(const node &x,const node &y)
{
    node a;
    rep(l,0,1) rep(r,0,1) 
    {
        a[l][r]=x[l][0]+y[0][r];
        chmax(a[l][r],x[l][0]+y[1][r]);
        chmax(a[l][r],x[l][1]+y[0][r]);
    }
    return a;
}

struct Segtree
{
    #define ls i*2
    #define rs i*2+1
    array<node,maxn*4+5> a;
    node res;
    void pu(int i) 
    {
        a[i]=a[ls]+a[rs];
    }
    void build(int i,int l,int r,int val[])
    {
        if(l==r) a[i]=node{0,0,0,1ll*val[l]};
        else
        {
            int mid=(l+r)>>1;
            build(ls,l,mid,val);
            build(rs,mid+1,r,val);
            pu(i);
        }
    }
    void ask(int i,int l,int r,int ql,int qr)
    {
        if(ql<=l && r<=qr) 
        {
            if(res[0][0]==-1) res=a[i];
            else res=res+a[i];
            return;
        }
        if(qr<l || r<ql) return;
        int mid=(l+r)>>1;
        ask(ls,l,mid,ql,qr); ask(rs,mid+1,r,ql,qr);
    }
    node ask(int n,int u,int v)
    {
        res=node{-1,-1,-1,-1};
        ask(1,1,n,u,v);
        return res;
    }
    #undef ls
    #undef rs
}seg;


struct HLD
{
    array<int,maxn+5> fa,sz,heavy,id,dep,top;
    int n,cnt;
    array<node,maxn+5> dp;
    
    void dfs(int now)
    {
        sz[now]=1;
        heavy[now]=-1;
        int mx=0;
        for(auto v: e[now]) if(dep[v]==0) 
        {
            dep[v]=dep[now]+1;
            fa[v]=now;
            dfs(v);
            sz[now]+=sz[v];
            if(chmax(mx,sz[v])) heavy[now]=v;
        }
    }
    void getid(int now,int sp)
    {
        top[now]=sp;
        id[now]=++cnt;
        if(sp==now) dp[now]=node{0,0,0,val[now]};
        else dp[now]=dp[fa[now]]+node{0,0,0,val[now]};

        if(heavy[now]==-1) return;
        getid(heavy[now],sp);
        for(auto v: e[now]) 
        {
            if(v==heavy[now] || v==fa[now]) continue;
            getid(v,v);
        }
    }
    void init(int _n)
    {
        n=_n;
        rep(i,1,n) dep[i]=0;
        dep[1]=1; cnt=0;
        dfs(1);
        getid(1,1);
        rep(i,1,n) b[id[i]]=val[i];
        seg.build(1,1,n,b);
    }
    ll query(int u,int v)
    {
        int f1=top[u],f2=top[v];
        node a{-1,-1,-1,-1},b{-1,-1,-1,-1};
        while(f1!=f2)
        {
            if(dep[f1]<dep[f2]) swap(f1,f2),swap(u,v),swap(a,b);
            if(a[0][0]==-1) a=dp[u];
            else a=dp[u]+a;
            u=fa[f1]; f1=top[u];
        }
        ll ans=0;
        if(dep[u]<dep[v]) swap(u,v),swap(a,b);
        node mid=seg.ask(n,id[v],id[u]);
        if(a[0][0]!=-1) mid=mid+a;
        if(b[0][0]!=-1)
        {
            chmax(ans,mid[0][1]+b[0][1]);
            chmax(ans,mid[0][1]+b[1][1]);
            chmax(ans,mid[1][1]+b[0][1]);
        }
        else ans=mid[1][1];
        return ans;
    }
}hld;

int main()
{    
    int n,m,seed; scanf("%d%d%d",&n,&m,&seed);
    rep(i,1,n) scanf("%d",&val[i]);
    rep(i,2,n)
    {
        int f; scanf("%d",&f);
        e[f].pb(i);
    }
    hld.init(n);
    ll lastans=0,ans=0;
    Rand rand(n,seed);
    rep(i,1,m)
    {
        int u=rand.get(lastans);
        int v=rand.get(lastans);
        int x=rand.get(lastans);
        lastans=hld.query(u,v);
        //printf("?? %d %d %lld\n",u,v,lastans);
        ans=(ans+lastans%mod*x)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值