2024.5 总结

动态规划

【YBOJ】 序列立方

题目描述

给出一个包含 \(n\) 个数的序列 \(a\) ,求所有不同的子序列的出现次数的立方和,\(1 \le n \le 250\)

解题思路

枚举出现过的子序列时间复杂度会直接爆炸,考虑 \(dp\)
将此问题转换成组合问题,我们每次选位置不同的 \(3\) 个子序列,求有多少种方案使得 \(3\) 个子序列相同。
那么我们就可以设状态 \(f_{i,j,k}\) 表示 \(3\) 个子序列末尾分别为 \(a_i,a_j,a_k\) 的方案数,转移时间复杂度 \(O(n^6)\)
我们加上上前缀和优化就可以做到 \(O(n^3)\)

【Luogu P1758】 管道取珠

题目描述

给出两个长度分别为 \(n,m\) 的序列 \(a,b\) ,每次从两个序列中的一个序列的末尾选一个数加到序列 \(c\) 的末尾,求不同的序列 \(c\) 出现次数的平方和,\(1 \le n,m \le 500\)

题目描述

和上一题类似,直接统计过难,考虑转成组合问题。
讲题目所求转化成选出两个 \(c\) 序列相同的方案数,直接做 \(dp\)
设状态 \(f_{i,j}\) 表示 \(a,b\) 序列分别选到 \(a_i,b_j\) 的方案数,用类似传纸条的方法,转移时间复杂度 \(O(n^3)\)

Code

#include<bits/stdc++.h>
using namespace std;
const long long mod=1024523;
int n,m;
string a,b;
long long f[505][505][2];
int main()
{
	scanf("%d%d",&n,&m);
	cin>>a>>b;
	f[0][0][0]=1;
	for(int i=1;i<=n+m;i++)
	{
		for(int j=0;j<=n;j++)
		{
			for(int u=0;u<=n;u++)
			{
				f[j][u][i&1]=0;
				if(j!=0&&u!=0&&a[j-1]==a[u-1])f[j][u][i&1]=(f[j][u][i&1]+f[j-1][u-1][(i&1)^1])%mod;
				if(i-j>0&&i-u>0&&i-j<=m&&i-u<=m&&b[i-j-1]==b[i-u-1])f[j][u][i&1]=(f[j][u][i&1]+f[j][u][(i&1)^1])%mod;
				if(j!=0&&i-u>0&&i-u<=m&&a[j-1]==b[i-u-1])f[j][u][i&1]=(f[j][u][i&1]+f[j-1][u][(i&1)^1])%mod;
				if(u!=0&&i-j>0&&i-j<=m&&b[i-j-1]==a[u-1])f[j][u][i&1]=(f[j][u][i&1]+f[j][u-1][(i&1)^1])%mod;
			}
		}
	}
	cout<<f[n][n][(n+m)&1];
  return 0;
}

【AGC013E】 Placing Squares

题目描述

给定一个长度为 \(n\) 的木板,木板上有 \(m\) 个标记点,距离木板左端点的距离分别为 \(X_i\),现在你需要在木板上放置一些不相交正方形,正方形需要满足

  • 正方形的边长为整数

  • 正方形底面需要紧贴木板

  • 正方形不能超出木板,正方形要将所有的木板覆盖

  • 标记点的位置不能是两个正方形的交界处

一种合法的正方形放置方案的贡献为所有正方形面积的乘积,也就是为 \(\prod\limits_{i=1}^k a_i^2\)\(a_i\) 为正方形的边长。
请你求出所有合法方案的贡献之和,答案对 \(10^9+7\) 取模,\(n \leq 10^9\)\(m \leq 10^5\)

解题思路

统计正方形的边长会将时间复杂度做到 \(O(n^2)\) 考虑转组合问题。
先不管有标记点,将问题转化成在正方形靠木板的面上选两点的方案数,这样我们只需要设 \(f_{i,j}\) 表示做到 \(i\) 同时当前放点的状态为 \(j\) (\(0 \le j \le 3\)) ,做 \(dp\) 即可。
考虑标记点,在标记点出只是 \(j=3\) 的状态不能转移到其他状态去,我们只需做一个 \(O(n)\)\(dp\)
由于 \(m \le 10^5\) ,我们将每一段分开来处理,做矩阵加速,时间复杂度 \(16 \times mlogn\)

Code

#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
struct datay
{
	long long a[4][4];
}c;
datay operator *(const datay &q,const datay &w)
{
	memset(c.a,0,sizeof(c.a));
	for(int i=0;i<4;i++)
	{
		for(int j=0;j<4;j++)
		{
			for(int u=0;u<4;u++)c.a[i][j]=(c.a[i][j]+q.a[i][u]*w.a[u][j]%mod)%mod;
		}
	}
	return c;	
}
datay pows(datay x,long long y)
{
	datay h;memset(h.a,0,sizeof(h.a)),h.a[0][0]=h.a[1][1]=h.a[2][2]=h.a[3][3]=1;
	while(y)
	{
		if(y&1)h=(h*x);
		x=(x*x),y>>=1;
	}
	return h;
}
int n,m,d[100005];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d",&d[i]);
	datay x,y,z,x1;
	memset(y.a,0,sizeof(y.a)),memset(x.a,0,sizeof(x.a));
	y.a[0][0]=y.a[0][1]=y.a[0][2]=y.a[0][3]=y.a[1][1]=y.a[1][3]=y.a[2][2]=y.a[2][3]=y.a[3][0]=y.a[3][1]=y.a[3][2]=1,y.a[3][3]=2;
	x.a[0][0]=1,d[++m]=n;
	for(int i=1;i<=m;i++)
	{
		memset(x1.a,0,sizeof(x1.a));
		x1.a[0][0]=x.a[0][0],x1.a[0][1]=(x.a[0][0]+x.a[0][1])%mod,x1.a[0][2]=(x.a[0][0]+x.a[0][2])%mod,x1.a[0][3]=(x.a[0][0]+x.a[0][1]+x.a[0][2]+x.a[0][3])%mod;
		z=pows(y,d[i]-d[i-1]-1),x=x1*z; 
	}
	cout<<x.a[0][3];
  return 0;
}

【Luogu P4492】 苹果树

题目描述

一开始有一个树根,每个节点最多有两个分叉,每次每个节点的分叉会等概率的长出一个节点,求加了 \(n\) 次后每个节点到其他节点的期望距离和,\(1 \le n \le 2000\)

解题思路

将距离和转为组合问题。
考虑每个节点向上连的那条边会被经过几次,设该节点为 \(x\) ,子树大小为 \(size_x\) ,很明显经过的次数为 \(size_x \times (n-size_x)\)
\(size_x\) 至多为 \(n\) ,那我们就可以做 \(dp\) 来求 \(size_x\)\(i\) 的节点期望个数。
设已经有了 \(i\) 个节点,很明显,剩余分叉的个数为 \(i+1\) ,对于一个子树来讲也同理。
那我们求可以求出该子树的 \(size+1\) 的概率并进行转移。
做一个时间复杂度 \(O(n^2)\)\(dp\) ,统计答案时我们只需要按照开始所说的处理方案即可。

Code

#include<bits/stdc++.h>
using namespace std;
long long n,mod,inv[2005],f[2005][2005],p[2005];
int main()
{
	scanf("%lld%lld",&n,&mod);
	f[0][0]=1,p[0]=1;
	for(int i=1;i<=n+1;i++)p[i]=p[i-1]*i%mod; 
	for(int i=1;i<=n;i++)
	{
		f[i][1]=(p[i]+f[i-1][1]*(i-2)%mod)%mod;
		for(int j=2;j<=i;j++)f[i][j]=(f[i][j]+f[i-1][j-1]*(j)%mod+f[i-1][j]*(i-j-1)%mod)%mod;
	}
	long long s=0;
	for(int i=1;i<=n;i++)s=(s+f[n][i]*(i*(n-i)%mod)%mod)%mod;
	cout<<s;
  return 0;
}

【GJOI】 珠宝

题目描述

\(n\) 个物品,对于所有 \(\forall i,1\le i\le m\),输出花费 \(i\) 能获得最大价值。
保证价格 \(1\le c_i\le 300\),价值 \(1\le v_i\le 10^9\)
\(1\le n\le 10^6,1\le m\le 5\times 10^4\)

解题思路

直接做 \(01\) 背包时间复杂度为 \(O(nm)\) ,考虑优化。
观察到 \(c_i\) 最大 \(300\) ,考虑将相同的 \(c_i\) 的物品的 \(v_i\) 加到一个 \(vector\) 里,再做背包。
考虑 \(i \in [1,300]\) 表示物品的大小,那么对于 \(j \in [1,m]\) 满足 \(j \% i\) 相同的我们就一起处理,使得更新 \(dp\) 值只与一起处理的有关。
这样 \(dp\) 的话明显是有决策单调性的,跑分治即可。
时间复杂度 \(\max_{i=1}^n c_i mlogm\)

Code

#include<bits/stdc++.h>
using namespace std;
long long n,m,b1[1000005],f[1000005],d[1000005],t[1000005];
multiset<long long> b[305];
vector<long long> a[305];
void solve(int x,int l,int r,int L,int R)
{
	if(l>r)return;
	long long mid=(l+r)>>1,q=max(mid-(long long)(a[x].size()),(long long)L),w=min((long long)R,mid),e,rr=-1e9-5;
	for(int i=q;i<=w;i++)
	{
		e=f[b1[i]]+(mid==i?0:a[x][mid-i-1]);
		if(e>rr)rr=e,t[mid]=i;
	}
	d[b1[mid]]=rr,solve(x,l,mid-1,L,t[mid]),solve(x,mid+1,r,t[mid],R);
	return;
}
int main()
{
	long long x,y,k;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&x,&y),b[x].insert(y);
	for(int i=1;i<=300;i++)
	{
		x=0,k=0;
		multiset<long long>::iterator q=b[i].begin();
		for(;q!=b[i].end();q++)x=*q,b1[++k]=x;
		b1[k+1]=0;
		for(int j=k;j>=1;j--)b1[j]+=b1[j+1],a[i].push_back(b1[j]);
	}
	for(int i=1;i<=300;i++)
	{
		for(int j=1;j<=m;j++)d[j]=f[j];
		for(int j=0;j<i;j++)
		{
			k=0;
			for(int u=j;u<=m;u+=i)b1[++k]=u,t[k]=0;
			solve(i,1,k,1,k);
		}
		for(int j=1;j<=m;j++)f[j]=max(f[j],d[j]);
	}
	for(int i=1;i<=m;i++)printf("%lld ",f[i]);
  return 0;
}

【Luogu P10260】 Rolete

题目描述

一个周六的下午,Luka 从午睡中醒来,想起今天有 COCI!在比赛前,他只需要做一件事情:拉起窗帘。
卢卡的房间里有 \(n\) 个窗帘,其中第 \(i\) 个窗帘距离窗户顶部下降了 \(a_i\) 厘米。他可以通过两种方式拉起窗帘:

  • 他可以手动开始拉起任意一个窗帘。使用这种方法,他每拉起 \(1\) 厘米需要 \(t\) 秒钟。
  • 他可以按下一个按钮,同时以相同的速度将所有窗帘同步拉起。

按下按钮时,窗帘的上升速度定义如下:如果所有窗帘都在上升,每个窗帘都会在 \(s\) 秒内上升 \(1\) 厘米。如果 \(r\) 个窗帘已经完全升起,这会减慢系统的速度。那么剩余的窗帘每上升 \(1\) 厘米需要 \(s + k \cdot r\) 秒钟。
COCI 即将开始,Luka 希望尽快拉起他的窗帘。与此同时,他的兄弟 Marin 走进房间,问了他 \(q\) 个问题:为了使窗帘下降的最大高度不超过 \(h\) 厘米,你需要的最短时间是多少?Marin 对于每个问题都考虑窗帘的初始状态。
他们意识到在 COCI 之前没有足够的时间来考虑这个问题。幸运的是,这个问题也出现在这里!帮助他们解决它!
注意:Luka 总是将窗帘拉起一个整数值的厘米,$ (1\le n,t,s,q\le 10^5,0\le k\le 10^5)$

解题思路

我们可以预处理出来拉到某一个值以上的答案。
我们先拉所有的再拉单个的肯定优,并且要拉到的目标越大所有一起拉的肯定也更大。
这就有决策单调性了,我们就可以分治解决问题。
用前缀和优化,计算一次答案的时间复杂度为 \(O(1)\) 的,这样总时间复杂度为 \(O(nlogn)\)

Code

#include<bits/stdc++.h>
using namespace std;
int n,a[200005];
long long t[200005],k1,k2,k3,suf[200005],d[200005],f[200005],b[200005];
long long gaia(int x,int y)
{
	return t[y]+k1*((suf[d[x+y]+1]-suf[n+1])-(long long)(n-d[y+x])*(y+x));
}
void solve(int l,int r,int L,int R)
{
	if(l>r)return;
	int mid=(l+r)>>1;
	long long x;
	for(int i=L;i<=R;i++)
	{
		x=gaia(mid,i);
		if(x<f[mid])b[mid]=i,f[mid]=x;
	}
	solve(l,mid-1,b[mid],R),solve(mid+1,r,L,b[mid]);
	return;
}
int main()
{
	memset(f,0x7F,sizeof(f));
	long long q=1,k,x;
	scanf("%d%lld%lld%lld",&n,&k1,&k2,&k3);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	for(int i=1;i<=200000;i++)
	{
		while(q<=n&&a[q]<i)q++;
		t[i]=t[i-1]+k2+k3*(q-1),d[i]=q-1;
	}
	for(int i=n;i>=1;i--)suf[i]=suf[i+1]+a[i];
	solve(0,100000,0,100000);
	scanf("%lld",&k);
	for(int i=1;i<=k;i++)scanf("%lld",&x),printf("%lld ",f[x]);
 return 0;
}

【Luogu P5999】 kangaroo

题目描述

\(n\) 个格子,一只袋鼠要从 \(s\) 跳到 \(t\) ,一次能跳到任意未去过的格子但必须满足前一次去的格子与后一次去的格子所在的位置必须同时大于或小于现在所在位置,求跳的方案数,\(1 \le n\le 2\times 10^3\)

解题思路

若将其转化为一个排列,求满足相邻两项的大小关系的数量,那我们用容斥 \(+dp\) 就可以在 \(O(n^2)\) 解决。
但这里要求开头与结尾,考虑插入 \(dp\)
\(f_{i,j}\) 表示从小到大做到 \(i\) 时,我们把已有的数分成了 \(j\) 段并组合的方案数,满足这里面每一段里的开头与结尾一个数小于其已确定的相邻项。
那么每新增一个数不为首项或尾项,要不然它新开一段,或它将两段连起来,从 \(f_{i-1,j-1}\)\(f_{i-1,j+1}\) 转移过来。
若为首项或尾项,只能单独开一段或连一段,从 \(f_{i-1,j}\)\(f_{i-1,j-1}\) 转移。
时间复杂度 \(O(n^2)\) ,最后的答案为 \(f_{n,1}\)

Code

#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
int n,st,en;
long long f[2005][2005];
int main()
{
	long long q=0;
	scanf("%d%d%d",&n,&st,&en),f[1][1]=1;
	for(int i=2;i<=n;i++)
	{
		q=0;
		if(i==st||i==en)
		{
			for(int j=1;j<=i;j++)f[i][j]=(f[i-1][j-1]+f[i-1][j])%mod;
			continue;
		}
        q-=(i>st)+(i>en);
		for(int j=1;j<=i;j++)f[i][j]=((++q)*f[i-1][j-1]+f[i-1][j+1]*j)%mod;
	}
	cout<<f[n][1];
  return 0;
}

【ARC137D】 Prefix XORs

题目描述

给定一个长度为 \(n\) 的序列 \(A\)(下标从 \(1\) 开始),还有一个整数 \(m\)
\(k = 1,2,\cdots ,m\),求经过如下操作 \(k\) 次后 \(A_n\) 的值:

  • 对每个 \(i(1\leq i \leq n)\),同时让 \(A_i := A_1\oplus A_2\oplus \cdots \oplus A_i\)

解题思路

所以 \(A_i\) 对第 \(j\) 次操作的 \(A_n\) 异或的次数为 \(C_{j-1}^{n+j-1}\) ,因为异或的性质,我们只关心这个数的奇偶性。
根据 \(Lucas\) 定理,当 \(j-1\)\(n+j-1\) 的子集时才会为 \(1\)
那我们就可以枚举子集,时间复杂度 \(3^{20}\)
\(SOSdp\) ,时间复杂度降到 \(O(nlogn)\)

Code

#include<bits/stdc++.h>
using namespace std;
int n,m,s,a[2000005],all=(1<<20)-1; 
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[n-i]);
	for(int i=0;i<=20;i++)
	{
		for(int j=1;j<=all;j++)if(j&(1<<i))a[j]^=a[j^(1<<i)];
	}
	for(int i=1;i<=m;i++)printf("%d ",a[all^(i-1)]);

  return 0;
}

【CF1770F】Koxia and Sequence

题目描述

给定非负整数 \(n,x,y\),对于所有满足 \(\sum_{i=1}^n a_i=x,\text{OR}_{i=1}^n a_i=y\),的序列 \(\{a_n\}\)。求 \(\bigoplus_{i=1}^n a_i\) 的异或和,\(1 \le n \le 2^40 ,1 \le X \le 2^60,1 \le Y \le 2^20\)

解题思路

根据序列的对称性,若 \(n\) 为偶数,答案为 \(0\)\(n\) 为奇数时答案为 \(a_i\) 的异或和。
我们考虑 \(a_1\) 项所有可能的异或和。
由于题目中有或操作,我们考虑容斥。
设我们枚举的 \(a_1=i\) ,容斥到的数为 \(T\) 满足 \(i \in T \in Y\),表示求 \(T\) 的子集的贡献异或和,由于异或操作,正负号可以忽略。
照常枚举的话我们可以枚举 \(T\) ,用 \(SOSdp\) 做,但比较麻烦。
对于 \(a_1\) 我们一位一位的考虑,设考虑到第 \(i\) 位,那么需满足 \(a_1-2^i \in T-2^i,a_2 \in T,...,a_n \in T\) ,同时 \(a_1+a_2+...+a_n=X\) ,即 \(a_1-2^i+a_2+...+a_n=X-2^i\)
逆用 \(Lucas\) 定理 \(mod\) \(2\) 时的性质,转化成 \(C_{a_1-2^i}^{T-2^i}C_{a_2}^{T}...C_{a_n}^{T}\) ,再结合和为 \(X\) ,用范德蒙卷积转化为 \(C_{X-2^i}^{T\times n-2^i}\)
已确定 \(i\)\(T \in Y\) ,我们只需枚举 \(T\) 即可。
时间复杂度 \(O(YlogY)\)

Code

#include<bits/stdc++.h>
using namespace std;
long long s,all=(1<<20)-1,n,X,Y,x;
int main()
{
	scanf("%lld%lld%lld",&n,&X,&Y);
	if(!(n&1)){cout<<0;return 0;}
	for(int i=0;i<=19;i++)
	{
		x=(1<<i);
		for(int j=0;j<=all;j++)
		{
			if((!(j&(1<<i)))||((j&Y)!=j))continue;
			if(((X-x)&(n*j-x))==(X-x))s^=x;
		}
	}
	cout<<s;
  return 0;
}

【CF1736E】Swap and Take

题目描述

给定一个长为 \(n\) 的正整数序列 \(a\)。初始你的分数为 \(0\),需要进行 \(n\) 轮操作。
在第 \(i\) 轮,你可以选择交换两个相邻的数并将其中一个变为 \(0\),也可以啥都不干。无论是否交换,第 \(i\) 轮结束后你的分数会多 \(a_i\)
求你最大能得到的分数,\(1 \le n \le 500\)

解题思路

对于每一个数,它只有可能先向左走再向右走。
那对于每次产生贡献,有两种选择:

  • 将一个比上次更后的位置移动到现在的位置。
  • 将前一个数移动过来

那么我们就可以设 \(f_{i,j,k}\) 表示做到第 \(i\) 次,产生贡献的是 \(a_j\) ,还有 \(k\) 次剩余交换次数未用。
这样我们可以有两种转移。

  • \(f_{i,j,k}=f_{i-1,j,k}\)
  • \(f_{i,j,k}=f_{i-1,u,k+(j-i)-1}\) \((i,u \le j)\)

这样 \(dp\) 时间复杂度是 \(O(n^4)\) 的,但是每次会找重复的项,我们处理一下前缀最大值就可以做到 \(O(n^3)\) 了。
空间会爆,要用滚动数组。

Code

#include<bits/stdc++.h>
using namespace std;
long long n,a[505],f[2][505][505],d[2][505][505],s=-1e9;
int main()
{
	memset(f,0x80,sizeof(f)),memset(d,0x80,sizeof(d));
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	f[1][1][1]=a[1],f[1][2][0]=a[2];
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int u=0;u<=n;u++)f[i&1][j][u]=max(f[i&1][j][u],f[(i&1)^1][j][u]+a[j]);
		}
		for(int j=i;j<=n;j++)
		{
			for(int u=0;u<=n;u++)
			{
				if(u+j-i-1>=0&&u+j-i-1<=n)f[i&1][j][u]=max(f[i&1][j][u],d[(i&1)^1][j-1][u+j-i-1]+a[j]);
			}
		}
		for(int j=1;j<=n;j++)
		{
			for(int u=0;u<=n;u++)d[i&1][j][u]=max(d[i&1][j-1][u],f[i&1][j][u]);
		}
	}
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=n;j++)s=max(s,f[n&1][i][j]);
	}
	cout<<s;
  return 0;
}

【CF79D】 Password

题目描述

你有 \(n\) 个灯泡,一开始都未点亮。
同时你有 \(l\) 个长度,分别为 \(a_1 \sim a_l\)
每次你可以选择一段连续的子序列,且长度为某个 \(a_i\),并将这些灯泡的明灭状态取反。
求最少的操作次数,使得最后有且仅有 \(k\) 个位置是亮的,这些位置已经给定,为 \(x_1 \sim x_k\)\(1 \le n \le 10^4,1 \le l \le 100,1\le k \le 10\)

解题思路

首先,你可以将区间操作给差分处理掉,那么问题就变成了每次修改两个位置,最后使若干个位置为 \(1\)
注意到本题操作顺序与答案无关,那我们就可以被操作分组,看成每次选取两个位置用最小代价让它们变为 \(1\)
只有 \(20\) 个位置要求放 \(1\) ,我们可以做状压 \(dp\)
求解代价我们只需考虑从某个要变为 \(1\) 的点出发,因为每次代价为 \(1\)\(bfs\) 即可。
时间复杂度 \(O(4^kk^2+knm)\)

Code

#include<bits/stdc++.h>
using namespace std;
int n,k,m,d[105],d1[10005],a[25][10005],f[2000005],t[100005],r1,l1;
bool v[100005];
vector<int> l; 
void spfa(int start)
{
	memset(v,false,sizeof(v));
	a[start][l[start]]=0;
	v[l[start]]=true,l1=r1=1,t[1]=l[start];
	int x;
	while(l1<=r1)
	{
		x=t[l1],l1++;
		for(int i=1;i<=m;i++)
		{
			if(x+d[i]<=n&&(!v[x+d[i]]))a[start][x+d[i]]=a[start][x]+1,v[x+d[i]]=true,t[++r1]=x+d[i];
			if(x-d[i]>=1&&(!v[x-d[i]]))a[start][x-d[i]]=a[start][x]+1,v[x-d[i]]=true,t[++r1]=x-d[i];
		}
	}
	return;
}
int main()
{
	memset(a,1,sizeof(a)),memset(f,1,sizeof(f)); 
	int x,y,z;
	scanf("%d%d%d",&n,&k,&m);
	for(int i=1;i<=k;i++)scanf("%d",&x),d1[x]^=1,d1[x+1]^=1;
	for(int i=1;i<=m;i++)scanf("%d",&d[i]);
	n++;
	for(int i=1;i<=n;i++)if(d1[i])l.push_back(i);
	k=l.size(),f[0]=0;
	for(int i=0;i<k;i++)spfa(i);
	int p=(1<<k);
	for(int i=0;i<p;i++)
	{
		for(int j=0;j<k;j++)
		{
			for(int u=j+1;u<k;u++)
			{
				if((((1<<j)^(1<<u))&i)==0)f[i^((1<<j)^(1<<u))]=min(f[i^((1<<j)^(1<<u))],f[i]+min(a[j][l[u]],a[u][l[j]]));
			}
		}
	}
	if(f[p-1]<1e7)cout<<f[p-1];
	else cout<<-1;

  return 0;
}

图论

【Luogu P4183】 Cow at Large P

题目描述

贝茜被农民们逼进了一个偏僻的农场。农场可视为一棵有 \(N\) 个结点的树,结点分别编号为 \(1,2,\ldots, N\) 。每个叶子结点都是入口。开始时,每个出口都可以放一个农民(也可以不放)。每个时刻,贝茜和农民都可以移动到相邻的一个结点。如果某一时刻农民与贝茜相遇了(在边上或点上均算),则贝茜将被抓住。抓捕过程中,农民们与贝茜均知道对方在哪个结点。
请问:对于结点 \(i\,(1\le i\le N)\) ,如果开始时贝茜在该结点,最少有多少农民,她才会被抓住。

解题思路

分析一下题目,发现对于只要贝茜可以绕过农民,就能逃出去。
\(d_x\) 表示向下到最近叶子的距离,只要 \(d_x \le dis_{x,root}\) ,这个子树只要存在一个农民即可。
那我们就有了一个 \(O(n^2)\) 的做法。
再次观察,只需要在 \(d_x=dis_{x,root}-1\)\(d_x=dis_{x,root}\) 的子树内放置一个农民即可。
那我们就可以点分治了,预处理出 \(d_x\) ,每次分两类处理,有些麻烦,但能做,时间复杂度 \(O(nlog^2n)\)
注意一个点,我们每次其实是找 \(d_x \le dis_{x,root}\) 的节点构成的连通块个数,且这些连通块都满足一个子树的形式。
统计子树我们可以这样做:我们每次找到一个满足条件的节点,就累加 \(2\) 减去它的度数。
最后,我们发现,累加和即为连通块个数。
这样时间复杂度 \(O(nlog^2n)\) ,打起来简单一点。

Code

#include<bits/stdc++.h>
using namespace std;
int n,tot,root,siz[100005],minn,v1[100005],d[100005],b1[100005],b2[100005],d1[100005],d2[100005],k1,k2,m1,m2,deep[100005],f[100005];
bool v[100005];
vector<int> a[100005];
bool cmp1(int x,int y)
{
	return v1[x]-deep[x]<v1[y]-deep[y];
}
bool cmp2(int x,int y)
{
	return deep[x]<deep[y];
}
void dfs1(int x,int y)
{
	siz[x]=1;
	int q=0;
	for(int i=0;i<a[x].size();i++)
	{
		if(a[x][i]==y||v[a[x][i]])continue;
		dfs1(a[x][i],x),q=max(q,siz[a[x][i]]),siz[x]+=siz[a[x][i]];
	}
	q=max(q,tot-siz[x]);
	if(q<minn)root=x,minn=q;
	return;
}
void dfs2(int x,int y)
{
	if(a[x].size()==1)v1[x]=0;
	for(int i=0;i<a[x].size();i++)if(a[x][i]!=y)dfs2(a[x][i],x),v1[x]=min(v1[x],v1[a[x][i]]+1);
	return;
}
void dfs3(int x,int y)
{
	if(y)v1[x]=min(v1[x],v1[y]+1);
	for(int i=0;i<a[x].size();i++)if(a[x][i]!=y)dfs3(a[x][i],x);
	return;
}
void dfs4(int x,int y)
{
	deep[x]=deep[y]+1;
	for(int i=0;i<a[x].size();i++)
	{
		if(a[x][i]==y||v[a[x][i]])continue;
		dfs4(a[x][i],x);
	}
	b1[++k1]=x,b2[++k2]=x;
	return;
}
void calc(int x)
{
	int q,w;
	deep[x]=0,m1=m2=0;
	d1[++m1]=d2[++m2]=x;
	for(int i=0;i<a[x].size();i++)
	{
		if(v[a[x][i]])continue;
		k1=k2=w=0,q=1,dfs4(a[x][i],x),sort(b1+1,b1+k1+1,cmp1),sort(b2+1,b2+k2+1,cmp2);
		for(int j=1;j<=k2;j++)
		{
			while(q<=k1&&deep[b1[q]]+deep[b2[j]]>=v1[b1[q]])w+=2-d[b1[q]],q++;
			f[b2[j]]-=w;
		}
		for(int j=1;j<=k1;j++)d1[++m1]=b1[j];
		for(int j=1;j<=k2;j++)d2[++m2]=b2[j];
	}
	q=1,w=0,sort(d1+1,d1+m1+1,cmp1),sort(d2+1,d2+m2+1,cmp2);
	for(int i=1;i<=m2;i++)
	{
		while(q<=m1&&deep[d1[q]]+deep[d2[i]]>=v1[d1[q]])w+=2-d[d1[q]],q++;
		f[d2[i]]+=w;
	}
	return;
}
void solve(int x)
{
	dfs1(root,0),calc(x);
	for(int i=0;i<a[x].size();i++)
	{
		if(v[a[x][i]])continue;
		minn=1e9+5,tot=siz[a[x][i]],dfs1(a[x][i],x);
		v[root]=true,solve(root);
	}
	return;
}
int main()
{
	memset(v1,0x7F,sizeof(v1));
	int x,y;
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%d%d",&x,&y),a[x].push_back(y),a[y].push_back(x),d[x]++,d[y]++;
	dfs2(1,0),dfs3(1,0);
	tot=n,minn=1e9+5,dfs1(1,0);
	v[root]=true,solve(root);
	for(int i=1;i<=n;i++)printf("%d\n",(a[i].size()==1)?1:f[i]);

  return 0;
}

【Luogu P2664】树上游戏

题目描述

有一棵树,树的每个节点有个颜色。给一个长度为 \(n\) 的颜色序列,定义 \(s(i,j)\)\(i\)\(j\) 的颜色数量。以及

\[sum_i=\sum_{j=1}^n s(i, j) \]

求 $ \sum_{i=1}^n sum_i$ 。
对于 \(100\%\) 的数据,\(1\leq n,c_i\leq 10^5\)

解题思路

看到点对贡献的我们就可以考虑点分治了。
首先注意到一个点,对于一条路径上出现过的颜色,再次出现时是不记贡献的。
我们据此可以来计算,每次遍历到一棵新子树时往下遍历,计算一下有多少条已遍历的路径是没出现该颜色的。
处理起来细节比较多,注意一下即可。
时间复杂度 \(O(nlog^2n)\)

Code

#include<bits/stdc++.h>
using namespace std;
long long n,v[100005],minn,root,siz[100005],tot,b[100005],tot1=0,b1[100005],b2[100005];
long long f[100005],qwe=0;
bool v1[100005];
vector<long long> a[100005];
void dfs1(long long x,long long y)
{
	siz[x]=1;
	long long q=0;
	for(int i=0;i<a[x].size();i++)
	{
		if(a[x][i]==y||v1[a[x][i]])continue;
		dfs1(a[x][i],x),q=max(q,siz[a[x][i]]),siz[x]+=siz[a[x][i]];
	}
	q=max(q,tot-siz[x]);
	if(q<minn)root=x,minn=q;
	return;
}
void dfs2(long long x,long long y,long long z,long long q)
{
	if(!b[v[x]])z+=tot1-b1[v[x]],q++;
	b[v[x]]++,f[x]+=z,qwe+=q;
	for(int i=0;i<a[x].size();i++)
	{
		if(a[x][i]==y||v1[a[x][i]])continue;
		dfs2(a[x][i],x,z,q);
	}
	b[v[x]]--;
	return;
}
void dfs3(long long x,long long y)
{
	if(!b2[v[x]])b1[v[x]]+=siz[x];
	b2[v[x]]++;
	for(int i=0;i<a[x].size();i++)
	{
		if(a[x][i]==y||v1[a[x][i]])continue;
		dfs3(a[x][i],x);
	}
	b2[v[x]]--;
	return;
}
void dfs4(long long x,long long y,long long z)
{
	if(!b2[v[x]])z++;
	b1[v[x]]=0,f[x]-=z,b2[v[x]]++;
	for(int i=0;i<a[x].size();i++)if(a[x][i]!=y&&(!v1[a[x][i]]))dfs4(a[x][i],x,z);
	b2[v[x]]--;
	return;
}
void dfs5(long long x,long long y)
{
	b1[v[x]]=0;
	for(int i=0;i<a[x].size();i++)if(a[x][i]!=y&&(!v1[a[x][i]]))dfs5(a[x][i],x);
	return;
}
void calc(long long x)
{
	long long q=1;
	root=x,b[v[x]]=b2[v[x]]=1,tot1=1;
	long long w=1;
	for(int i=0;i<a[x].size();i++)
	{
		if(v1[a[x][i]])continue;
		qwe=0,dfs2(a[x][i],x,w,1),dfs3(a[x][i],x);
		q+=siz[a[x][i]],tot1+=siz[a[x][i]],w+=qwe;
	}
	f[x]+=w;
	for(int i=0;i<a[x].size();i++)if(!v1[a[x][i]])dfs4(a[x][i],x,1);
	tot1=w=q=1;
	for(int i=a[x].size()-1;i>=0;i--)
	{
		if(v1[a[x][i]])continue;
		qwe=0,dfs2(a[x][i],x,w,1),dfs3(a[x][i],x);
		q+=siz[a[x][i]],tot1+=siz[a[x][i]],w+=qwe;
	}
	for(int i=0;i<a[x].size();i++)if(!v1[a[x][i]])dfs5(a[x][i],x);
	b[v[x]]=b2[v[x]]=0;
	return;
}
void solve(long long x)
{
	dfs1(x,0),calc(x);
	for(int i=0;i<a[x].size();i++)
	{
		if(v1[a[x][i]])continue;
		tot=siz[a[x][i]],minn=1e8,dfs1(a[x][i],x);
		v1[root]=true,solve(root);
	}
	return;
}
int main()
{
	long long x,y;
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&v[i]);
	for(int i=1;i<n;i++)scanf("%lld%lld",&x,&y),a[x].push_back(y),a[y].push_back(x);
	minn=1e8,tot=n,dfs1(1,0);
	v1[root]=true,solve(root);
	for(int i=1;i<=n;i++)printf("%lld\n",f[i]);

  return 0;
}

【CF875F】 Royal Questions

题目描述

在一个中世纪时期的的王国,经济危机正在肆虐。牛奶产量下降,经济指标每天都在恶化,财政部的钱在消失。为了补救这种情况,国王Charles Sunnyface决定让他的n个儿子(也就是王子)以尽可能大的嫁妆娶新娘。
为了寻找候选人,国王去了邻近的王国,过了一会儿,几个代表团和m个未婚公主来到了。接到客人后,Charles得知第i个公主的嫁妆是wi个金币。
虽然这件事发生在中世纪,但进步的思想已在社会上普遍存在。所以任何人都不能强迫公主嫁给一个她不喜欢的王子。因此,每个公主都可以选择两个王子,并且都准备好成为一个妻子。王子们就惨一点,他们在选择新娘时只能听从父亲的意愿。
国王Charles 每一位公主的嫁妆价值和喜好,就想以一种使他的所有儿子从新娘获得的嫁妆越多的方式举行婚礼。所有王子或公主不一定都被娶(嫁)。每个王子只能娶一个公主,反之亦然,每个公主只能嫁给一个王子。
现在你要帮助国王编写一个程序,求出组织他儿子的婚姻的最赚钱方式,\(1 \le n,m \le 2 \times 10^5\)

解题思路

对于这种两个选择选一个且选不能重复的问题,都可以转化为基环树。
首先,我们把每一个王子看成一个点,每一个公主看成一条边,边的权值为公主的嫁妆,两端变为选择的两个王子。
由于每条边只用选一端即可,那么,满足题目要求的边组成的图形一定为一个基环树森林。
对于最大生成基环树的求法,只需要在一个最大生成树上再加一条边即可。
\(Kruskal\) 解决,时间复杂度 \(O(nlogn)\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	int x,y;
	long long v;
}a[200005];
int n,m,fa[200005],d[200005];
long long s=0;
int search(int x)
{
	if(fa[x]!=x)fa[x]=search(fa[x]);
	return fa[x];
}
bool cmp1(datay q,datay w)
{
	return q.v>w.v;
}
int main()
{
	int x,y;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++)scanf("%d%d%lld",&a[i].x,&a[i].y,&a[i].v);
	sort(a+1,a+m+1,cmp1);
	for(int i=1;i<=m;i++)
	{
		x=search(a[i].x),y=search(a[i].y);
		if(x!=y&&(d[x]&d[y])==0)fa[y]=x,d[x]=d[x]|d[y],s+=a[i].v;
		else if(x==y&&(!d[x]))d[x]=1,s+=a[i].v;
	}
	cout<<s;







  return 0;
}

【梦熊】 普及良

题目描述

你手下有 \(n \times m\) 名士兵,现在他们站成了一个 \(n\)\(m\) 列的矩阵。为了提高军队的凝聚力,你要选出一些小队长来帮助你管理这些士兵。
选择每一名士兵当小队长的代价不同,可以用一个数值 \(a_{i,j}\) 来描述选择第 \(i\) 行第 \(j\) 列的士兵成为小队长所需要的代价,也就是花费 \(a_{i,j}\) 的代价就可以使得第 \(i\) 行第 \(j\) 列的士兵管辖第 \(i\) 行 或 第 \(j\) 列。
你想知道使得每行每列都被管辖所需要的最小总代价。请注意,你不能花费两倍代价使一名士兵同时管辖他所在的行和列,\(1 \le n\times m \le 10^5\)

解题思路

这题思路同上。
设出一个 \(n+m\) 个点的图,每点代表每列或每行,第 \(i\) 行第 \(j\) 列的点即与点 \(i\) 与点 \(n+j\) 相连,边权为 \(a_{i,j}\)
即为求最小生成基环树代价,用上题的方法即可。
时间复杂度 \(O(nmlog(nm))\)
也可以暴力,\(n,m\) 中必有一个小于 \(\sqrt{10^5}\) ,从小的那个出发即可。
时间复杂度 \(O(nm\sqrt{n}))\)

数据结构

【Luogu P3674】 小清新人渣的本愿

题目描述

给你一个序列 \(a\),长度为 \(n\),有 \(m\) 次操作,每次询问一个区间是否可以选出两个数它们的差为 \(x\),或者询问一个区间是否可以选出两个数它们的和为 \(x\),或者询问一个区间是否可以选出两个数它们的乘积为 \(x\) ,这三个操作分别为操作 \(1,2,3\)
对于 \(100\%\) 的数据,\(n,m,c \leq 10^5\)

解题思路

对于统计和与差的操作,本质上一种,这里将他们一起处理。
看到求区间贡献,以及 \(1e5\) 的数据范围,我们可以考虑到莫队。
对于统计和与差的操作,我们可以用 \(bitset\) 进行维护,每次移动时维护区间内存在的数,查询时用位运算进行处理。
这些操作的时间复杂度为 \(O(m(\sqrt{m}+\frac{c}{64}))\)
对于统计乘积的操作,我们直接枚举小于 \(\sqrt{x}\) 的数即可,检查 \(x\)\(\frac{c}{x}\) 存不存在。
这些操作时间复杂度 \(O(m(\sqrt{m}+\sqrt{c}))\) ,总时间复杂度 \(O(m(\sqrt{m}+\frac{c}{64}))\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	int op,l,r,x,v,p;
}a[200005];
int n,m,v[200005],d[200005],block,l=1,r=0;
bitset<400005> s1,s2;
bool cmp(datay q,datay w)
{
	if((q.l-1)/block!=(w.l-1)/block)return q.l<w.l;
	return q.r<w.r;
}
bool cmp1(datay q,datay w)
{
	return q.p<w.p;
}
void add(int x)
{
	d[x]++;
	if(d[x]==1)s1[x]=1,s2[400000-x]=1;
	return;
}
void del(int x)
{
	d[x]--;
	if(d[x]==0)s1[x]=0,s2[400000-x]=0;
	return;
}
int main()
{
	int y;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=m;i++)scanf("%d%d%d%d",&a[i].op,&a[i].l,&a[i].r,&a[i].x),a[i].p=i;
	block=sqrt(n),sort(a+1,a+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		a[i].v=0;
		while(l<a[i].l)del(v[l]),l++;
		while(l>a[i].l)l--,add(v[l]);
		while(r<a[i].r)r++,add(v[r]);
		while(r>a[i].r)del(v[r]),r--;
		if(a[i].op==2&&((s2>>(400000-a[i].x))&s1).any())a[i].v=1; 
		else if(a[i].op==1&&((s1>>a[i].x)&s1).any())a[i].v=1;
		else if(a[i].op==3)
		{
			y=sqrt(a[i].x);
			for(int j=1;j<=y;j++)
			{
				if(a[i].x%j!=0)continue;
				if(d[j]&&d[a[i].x/j])
				{
					a[i].v=1;
					break;
				}
			}
		}
	}
	sort(a+1,a+m+1,cmp1);
	for(int i=1;i<=m;i++)
	{
		if(a[i].v)printf("hana\n");
		else printf("bi\n");	
	}
  return 0;
}

【Luogu P4689】 这是我自己的发明

题目描述

您正在打 galgame,然后突然家长进来了,于是您假装在写数据结构题:
给一个树,\(n\) 个点,有点权,初始根是 1。
\(m\) 个操作,种类如下:
1 x 将树根换为 \(x\)
2 x y 给出两个点 \(x,y\),从 \(x\) 的子树中选每一个点,\(y\) 的子树中选每一个点,求点权相等的情况数。
\(1 \le n \le 10^5, 1 \le m \le 5 \times 10^5\)

解题思路

求出 \(dfs\) 序,先分类讨论一下根的情况,再推一下式子,省略掉一些可以预处理的东西,整个问题就变成了求两个区间选两个点点权相等的情况数。
设两个区间分别为 \([l_1,r_1],[l_2,r_2]\) ,那么答案即为 \(\sum_{i=1}^n f(l_1,r_1,i) \times f(l_2,r_2,i)\)\(f_{l,r,i}\) 即为区间 \([l,r]\) 内颜色为 \(i\) 的点数。
\(f(l,r,i)\) 转化为 \(f(1,r,i)-f(1,l-1,i)\) ,拆开式子,就是求四个形如 \(f_{1,x,i} \times f_{1,y,i}\) 形式的权值。
用莫队解决即可。
时间复杂度 \(O(m\sqrt{m})\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	int p,l,r;
	long long v;
}b[2000005];
int m,v1[100005],dfn[100005],re[100005],v[100005],num,out[100005],root,num1,num2,block,v2[500005],f1[100005][21],deep[100005];
long long s=0,d1[100005],d2[100005],n,m1,d[500005],t[500005];
vector<int> a[100005];
set<int> l1;
map<int,int> p1;
bool cmp1(datay q,datay w)
{
	if((q.l-1)/block!=(w.l-1)/block)return q.l<w.l;
	return q.r<w.r;
}
bool cmp2(datay q,datay w)
{
	return q.p<w.p;
}
void add1(int x)
{
	s+=d2[x],d1[x]++;
	return;
}
void del1(int x)
{
	s-=d2[x],d1[x]--;
	return;
}
void add2(int x)
{
	s+=d1[x],d2[x]++;
	return;
}
void del2(int x)
{
	s-=d1[x],d2[x]--;
	return;
}
void dfs1(int x,int y)
{
	dfn[x]=++num,re[num]=x,v[num]=v1[x],d[v[num]]++,f1[x][0]=y,deep[x]=deep[y]+1;
	for(int i=0;i<a[x].size();i++)if(a[x][i]!=y)dfs1(a[x][i],x);
	out[x]=num;
	return;
}
int search(int x,int y)
{
	for(int i=20;i>=0;i--)x=(deep[f1[x][i]]>deep[y])?f1[x][i]:x;
	return x;
} 
void add(int p,int l,int r)
{
	b[++num1].p=p,b[num1].l=l,b[num1].r=r;
	return;
}
void add1(int x,int y)
{
	int p1,p2,q1,q2,w1,w2;
	p1=p2=q1=q2=w1=w2=0;
	if(x==root)q1=1,w1=n;
	else if(dfn[x]<=dfn[root]&&dfn[root]<=out[x])p1=1,q1=dfn[search(root,x)],w1=out[search(root,x)];
	else q1=dfn[x],w1=out[x];
	if(y==root)q2=1,w2=n;
	else if(dfn[y]<=dfn[root]&&dfn[root]<=out[y])p2=1,q2=dfn[search(root,y)],w2=out[search(root,y)];
	else q2=dfn[y],w2=out[y];
	q1--,q2--,v2[++num2]=p1*2+p2,add(4*num2-3,q1,q2),add(4*num2-2,q1,w2),add(4*num2-1,w1,q2),add(4*num2,w1,w2);
	return;
}
int main()
{
	int x=0,y,z,op,l=0,r=0;
	scanf("%lld%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v1[i]),l1.insert(v1[i]);
	x=0;
	set<int>::iterator q=l1.begin();
	for(;q!=l1.end();q++)p1[*q]=++x;
	for(int i=1;i<=n;i++)v1[i]=p1[v1[i]];	
	for(int i=1;i<n;i++)scanf("%d%d",&x,&y),a[x].push_back(y),a[y].push_back(x);
	dfs1(1,0),root=1;
	for(int i=1;i<=20;i++)
	{
		for(int j=1;j<=n;j++)f1[j][i]=f1[f1[j][i-1]][i-1];
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&op);
		if(op==1)scanf("%d",&x),root=x;
		else scanf("%d%d",&x,&y),add1(x,y);
	}
	for(int i=1;i<=n;i++)m1+=d[i]*d[i],t[i]=t[i-1]+d[v[i]];
	block=double(n)/sqrt(m),sort(b+1,b+num1+1,cmp1);
	for(int i=1;i<=num1;i++)
	{
		while(l<b[i].l)l++,add1(v[l]);
		while(l>b[i].l)del1(v[l]),l--;
		while(r<b[i].r)r++,add2(v[r]);
		while(r>b[i].r)del2(v[r]),r--;
		b[i].v=s;
	}
	sort(b+1,b+num1+1,cmp2);
	for(int i=1;i<=num1;i+=4)
	{
		if(v2[i/4+1]==0)printf("%lld\n",b[i+3].v+b[i].v-b[i+2].v-b[i+1].v);
		else if(v2[i/4+1]==1)printf("%lld\n",t[b[i+2].l]-t[b[i].l]-b[i+3].v-b[i].v+b[i+2].v+b[i+1].v);
		else if(v2[i/4+1]==2)printf("%lld\n",t[b[i+3].r]-t[b[i+2].r]-b[i+3].v-b[i].v+b[i+2].v+b[i+1].v);
		else printf("%lld\n",m1-t[b[i+2].l]-t[b[i+3].r]+t[b[i].l]+t[b[i+2].r]+b[i+3].v+b[i].v-b[i+2].v-b[i+1].v);
	}

  return 0;
}

【Luogu P4121】 糖果公园

题目描述

Candyland 有一座糖果公园,公园里不仅有美丽的风景、好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园游玩。
糖果公园的结构十分奇特,它由 \(n\) 个游览点构成,每个游览点都有一个糖果发放处,我们可以依次将游览点编号为 \(1\)\(n\)。有 \(n - 1\) 条双向道路连接着这些游览点,并且整个糖果公园都是连通的,即从任何一个游览点出发都可以通过这些道路到达公园里的所有其它游览点。
糖果公园所发放的糖果种类非常丰富,总共有 \(m\) 种,它们的编号依次为 \(1\)\(m\)。每一个糖果发放处都只发放某种特定的糖果,我们用 \(C_i\) 来表示 \(i\) 号游览点的糖果。
来到公园里游玩的游客都不喜欢走回头路,他们总是从某个特定的游览点出发前往另一个特定的游览点,并游览途中的景点,这条路线一定是唯一的。他们经过每个游览点,都可以品尝到一颗对应种类的糖果。
大家对不同类型糖果的喜爱程度都不尽相同。 根据游客们的反馈打分,我们得到了糖果的美味指数, 第 \(i\) 种糖果的美味指数为 \(V_i\)。另外,如果一位游客反复地品尝同一种类的糖果,他肯定会觉得有一些腻。根据量化统计,我们得到了游客第 \(i\) 次品尝某类糖果的新奇指数 \(W_i\)。如果一位游客第 \(i\) 次品尝第 \(j\) 种糖果,那么他的愉悦指数 \(H\) 将会增加对应的美味指数与新奇指数的乘积,即 \(V_j \times W_i\)。这位游客游览公园的愉悦指数最终将是这些乘积的和。
当然,公园中每个糖果发放点所发放的糖果种类不一定是一成不变的。有时,一些糖果点所发放的糖果种类可能会更改(也只会是 \(m\) 种中的一种),这样的目的是能够让游客们总是感受到惊喜。
糖果公园的工作人员小 A 接到了一个任务,那就是根据公园最近的数据统计出每位游客游玩公园的愉悦指数。但数学不好的小 A 一看到密密麻麻的数字就觉得头晕,作为小 A 最好的朋友,你决定帮他一把。
\(1 \le n,m,q \le 10^5\)

解题思路

求树上点对贡献再加上这个数据范围我们可以考虑树上莫队。
树上莫队的核心思路就是利用欧拉序与异或的性质来维护一段路径的贡献,将两点的欧拉序看成一段区间,不在路径上的点用异或处理掉。
这题有修改操作,还要加上带修莫队,时间复杂度 \(O(m^{\frac{5}{3}})\)
细节有点多,需要注意。

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	int op,p,x,y,z,p1;
	long long v;
}t[200005],b1[200005];
int n,m,k,dfn[200005],out[200005],b[200005],v[200005],num,num1,f1[200005][21],block,deep[200005],dfn1[200005],out1[200005],k1,k2;
bool v3[200005];
long long v1[200005],v2[200005],s,d[200005];
vector<int> a[200005];
bool cmp1(datay q,datay w)
{
	if((q.p-1)/block!=(w.p-1)/block)return q.p<w.p;
	if((q.x-1)/block!=(w.x-1)/block)return q.x<w.x;
	return q.y<w.y;
}
bool cmp2(datay q,datay w)
{
	return q.p1<w.p1;
}
void dfs1(int x,int y)
{
	dfn[x]=++num,b[++num1]=x,f1[x][0]=y,dfn1[x]=num1,deep[x]=deep[y]+1;
	for(int i=0;i<a[x].size();i++)if(a[x][i]!=y)dfs1(a[x][i],x);
	out[x]=num,b[++num1]=x,out1[x]=num1;
	return;
}
int LCA(int x,int y)
{
	if(deep[x]<deep[y])swap(x,y);
	for(int i=20;i>=0;i--)x=(deep[f1[x][i]]>=deep[y])?f1[x][i]:x;
	if(x==y)return x;
	for(int i=20;i>=0;i--)x=(f1[x][i]!=f1[y][i])?f1[x][i]:x,y=(deep[x]!=deep[y])?f1[y][i]:y;
	return f1[x][0];
}
void add1(int x)
{
	d[x]++,s+=v2[d[x]]*v1[x];
	return;
}
void del1(int x)
{
	s-=v2[d[x]]*v1[x],d[x]--;
	return;
}
void add(int x)
{
	if(v3[b[x]])del1(v[b[x]]);
	else add1(v[b[x]]);
	v3[b[x]]^=1;
	return;
}
int main()
{
	int x,y,op,p,l,r;
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++)scanf("%lld",&v1[i]);
	for(int i=1;i<=n;i++)scanf("%lld",&v2[i]);
	for(int i=1;i<n;i++)scanf("%d%d",&x,&y),a[x].push_back(y),a[y].push_back(x);
	dfs1(1,0);
	for(int i=1;i<=20;i++)
	{
		for(int j=1;j<=n;j++)f1[j][i]=f1[f1[j][i-1]][i-1];
	}
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=k;i++)
	{	
		scanf("%d",&op);
		if(op==0)k2++,scanf("%d%d",&b1[k2].x,&b1[k2].v),b1[k2].p=i,b1[k2].z=v[b1[k2].x],v[b1[k2].x]=b1[k2].v;
		else 
		{
			k1++;
			scanf("%d%d",&t[k1].x,&t[k1].y);
			if(dfn[t[k1].x]>dfn[t[k1].y])swap(t[k1].x,t[k1].y);
			if(LCA(t[k1].x,t[k1].y)!=t[k1].x)t[k1].z=LCA(t[k1].x,t[k1].y),t[k1].x=out1[t[k1].x],t[k1].y=dfn1[t[k1].y];
			else swap(t[k1].x,t[k1].y),t[k1].x=out1[t[k1].x],t[k1].y=out1[t[k1].y];
			t[k1].p=k2;
			t[k1].p1=i;
		}
	}
	for(int i=k2;i>=1;i--)v[b1[i].x]=b1[i].z;
	block=pow(n,double(2)/3),sort(t+1,t+k1+1,cmp1),p=r=0,l=1;
	if(block<=1)block=1;
	for(int i=1;i<=k1;i++)
	{
		while(l<t[i].x)add(l),l++;
		while(l>t[i].x)l--,add(l);
		while(r>t[i].y)add(r),r--;
		while(r<t[i].y)r++,add(r);
		while(p<t[i].p)
		{
			p++,v[b1[p].x]=b1[p].v;
			if(v3[b1[p].x])del1(b1[p].z),add1(b1[p].v);
		}
		while(p>t[i].p)
		{
			if(v3[b1[p].x])add1(b1[p].z),del1(b1[p].v);
			v[b1[p].x]=b1[p].z,p--;
		}
		if(v3[t[i].z])del1(v[t[i].z]),t[i].v=s,add1(v[t[i].z]);
		else add1(v[t[i].z]),t[i].v=s,del1(v[t[i].z]);
	}
	sort(t+1,t+k1+1,cmp2);
	for(int i=1;i<=k1;i++)cout<<t[i].v<<'\n';
  return 0;
}

【Luogu P3964】 松鼠聚会

题目描述

给出 \(n\) \((1 \le n \le 10^5)\) 个点,找到一个点,使得其他点到该点的切雪比夫距离之和最小。

解题思路

切雪比夫距离就是两点之间横纵坐标之差的最大值。
观察一下切雪比夫距离的形式,我们可以发现切雪比夫距离与曼哈顿距离之间可以相互转换,转 \(45\) 度即可,在代码中具体实现即为将点 \((x,y)\) 转化为点 \((\frac{x+y}{2},\frac{x-y}{2})\)
将这题的点坐标转化后,就变得很简单了,曼哈顿距离分类讨论即可。
时间复杂度 \(O(nlogn)\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	long long x,y,p;
}a[100005];
int n;
long long s=1e15,d[100005],t[100005];
bool cmp(datay q,datay w)
{
	return q.x<w.x;
}
bool cmp1(datay q,datay w)
{
	return q.y<w.y;
}
int main()
{
	long long x,y;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%lld",&x,&y);
		a[i].x=(x+y),a[i].y=(x-y);
	}
	sort(a+1,a+n+1,cmp1);
	for(int i=1;i<=n;i++)a[i].p=i,d[i]=a[i].y+d[i-1];
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)t[i]=t[i-1]+a[i].x;
	for(int i=1;i<=n;i++)
	{
		x=(t[n]-t[i]-(n-i)*a[i].x+a[i].x*(i-1)-t[i-1])+(d[n]-d[a[i].p]-(n-a[i].p)*a[i].y+a[i].y*(a[i].p-1)-d[a[i].p-1]);
		s=min(s,x);
	}
	printf("%lld",s/2);
	
  return 0;
}

【Luogu P4121】 双面棋盘

题目描述

给出一个 \(n\times n\) 的棋盘(\(1 \le n \le 200\)),每个格子有黑白两种颜色,有 \(m\) 次操作 (\(1 \le m \le 10^4\)) ,每次将一个格子的颜色翻转,求每次操作后黑色格子组成的连通块与白色格子组成的连通块的数目。

解题思路

首先,将原先就为黑色的格子也看成若干次操作,并将他们组合起来,就变成了若干个操作,使得一个格子一段时间内为黑色。
这就是线段树分治的标准形式。
结合线段树的结构将每个操作加到线段树对应的若干个区间上,每次增加时就直接用并查集维护,撤销时用可撤销并查集进行维护,用队列储存并进行撤销。
时间复杂度为 \(O(n^2+mnlogm)\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	int x,y;
}d[100005];
int n,m,b[205][205],st[100005],s,fa[100005],deep[100005],v[205][205];
int v1[100005],v2[100005];
vector<int> t[200005];
stack<datay> l1,l2;
int point(int x,int y)
{
	return (x-1)*n+y;
}
void modify(int x,int l,int r,int ql,int qr,int v)
{
	if(ql<=l&&r<=qr)
	{
		t[x].push_back(v);
		return;
	}
	int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
	if(ql<=mid)modify(lc,l,mid,ql,qr,v);
	if(qr>mid)modify(rc,mid+1,r,ql,qr,v); 
	return;
}
int search(int x)
{
	while(x!=fa[x])x=fa[x];
	return x;
}
void merge(int x,int y)
{
	datay q;
	x=search(x),y=search(y);
	if(deep[x]<deep[y])swap(x,y);
	q.x=x,q.y=deep[x],l2.push(q);
	if(x!=y)deep[x]=max(deep[x],deep[y]+1),s--;
	q.x=y,q.y=fa[y],l1.push(q);
	fa[y]=x;
	return;
}
void check(int x)
{
	int q=(x-1)/n+1,w=(x-1)%n+1;s++,v[q][w]=1;
	if(q>1&&v[q-1][w])merge(x,x-n);
	if(w>1&&v[q][w-1])merge(x,x-1);
	if(w<n&&v[q][w+1])merge(x,x+1);
	if(q<n&&v[q+1][w])merge(x,x+n);
	return;
}
void re1()
{
	fa[l1.top().x]=l1.top().y,l1.pop();
	deep[l2.top().x]=l2.top().y,l2.pop();
	return;
}
void re(int x)
{
	int q,w;
	for(int i=t[x].size()-1;i>=0;i--)
	{
		q=(t[x][i]-1)/n+1,w=(t[x][i]-1)%n+1;
		if(q>1&&v[q-1][w])re1();
		if(w>1&&v[q][w-1])re1();
		if(w<n&&v[q][w+1])re1();
		if(q<n&&v[q+1][w])re1();
		v[q][w]=0;
	}
	return;
}
void query(int x,int l,int r)
{
	int q=s;
	for(int i=0;i<t[x].size();i++)check(t[x][i]);
	if(l==r)
	{
		v1[l]=s,re(x),s=q;
		return;
	}
	int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
	query(lc,l,mid),query(rc,mid+1,r);
	re(x),s=q;
	return;
}
void solve()
{
	memset(v,0,sizeof(v));
	s=0;int x;
	for(int i=1;i<=n*n;i++)fa[i]=i,deep[i]=1;
	for(int i=1;i<=4*m;i++)t[i].clear();
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(b[i][j])st[point(i,j)]=1;
			else st[point(i,j)]=0;
		}
	}	
	for(int i=1;i<=m;i++)
	{
		x=point(d[i].x,d[i].y);
		if(st[x])
		{
			if(i==1){st[x]=0;continue;}
			modify(1,1,m,st[x],i-1,x),st[x]=0;
		}
		else st[x]=i;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			x=point(i,j);
			if(st[x])modify(1,1,m,st[x],m,x);
		}
	}
	query(1,1,m);
	return;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)scanf("%d",&b[i][j]);
	} 
	scanf("%d",&m);
	for(int i=1;i<=m;i++)scanf("%d%d",&d[i].x,&d[i].y);
	solve();
	for(int i=1;i<=m;i++)v2[i]=v1[i],v1[i]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)b[i][j]^=1;
	}
	solve();
	for(int i=1;i<=m;i++)printf("%d %d\n",v2[i],v1[i]);







  return 0;
}

【CF603E】 Pastoral Oddities

题目描述

给定一张 \(n\) 个点的无向图,初始没有边。
依次加入 \(m\) 条带权的边,每次加入后询问是否存在一个边集,满足每个点的度数均为奇数。
若存在,则还需要最小化边集中的最大边权。
\(n \le 10^5\)\(m \le 3 \times 10^5\)

解题思路

首先先分析如何才能达到题目要求。
很明显,若 \(n\) 为奇数,是不可能使得每个点的度数均为奇数的。
\(n\) 为偶数,那只需保证图联通,即可出现一个边集使得每个点的度数均为奇数。
那问题就转化为了使图联通所需的最大边权。
在这题里面是不可能做到动态加边的,但是我们可以发现一个性质:答案是不断递减的,说明每条边产生贡献是一段区间。
那我们就可以用线段树区间来解决问题了。
这道题里面不知道每条边能影响到哪儿,这种情况下,我们只需要倒着做即可。
结合答案不断递减的性质,我们可以在每次遍历到线段树叶子的时候去查找那些没产生过贡献的边,逐条尝试是否能加入边集,若能就加入,由于影响区间的左端点已知,可以直接加到线段树上。
时间复杂度 \(O(mlogmlogn)\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	int x,y,v,p;
}b[300005];
int n,m,d[100005],fa[100005],deep[100005],ans[300005],s,ll=1;
stack<datay> l1,l2,l3;
vector<datay> t[1200005];
bool cmp1(datay q,datay w)
{
	return q.v<w.v;
}
int search(int x)
{
	while(x!=fa[x])x=fa[x];
	return x;
}
void re()
{
	fa[l1.top().x]=l1.top().y,deep[l2.top().x]=l2.top().y,d[l3.top().x]=l3.top().y;
	l1.pop(),l2.pop(),l3.pop();
	return;
}
void merge(int x,int y)
{
	x=search(x),y=search(y);
	if(deep[x]<deep[y])swap(x,y);
	datay q;
	q.x=x,q.y=deep[x],l2.push(q);
	q.x=x,q.y=d[x],l3.push(q);
	if(x!=y)deep[x]=max(deep[x],deep[y]+1),s+=(d[x]^d[y])-d[x]-d[y],d[x]=d[x]^d[y];
	q.x=y,q.y=fa[y],l1.push(q);
	fa[y]=x;
	return;
}
void cover(int x,int l,int r,int ql,int qr,datay v)
{
	if(ql>qr)return;
	if(ql<=l&&r<=qr)
	{
		t[x].push_back(v);
		return;
	}
	int mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1;
	if(ql<=mid)cover(lc,l,mid,ql,qr,v);
	if(qr>mid)cover(rc,mid+1,r,ql,qr,v); 
	return;
}
void solve(int x,int l,int r)
{
	int s1=s,len=l1.size();
	for(int i=0;i<t[x].size();i++)merge(t[x][i].x,t[x][i].y);
	if(l==r)
	{
		while(ll<=m)
		{
			if(s==0)break;
			if(b[ll].p<=l)merge(b[ll].x,b[ll].y),cover(1,1,m,b[ll].p,l-1,b[ll]);
			ll++; 
		}
		if(s==0)ans[l]=b[ll-1].v;
		else ans[l]=-1;
		s=s1;
		while(l1.size()>len)re();	
		return;
	}
	int mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1;
	solve(rc,mid+1,r),solve(lc,l,mid),s=s1;
	while(l1.size()>len)re();	
	return;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)fa[i]=i,d[i]=1,deep[i]=1,s++;
	for(int i=1;i<=m;i++)scanf("%d%d%d",&b[i].x,&b[i].y,&b[i].v),b[i].p=i;
	sort(b+1,b+m+1,cmp1);
	solve(1,1,m);
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);

  return 0;
}

【ARC145D】 Non Arithmetic Progression Set

题目描述

构造一个满足以下条件的整数集合 \(S\)。可以证明在本题的约束下一定存在至少一个。

  • \(S\) 里有恰好 \(N\) 个元素。
  • \(\forall x\in S\)\(-10^7\le x\le 10^7\),且 \(x\) 两两不同。
  • \(\sum\limits_{x\in S} x=M\)
  • \(\forall x,y,z\in S\),若 \(x<y<z\),则 \(y-x\neq z-y\)

\(1\le N\le 10^4\)\(|M|\le N\times 10^6\)

解题思路

\(y-x \neq z-y\) 转化为 \(x+z \neq 2 \times y\)
联想到进制,有 $2 \times $ 用二进制不好做,用三进制即可构造。
我们设计的三进制数每一位上只填 \(0\)\(1\) ,这样的话 \(\times 2\) 后也不会进位,容易构造。
这样的话,两个以此法构造的不一样的三进制数相加必有一位为 \(1\) ,既满足了条件。
其他的条件乱搞即可。

Code

#include<bits/stdc++.h>
using namespace std;
long long n,m,a[100005],p3[31],p; 
int check(int x)
{
	int q=0;
	for(int i=0;i<=20;i++)
	{
		if(x&(1<<i))q+=p3[i+1]; 
	}
	return q;
}
int main()
{
	p3[0]=1;
	for(int i=1;i<=30;i++)p3[i]=p3[i-1]*3;
	scanf("%lld%lld",&n,&m),m+=n*(1e7);
	for(int i=0;i<n;i++)a[i+1]=check(i),p+=a[i+1];
	p=(m-p)/n;
	for(int i=1;i<=n;i++)a[i]+=p,m-=a[i];
	for(int i=n;i>=n-m+1;i--)a[i]++;
	for(int i=1;i<=n;i++)cout<<(a[i])-(10000000)<<' '; 

  return 0;
}

【CF710F】String Set Queries

题目描述

维护一个字符串集合,支持三种操作:

  1. 加字符串
  2. 删字符串
  3. 查询集合中的所有字符串在给出的模板串中出现的次数

操作数 \(m \leq 3 \times 10^5\),输入字符串总长度 \(\sum |s_i| \leq 3\times 10^5\)
本题强制在线

解题思路

对于这种强制在线的问题,我们可以利用二进制分组花费 \(logn\) 的代价将其转化为离线问题。
原理很简单,二进制分组,将前面的拆成一块一块的就行了。
由于查询出现次数,直接套二进制分组即可。
时间复杂度 \(O(\sum len logm)\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	int op;
	string x;
}a[300005];
int n,pre[300005],num,to[6000005][26],fail[6000005],root[300005];
long long v[6000005];
int lowbit(int x){return x&(-x);}
vector<int> t[6000005];
queue<int> l;
void add(int x,int y)
{
	fail[y]=x,t[x].push_back(y);
	return;
}
void build_tree(int p,string x,int v1)
{
	for(int i=0;i<x.size();i++)
	{
		if(to[p][x[i]-'a'])p=to[p][x[i]-'a'];
		else to[p][x[i]-'a']=++num,p=num;
	}
	v[p]+=v1;
	return;
}
void build_AC(int p)
{
	int x;
	while(l.size())l.pop();
	for(int i=0;i<26;i++)
	{
		if(to[p][i])add(p,to[p][i]),l.push(to[p][i]);
		else to[p][i]=p;
	}
	while(l.size()!=0)
	{
		x=l.front(),l.pop();
		for(int i=0;i<26;i++)
		{
			if(to[x][i])add(to[fail[x]][i],to[x][i]),l.push(to[x][i]);
			else to[x][i]=to[fail[x]][i];
		}
	}
	return;
}
void dfs(int x,long long v1)
{
	v[x]+=v1;
	for(int i=0;i<t[x].size();i++)dfs(t[x][i],v[x]);
	return;
}
long long query(int p,string x)
{
	long long s=0;
	for(int i=0;i<x.size();i++)p=to[p][x[i]-'a'],s+=v[p];
	return s;
}
int main()
{
	int r;long long s=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)pre[i]=i^lowbit(i);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i].op),cin>>a[i].x;
		root[i]=++num;
		for(int j=pre[i]+1;j<=i;j++)if(a[j].op!=3)build_tree(root[i],a[j].x,((a[j].op==1)?1:(-1)));
		build_AC(root[i]),dfs(root[i],0);
		if(a[i].op==3)
		{
			r=i-1,s=0;
			while(r!=0)s+=query(root[r],a[i].x),r=pre[r];
			printf("%lld\n",s),fflush(stdout);
		}
	}
  return 0;
}

【Luogu P4094】 字符串

题目描述

给出一个长度为 \(n\) 的字符串 \(S\) 以及 \(m\) 个询问 \((1 \le n,m \le 10^5)\),每次询问给出四个参数 \(a,b,c,d\) ,询问 \(S[a...b]\) 的所有子串与 \(S[c...d]\) 的最长公共前缀的长度的最大值为多少。

解题思路

看到与字符串有关同时还询问最长公共前缀,我们可以想到后缀数组。
由于不要求 \(S[c...d]\) 的所有子串,只是 \(S[c...d]\) ,我们可以想到 \(d\) 只是把最长公共前缀的长度给限制了,只需考虑以第 \(c\) 为开头的后缀即可。
对原字符串求后缀数组,按 \(rk\) 排序后,我们发现问题与字符串便无关了。
考虑二分答案,设二分的答案为 \(k\) ,那么我们只能计算首位范围在 \([a,b-k+1]\) 之间的后缀即可。
结合 \(ST\) 表快速求两个后缀的最长公共前缀,我们再做一次二分,找到与 \(S[c...d]\) 最长公共前缀长度大于等于 \(k\) 的所有后缀的所在区间,然后检查是否有在 \([a,b-k+1]\) 内的。
这一步我们可以用可持久化线段树来检验,有一个就说明这个答案满足要求。
时间复杂度 \(O(nlog^2n)\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	int v,lc,rc;
}f[3000005];
int n,m,t1[100005],sa[100005],rk[100005],x[100005],y[100005],num,m1,height[100005],H[100005],z,p1[21],lo[100005],f1[100005][21],root[100005];
string a;
int ask(int x,int y)
{
	if(x==y)return 100001;
	if(x>y)swap(x,y);
	x++,z=lo[y-x+1];
	return min(f1[x][z],f1[y-p1[z]+1][z]);
}
int build(int l,int r)
{
	int p=++num;
	if(l==r)return p;
	int mid=(l+r)>>1;
	f[p].lc=build(l,mid),f[p].rc=build(mid+1,r);
	return p;
}
int modify(int x,int l,int r,int k,int v)
{
	int p=++num;f[p]=f[x];
	if(l==r)
	{
		f[p].v+=v;
		return p;
	}
	int mid=(l+r)>>1;
	if(k<=mid)f[p].lc=modify(f[x].lc,l,mid,k,v);
	else f[p].rc=modify(f[x].rc,mid+1,r,k,v);
	f[p].v=f[f[p].lc].v+f[f[p].rc].v;
	return p;
}
int query(int x1,int x2,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr)return f[x2].v-f[x1].v;
	int mid=(l+r)>>1,h=0;
	if(ql<=mid)h+=query(f[x1].lc,f[x2].lc,l,mid,ql,qr);
	if(qr>mid)h+=query(f[x1].rc,f[x2].rc,mid+1,r,ql,qr);
	return h;
}
bool check(int l,int r,int x,int y)
{
	int le=1,ri=rk[x],mid,h1=rk[x],h2=rk[x];
	while(le<=ri)
	{
		mid=(le+ri)>>1;
		if(ask(mid,rk[x])>=y)ri=mid-1,h1=min(h1,mid);
		else le=mid+1;
	}
	le=rk[x],ri=n;
	while(le<=ri)
	{
		mid=(le+ri)>>1;
		if(ask(rk[x],mid)>=y)le=mid+1,h2=max(h2,mid);
		else ri=mid-1; 
	}
	if(query(root[h1-1],root[h2],1,n,l,r-y+1)==0)return false;
	return true;
}
int solve(int l,int r,int x)
{
	int le=1,ri=r-l+1,mid,s=0;
	while(le<=ri)
	{
		mid=(le+ri)>>1;
		if(check(l,r,x,mid))s=max(s,mid),le=mid+1;
		else ri=mid-1;
	}
	return s;
}
int main()
{
	int q1,w1,e1,r1;
	scanf("%d%d",&n,&m);
	cin>>a,a=' '+a,m1=150,p1[0]=1,lo[0]=-1;
	for(int i=1;i<=20;i++)p1[i]=p1[i-1]<<1;
	for(int i=1;i<=n;i++)lo[i]=lo[i>>1]+1;
	for(int i=1;i<=n;i++)x[i]=a[i],t1[x[i]]++;
	for(int i=1;i<=m1;i++)t1[i]+=t1[i-1];
	for(int i=1;i<=n;i++)sa[t1[x[i]]--]=i;
	for(int p=1;p<n;p<<=1)
	{
		num=0;
		for(int j=n-p+1;j<=n;j++)y[++num]=j;
		for(int j=1;j<=n;j++)if(sa[j]>p)y[++num]=sa[j]-p;
		memset(t1,0,sizeof(t1));
		for(int j=1;j<=n;j++)t1[x[j]]++;
		for(int j=1;j<=m1;j++)t1[j]+=t1[j-1];
		for(int j=n;j>=1;j--)sa[t1[x[y[j]]]--]=y[j];
		swap(x,y),m1=1,x[sa[1]]=1;
		for(int j=2;j<=n;j++)x[sa[j]]=(y[sa[j]]==y[sa[j-1]]&&y[sa[j]+p]==y[sa[j-1]+p])?m1:(++m1); 
	}
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	for(int i=1;i<=n;i++)
	{
		if(rk[i]==1)continue;
		H[i]=(H[i-1]>=1)?(H[i-1]-1):0;
		while(a[i+H[i]]==a[sa[rk[i]-1]+H[i]])H[i]++;
		f1[rk[i]][0]=height[rk[i]]=H[i];
	}
	for(int i=1;p1[i]<=n;i++)
	{
		for(int j=1;j+p1[i]-1<=n;j++)f1[j][i]=min(f1[j][i-1],f1[j+p1[i-1]][i-1]);
	}
	num=0,root[0]=build(1,n);
	for(int i=1;i<=n;i++)root[i]=modify(root[i-1],1,n,sa[i],1);
	for(int i=1;i<=m;i++)scanf("%d%d%d%d",&q1,&w1,&e1,&r1),printf("%d\n",min(solve(q1,w1,e1),r1-e1+1));
	

  return 0;
}

【Luogu P4556】 雨天的尾巴 /【模板】线段树合并

题目描述

首先村落里的一共有 \(n\) 座房屋,并形成一个树状结构。然后救济粮分 \(m\) 次发放,每次选择两个房屋 \((x, y)\),然后对于 \(x\)\(y\) 的路径上(含 \(x\)\(y\))每座房子里发放一袋 \(z\) 类型的救济粮。
当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
\(1 \le n,m,z \le 10^5\)

解题思路

看到路径操作,我们可以想到树上差分。
将放救济粮的操作用树上差分处理,那么问题转化,我们可以用线段树合并来做。
每次将自己儿子的线段树给合并了,并对自己的线段树完成发或收救济粮的操作,然后上传即可。
线段树合并的时间复杂度为 \(O(nlogn)\)

Code

#include<bits/stdc++.h>
using namespace std;
struct datay
{
	int v,lc,rc,maxx;
}f[5000005];
int n,m,root[100005],num,f1[100005][21],deep[100005],ans[100005];
vector<int> a[100005];
int LCA(int x,int y)
{
	if(deep[x]<deep[y])swap(x,y);
	for(int i=20;i>=0;i--)x=(deep[f1[x][i]]>=deep[y])?f1[x][i]:x;
	if(x==y)return x;
	for(int i=20;i>=0;i--)x=(f1[x][i]!=f1[y][i])?f1[x][i]:x,y=(deep[x]!=deep[y])?f1[y][i]:y;
	return f1[x][0]; 
}
void modify(int x,int l,int r,int k,int v)
{
	if(l==r)
	{
		f[x].v+=v,f[x].maxx=l;
		return;
	}
	int mid=(l+r)>>1;
	if(k<=mid)f[x].lc=(f[x].lc)?f[x].lc:(++num),modify(f[x].lc,l,mid,k,v);
	else f[x].rc=(f[x].rc)?f[x].rc:(++num),modify(f[x].rc,mid+1,r,k,v);
	if(f[f[x].lc].v>f[f[x].rc].v)f[x].v=f[f[x].lc].v,f[x].maxx=f[f[x].lc].maxx;
	else if(f[f[x].lc].v!=f[f[x].rc].v)f[x].v=f[f[x].rc].v,f[x].maxx=f[f[x].rc].maxx;
	else f[x].v=f[f[x].rc].v,f[x].maxx=min(f[f[x].lc].maxx,f[f[x].rc].maxx);
	return;
}
void merge(int x1,int x2,int l,int r)
{
	if(l==r)
	{
		f[x1].v=f[x1].v+f[x2].v,f[x1].maxx=l;
		return;
	}
	int mid=(l+r)>>1;
	if(f[x1].lc&&f[x2].lc)merge(f[x1].lc,f[x2].lc,l,mid);
	else f[x1].lc=f[x1].lc|f[x2].lc;
	if(f[x1].rc&&f[x2].rc)merge(f[x1].rc,f[x2].rc,mid+1,r);
	else f[x1].rc=f[x1].rc|f[x2].rc;
	if(f[f[x1].lc].v>f[f[x1].rc].v)f[x1].v=f[f[x1].lc].v,f[x1].maxx=f[f[x1].lc].maxx;
	else if(f[f[x1].lc].v!=f[f[x1].rc].v)f[x1].v=f[f[x1].rc].v,f[x1].maxx=f[f[x1].rc].maxx;
	else f[x1].v=f[f[x1].rc].v,f[x1].maxx=min(f[f[x1].lc].maxx,f[f[x1].rc].maxx);
	return;
}
void dfs1(int x,int y)
{
	deep[x]=deep[y]+1,f1[x][0]=y;
	for(int i=0;i<a[x].size();i++)if(a[x][i]!=y)dfs1(a[x][i],x);
	return;
}
void dfs2(int x,int y)
{
	for(int i=0;i<a[x].size();i++)if(a[x][i]!=y)dfs2(a[x][i],x),merge(root[x],root[a[x][i]],1,100000);
	ans[x]=f[root[x]].maxx;
	return;
}
int main()
{
	int x,y,z;
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)scanf("%d%d",&x,&y),a[x].push_back(y),a[y].push_back(x);
	for(int i=1;i<=n;i++)root[i]=++num;
	dfs1(1,0);
	for(int i=1;i<=20;i++)
	{
		for(int j=1;j<=n;j++)f1[j][i]=f1[f1[j][i-1]][i-1];
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		modify(root[x],1,100000,z,1),modify(root[y],1,100000,z,1),modify(root[LCA(x,y)],1,100000,z,-1);
		if(LCA(x,y)!=1)modify(root[f1[LCA(x,y)][0]],1,100000,z,-1);
	}
	dfs2(1,0);
	for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
	

  return 0;
}

posted @ 2024-05-31 09:54  dijah  阅读(79)  评论(0编辑  收藏  举报