dp训练记录 2025.1

Min-Fund Prison (Medium)

https://www.gxyzoj.com/d/hzoj/p/CF1970G2

显然,要加的边数就是联通快的个数-1,所以只需要让x,y尽量接近即可

因为要分成两个联通快,所以删掉的边一定是割边或加上的边

有一个性质,经过边双缩点后的图一定是一个森林,记深度较大的节点为u,x子树大小为sizx,所以断开割边后的两部分的值分别为sizusizrtsizu

考虑dp,因为算平方很难统计,考虑可行性dp

dpi,j,0/1表示当前是第i个联通快,第一个联通快大小为j,是否断开已知边

显然的,如果没有断开,直接连到对应联通快即可,有:

连到第二个集合

dpi,j,0|=dpi1,j,0

dpi,j,1|=dpi1,j,1

连到第一个集合

dpi,j,0|=dpi1,jsizrt,0

dpi,j,1|=dpi1,jsizrt,1

对于断开已知边,必然会分成两部分,一部分连在第一个联通快,另一部分在第二个

dpi,j,1|=dpi1,j(sizrtsizx),0

dpi,j,1|=dpi1,jsizx,0

最后看每一个j值是否可行即可

初始状态为dp0,0,0=1

点击查看代码
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,T,head[305],edgenum,head1[305],edgenum1,cnt;
ll c;
struct edge{
	int to,nxt;
}e[606],e1[606];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
void add_edge1(int u,int v)
{
	e1[++edgenum1].nxt=head1[u];
	e1[edgenum1].to=v;
	head1[u]=edgenum1;
}
int s[305],dfn[305],low[305],idx,top,ecnt,id[305],siz[305];
bool bri[305];
void tarjan(int i,int in_edge)
{
	dfn[i]=low[i]=++idx;
	s[++top]=i;
	for(int u=head[i];u;u=e[u].nxt)
	{
		int j=e[u].to;
		if(!dfn[j])
		{
			tarjan(j,u);
			low[i]=min(low[i],low[j]);
			if(low[j]>dfn[i])
			{
				bri[u]=bri[u^1]=1;
			}
		}
		else if(u!=(in_edge^1))
		{
			low[i]=min(low[i],dfn[j]);
		}
	}
	if(dfn[i]==low[i])
	{
		ecnt++;
		int v=0;
		while(v!=i)
		{
			v=s[top];
			top--;
			id[v]=ecnt;
			siz[ecnt]++;
		}
	}
}
int rt[305],st[305];
bool vis[305],dp[305][305][2];
void dfs(int u,int root)
{
	vis[u]=1;
	rt[u]=root;
	for(int i=head1[u];i;i=e1[i].nxt)
	{
		int v=e1[i].to;
		if(vis[v]) continue;
		dfs(v,root);
//		printf("%d %d %d %d\n",u,v,siz[u],siz[v]);
		siz[u]+=siz[v];
	}
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%lld",&n,&m,&c);
		for(int i=1;i<=n;i++)
		{
			head[i]=head1[i]=dfn[i]=low[i]=0;
			siz[i]=vis[i]=bri[i]=0;
		}
		top=cnt=idx=ecnt=0;
		edgenum=1,edgenum1=0;
		for(int i=1;i<=m;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			add_edge(u,v);
			add_edge(v,u);
		}
		for(int i=1;i<=n;i++)
		{
			if(!dfn[i])
			{
				tarjan(i,0);
			}
		}
		for(int i=1;i<=n;i++)
		{
//			printf("%d ",id[i]);
			for(int j=head[i];j;j=e[j].nxt)
			{
				int v=e[j].to;
				if(id[i]!=id[v])
				{
//					printf("%d %d\n",i,v);
					add_edge1(id[i],id[v]);
				}
			}
		}
		if(ecnt==1)
		{
			printf("-1\n");
			continue;
		}
		cnt=0;
//		for(int i=1;i<=ecnt;i++)
//		{
//			printf("%d ",siz[i]);
//		}
		for(int i=1;i<=ecnt;i++)
		{
			if(!vis[i])
			{
				dfs(i,i);
				st[++cnt]=i;
			}
		}
//		printf("%d\n",ecnt);
		for(int i=1;i<=cnt;i++)
		{
			for(int j=0;j<=n;j++)
			{
				dp[i][j][0]=dp[i][j][1]=0;
			}
		}
		dp[0][0][0]=1;
		c=(cnt-1)*c;
		for(int i=1;i<=cnt;i++)
		{
//			printf("%d ",st[i]);
			int srt=siz[st[i]];
			for(int j=0;j<=n;j++)
			{
				if(j>=srt)
				{
					dp[i][j][0]|=dp[i-1][j-srt][0];
					dp[i][j][1]|=dp[i-1][j-srt][1];
				}
				dp[i][j][0]|=dp[i-1][j][0];
				dp[i][j][1]|=dp[i-1][j][1];
				for(int k=1;k<=ecnt;k++)
				{
					if(rt[k]!=st[i]||k==st[i]) continue;
					if(j>=srt-siz[k]) dp[i][j][1]|=dp[i-1][j-srt+siz[k]][0];
					if(j>=siz[k]) dp[i][j][1]|=dp[i-1][j-siz[k]][0];
				}
			}
		}
		ll ans=1e17;
		for(int i=0;i<=n;i++)
		{
			if(dp[cnt][i][0]||dp[cnt][i][1])
			{
				ans=min(ans,1ll*i*i+1ll*(n-i)*(n-i));
//				printf("%d %d\n",dp[cnt][i][0],dp[cnt][i][1]);
			}
		}
		printf("%lld\n",ans+c);
	}
	return 0;
}

Construct Tree

https://www.gxyzoj.com/d/hzoj/p/4477

先将l按从小到大排序,如果ln+ln1>d一定无解

因为最优当前情况一定是将所有多余的边连在同一个点上,所以如果有一种情况能包含最长边,就一定有解

所以设fi表示i能否被l1ln1表示,如果fdln为1,则有解

另外一种情况是虽然无法用到最长边,但是可以将直径分成两部分,满足两部分长度均大于an

dpi,j表示前半段长为i,后半段长为j是否可行

此时,判断ilnjlni+j=d的情况有没有可行解即可

因为时间复杂度为O(nd2),可以bitset优化

点击查看代码
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
int T,a[2005],f[2005],n,d;
bitset<2005> b[2001];
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&d);
		for(int i=0;i<=d;i++)
		{
			b[i]=0,f[i]=0;
		}
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
		}
		sort(a+1,a+n+1);
		if(a[n]+a[n-1]>d)
		{
			printf("No\n");
			continue;
		}
		f[0]=1;
		for(int i=1;i<n;i++)
		{
			for(int j=d-a[n];j>=a[i];j--)
			{
				f[j]|=f[j-a[i]];
			}
		}
		if(f[d-a[n]])
		{
			printf("Yes\n");
			continue;
		}
		b[0][0]=1;
		for(int i=1;i<=n;i++)
		{
			for(int j=d;j>=a[i];j--)
			{
				b[j]|=(b[j]<<a[i]);
				b[j]|=b[j-a[i]];
			}
			for(int j=0;j<a[i];j++)
			{
				b[j]|=(b[j]<<a[i]);
			}
		}
		bool fl=0;
		for(int i=a[n];i<=d;i++)
		{
			int j=d-i;
			if(j<a[n]) break;
			if(b[i][j])
			{
				fl=1;
			}
		}
		if(fl) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

Light Bulbs (Hard Version)

https://www.gxyzoj.com/d/hzoj/p/4479

人类智慧题(我的秒随机数被卡了!!!),CF题解说这是异或哈希

我们可以将所有从1到i每个数出现次数均为偶数的点标记,两点之间的定义为偶区间(不包括左,包括右)

那么偶区间每个数出现次数均为偶数,必然可以通过改变一个状态满足条件

因为如果这个区间长度大于二的情况下,左端点和它对应的数之间必然存在别的数,不然可以分成功更小的区间

而其中的数同理,所以,必然可以通过改变左端点使其满足条件

找左端点是容易的,但是如何统计方案数

在一段区间中,如果其中的一段所有数字的个数均为偶数,因为没有相同的数,那么改变这个区间的数必然不能影响到其他部分

所以,应当除去这些段,才是一个偶区间的总方案数

考虑如何去除这些段,因为异或有一个性质,当一个数疑惑两次同一个数时,值不变

所以可以先求出异或前缀和,假设当前点为0,则就是偶区间的右端点

因为每个数只会出现两次,所以同一个值至多出现两次,记录每一个值最后出现的位置

此时,假设当前点为i,值为x,另一个为x的点在j,记为lstx,那么[i+1,j]区间内一定满足数字的个数均为偶数

所以,每次直接遍历lstx+1即可,因为不好处理每个数值对应的状态,可以随机数

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int n,T,a[400004];
ll w[400005],s[400005];
ll ans;
map<ll,int> lst;
mt19937_
ll get_rnd()
{
	ll x=0;
	while(!x)
	{
		x=rnd();
	}
	return x;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
//		srand((unsigned)time(0));
		ans=1;
		lst.clear();
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			w[i]=get_rnd();
		}
		ll cnt=0;
		for(int i=1;i<=n*2;i++)
		{
			scanf("%d",&a[i]);
			s[i]=s[i-1]^w[a[i]];
			lst[s[i]]=i;
			if(!s[i]) cnt++;
		}
		for(int i=0;i<n*2;i++)
		{
			if(s[i]) continue;
			int j=i+1,res=1;
			while(s[j])
			{
				j=lst[s[j]]+1;
				res++;
			}
			ans=ans*res%mod;
//			printf("%d ",res);
		}
		printf("%lld %lld\n",cnt,ans);
	}
	return 0;
}

Twin Friends

https://www.gxyzoj.com/d/hzoj/p/4480

其实是简单题

其实A串的顺序没有很大关系,主要看B串的对应情况

因为分为两种情况,直接对应和+1对应,所以设fi,j表示当前是第i种字符,i+1种用了j个

此时,暴力的转移方程为fi,j=k=0cntbicntai+jfi1,kCj,cntai

因为这样重复的相加过多,会T,前缀和优化即可

点击查看代码
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int mod=998244353;
int n,m,cnta[30],cntb[30];
ll fac[200005],inv[200005],s[200005],f[27][200005];
string a,b;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
ll C(int x,int y)
{
	if(x>y) return 0;
	return fac[y]*inv[x]%mod*inv[y-x]%mod;
}
int main()
{
	scanf("%d%d",&n,&m);
	cin>>a;
	cin>>b;
//	cout<<b;
	for(int i=0;i<a.size();i++)
	{
		cnta[a[i]-'A'+1]++;
	}
	for(int i=0;i<b.size();i++)
	{
		cntb[b[i]-'A'+1]++;
	}
	fac[0]=1;
	for(int i=1;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%mod;
	}
	inv[n]=qpow(fac[n],mod-2);
	for(int i=n-1;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%mod;
	}
	ll sum=fac[n];
	for(int i=0;i<=cntb[1];i++)
	{
		s[i]=1;
	}
//	for(int i=1;i<=26;i++)
//	{
//		printf("%d ",cntb[i]);
//	}
	for(int i=1;i<=26;i++)
	{
//		printf("%d %d\n",cnta[i],cntb[i]);
		for(int j=max(0,cnta[i]-cntb[i]);j<=min(cntb[i+1],cnta[i]);j++)
		{
			f[i][j]=s[cntb[i]-cnta[i]+j]*C(j,cnta[i])%mod;
//			printf("%d %d %lld\n",i,j,f[i][j]);
		}
		s[0]=f[i][0];
		for(int j=1;j<=cntb[i+1];j++)
		{
			s[j]=(s[j-1]+f[i][j])%mod;
		}
		sum=sum*inv[cnta[i]]%mod;
	}
	printf("%lld",s[0]*sum%mod);
	return 0;
}

One-X

https://www.gxyzoj.com/d/hzoj/p/CF1905E

对于一个节点x,假设它左区间有a个叶子,右区间有b个叶子,它对答案的贡献就是(2a1)(2b1)x

所以第一要考虑区间的大小和个数,第二要考虑编号

先证明一个结论,每一层至之多有两种长度

假设当前根长度为len,则左边为len2,右边为len2

两个之多相差一,要么相等,要么一奇一偶,对于一奇一偶的情况,先将偶数除以2,此时,将奇数家去这个值,必然只差一,以此类推,显然

接下来考虑长度,可以统计两个量,区间个数和编号和

因为左边是直接乘2,所以转移时乘2就是新的编号和,右区间要+1,因为每个区间都能分裂,座椅最后应在乘2的基础上+区间个数

注意,当区间长度均为1时,终止

点击查看代码
#include<cstdio>
#define ll long long
using namespace std;
const int mod=998244353;
int T;
ll n,a,b,cnta,cntb,suma,sumb,ans;
ll qpow(ll x,ll y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld",&n);
		a=n,cnta=1,suma=1;
		b=cntb=sumb=0;
		ll a1,b1,cnta1,cntb1,suma1,sumb1;
		ans=0;
		while(1)
		{
			if(a==1) break;
			a1=b1=cnta1=cntb1=suma1=sumb1=0;
			ll x=(a+1)/2;
			a1=x,cnta1=cnta,suma1=suma*2;
			ll y=a/2;
			if(y==a1) cnta1+=cnta,suma1+=suma*2+cnta;
			else b1=y,cntb1=cnta,sumb1=suma*2+cnta;
			ans=(ans+(qpow(2,x)-1)*(qpow(2,y)-1)%mod*suma%mod)%mod;
			if(b>1)
			{
				x=(b+1)/2;
				if(x==a1) cnta1+=cntb,suma1+=sumb*2;
				else b1=x,cntb1+=cntb,sumb1+=sumb*2;
				y=b/2;
				if(y==a1) cnta1+=cntb,suma1+=sumb*2+cntb;
				else b1=y,cntb1+=cntb,sumb1+=sumb*2+cntb;
				ans=(ans+(qpow(2,x)-1)*(qpow(2,y)-1)%mod*sumb%mod)%mod;
			}
			else if(b==1) ans=(ans+sumb)%mod;
			a=a1,cnta=cnta1%mod,suma=suma1%mod;
			b=b1,cntb=cntb1%mod,sumb=sumb1%mod;
		}
		printf("%lld\n",(ans+suma)%mod);
	}
	return 0;
}

Jellyfish and EVA

https://www.gxyzoj.com/d/hzoj/p/4253

注意题面,说的是单项边,而且只能连向编号更大的点,所以是DAG,,可以dp

挂个之前写过的:https://www.cnblogs.com/wangsiqi2010916/p/18077354

Another MEX Problem

https://www.gxyzoj.com/d/hzoj/p/4481

因为异或和mex不好转移,考虑可行性dp

fi,j表示前i个数,值为j是否可行

但是暴力是O(n3)的,考虑优化

因为区间很多,只考虑什么区间是有贡献的

假设区间[l,r]是优的,那么必然存在当前的mex值去掉左或右的一个会变小,不然小区间更优

最后合法的区间很少,可以暴力枚举

点击查看代码
#include<cstdio>
#include<vector>
using namespace std;
int T,n,a[5005],mex[5005][5005];
bool vis[5005],f[5005][10005];
vector<int> v[5005];
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=0;i<=n;i++)
		{
			for(int j=0;j<=8191;j++)
			{
				f[i][j]=0;
			}
		}
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
		}
		for(int i=1;i<=n;i++)
		{
			int now=0;
			for(int j=0;j<=n;j++)
			{
				vis[j]=0;
			}
			for(int j=i;j<=n;j++)
			{
				vis[a[j]]=1;
				while(vis[now]) now++;
				mex[i][j]=now;
			}
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=i;j<=n;j++)
			{
				if((i==j)||(mex[i][j-1]!=mex[i][j]&&mex[i][j]!=mex[i+1][j]))
				{
					v[i-1].push_back(j);
				}
			}
		}
		f[0][0]=1;
		for(int i=0;i<=n;i++)
		{
			for(int j=0;j<=8191;j++)
			{
				if(!f[i][j]) continue;
				f[i+1][j]|=f[i][j];
				for(int k=0;k<v[i].size();k++)
				{
					int x=v[i][k];
					f[x][mex[i+1][x]^j]|=f[i][j];
				}
			}
		}
		int ans=0;
		for(int i=0;i<=8192;i++)
		{
			if(f[n][i]) ans=i;
		}
		printf("%d\n",ans);
	}
	return 0;
}

Travel Plan

https://www.gxyzoj.com/d/hzoj/p/CF1868C

因为是路径最大值,而且m不大,考虑枚举

假设一条路径的长度为x,那么最大值小于等于y的方案数为yx+m(nx)

而要恰好等于y,减去小于等于y-1的即可

接下来考虑如何统计路径长度对应的个数,显然的,,每条路经都可以拆成两条链,假设一条长x,一条长y

对于一个节点,假设它深度为max(x,y)深度的子树是满的,那么它的情况数就是2x12y1

如果是满二叉树,直接看所对应层及以上的节点数即可

但是在最后一层有节点的情况下,考虑哪些地方会多出解

首先,记这一层的节点数为t,那么会多有t2max(x,y)个满的子树

对于剩下的,看可以分在几个子树中,然后看能否构成即可

点击查看代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int mod=998244353;
int T;
ll n,m,p[70],vis[500],l;
ll get(ll x,ll y)
{
	if(!x&&!y) return n;
	ll u,v,a,b;
	if(x) u=p[x-1]%mod;
	else u=1;
	if(y) v=p[y-1]%mod;
	else v=1;
	ll tmp=u*v%mod,tmp2=(n-p[l]+1)%p[max(x,y)];
	ll t=(p[l-max(x,y)]-1)+(n-p[l]+1)/p[max(x,y)];
	t%=mod;
	tmp=(tmp*t%mod)%mod;
	if(x>=y) a=min(p[x-1],tmp2)%mod;
	else a=u;
	if(x<=y) b=max(0ll,tmp2-p[y-1])%mod;
	else b=v;
	tmp=(tmp+a*b%mod)%mod;
	return tmp;
}
ll qpow(ll x,ll y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d",&T);
	p[0]=1;
	for(int i=1;i<=60;i++)
	{
		p[i]=p[i-1]*2;
	}
	while(T--)
	{
		scanf("%lld%lld",&n,&m);
		l=0;
		for(int i=1;i<=60;i++)
		{
			if(p[i]<=n) l=i;
		}
		ll ans=0;
		for(int i=0;i<=l;i++)
		{
			for(int j=0;j<=l;j++)
			{
				vis[i+j+1]=(vis[i+j+1]+get(i,j))%mod;
			}
		}
		ll t1=qpow(m,mod-2),t2=qpow(m,n);
		for(int i=1;i<=m;i++)
		{
			ll a=1,b=1,now=t2,sum=0;
			for(int j=1;j<=2*l+1;j++)
			{
				a=a*i%mod,b=b*(i-1)%mod,now=now*t1%mod;
				sum=(sum+now*((a-b)%mod+mod)%mod*vis[j]%mod)%mod; 
			}
			ans=(ans+sum*i%mod)%mod;
		}
		printf("%lld\n",ans);
		for(int i=1;i<=l*2+1;i++) vis[i]=0;
	}
	return 0;
}

Mighty Rock Tower

https://www.gxyzoj.com/d/hzoj/p/4255

挂个博客,不想打LaTeX了 https://www.luogu.com.cn/article/g9o3xdhh

BZOJ2164 采矿

https://www.gxyzoj.com/d/hzoj/p/4482

线段树维护去x个的最大值和最大区间和,树剖求解即可

点击查看代码
#include<cstdio>
#include<algorithm>
#define ll long long
#define lid id<<1
#define rid id<<1|1
using namespace std;
int n,m,head[20004],edgenum;
struct edge{
	int nxt,to;
}e[20004];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
ll x=1<<16;
signed long int a,b,y=(1ll<<31)-1,q;
ll get()
{
	a=((a^b)+b/x+b*x)&y;
	b=((a^b)+a/x+a*x)&y;
	return (a^b)%q;
}
int dep[20005],f[20005],siz[20005],son[20005],top[20005];
int dfn[20005],cnt,rnk[20005];
void dfs(int u,int fa)
{
	dep[u]=dep[fa]+1;
	f[u]=fa,siz[u]=1;
	int maxn=-1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		siz[u]+=siz[v];
		if(siz[v]>maxn)
		{
			maxn=siz[v],son[u]=v;
		}
	}
}
void dfs2(int u,int t)
{
	top[u]=t,dfn[u]=++cnt;
	rnk[cnt]=u;
	if(son[u])
	{
		dfs2(son[u],t);
	}
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==son[u]) continue;
		dfs2(v,v);
	}
}
ll w[20005][51];
struct seg_tr{
	int l,r;
	ll f[51],g[51];
}tr[160005];
seg_tr merge(seg_tr x,seg_tr y,int l,int r)
{
	seg_tr tmp;
	for(int i=0;i<=m;i++)
	{
		tmp.f[i]=tmp.g[i]=0;
	}
	for(int i=0;i<=m;i++)
	{
		tmp.g[i]=max(x.g[i],y.g[i]);
		for(int j=0;j<=i;j++)
		{
			int k=i-j;
			tmp.f[i]=max(tmp.f[i],x.f[j]+y.f[k]);
		}
	}
	tmp.l=l,tmp.r=r;
	return tmp;
}
void build(int id,int l,int r)
{
	tr[id].l=l,tr[id].r=r;
	if(l==r)
	{
		for(int i=0;i<=m;i++)
		{
			tr[id].f[i]=tr[id].g[i]=w[l][i];
		}
		return;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	tr[id]=merge(tr[lid],tr[rid],l,r);
}
void update(int id,int x)
{
	if(tr[id].l==tr[id].r)
	{
		for(int i=0;i<=m;i++)
		{
			tr[id].f[i]=tr[id].g[i]=w[x][i];
		}
		return;
	}
	int mid=(tr[id].l+tr[id].r)>>1;
	if(x<=mid) update(lid,x);
	else update(rid,x);
	tr[id]=merge(tr[lid],tr[rid],tr[id].l,tr[id].r);
}
seg_tr query(int id,int l,int r)
{
	if(tr[id].l==l&&tr[id].r==r)
	{
		return tr[id];
	}
	int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid) return query(lid,l,r);
	else if(l>mid) return query(rid,l,r);
	else return merge(query(lid,l,mid),query(rid,mid+1,r),0,0); 
}
seg_tr getsum(int u,int v)
{
	seg_tr res;
	for(int i=0;i<=m;i++)
	{
		res.f[i]=res.g[i]=0;
	}
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res=merge(res,query(1,dfn[top[u]],dfn[u]),0,0);
		u=f[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	res=merge(res,query(1,dfn[v],dfn[u]),0,0);
	return res;
}
int main()
{
//	freopen("1.txt","w",stdout);
	scanf("%d%d%ld%ld%ld",&n,&m,&a,&b,&q);
	for(int i=2;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		add_edge(x,i);
	}
//	printf("1");
	dfs(1,0);
	dfs2(1,1);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			w[dfn[i]][j]=get();
		}
		sort(w[dfn[i]]+1,w[dfn[i]]+m+1);
	}
	build(1,1,n);
	int c;
	scanf("%d",&c);
	while(c--)
	{
		int opt;
		scanf("%d",&opt);
		if(opt==1)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			seg_tr tmp=query(1,dfn[u],dfn[u]+siz[u]-1);
			if(u!=v)
			{
				seg_tr t1=getsum(f[u],v);
				ll ans=0;
				for(int i=0;i<=m;i++)
				{
					int j=m-i;
					ans=max(ans,tmp.f[i]+t1.g[j]);
//					if(u==132&&v==72)
//					printf("%d \n",ans);
				}
				printf("%lld\n",ans);
			}
			else printf("%lld\n",tmp.f[m]);
		}
		else
		{
			int x;
			scanf("%d",&x);
			for(int j=1;j<=m;j++)
			{
				w[dfn[x]][j]=get();
			}
			sort(w[dfn[x]]+1,w[dfn[x]]+m+1);
			update(1,dfn[x]);
		}
	}
	return 0;
}
posted @   wangsiqi2010916  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示