11 月做题记录(一)

这个题单用来存放一些宝宝题,不是特别宝宝的放在隔壁了。

11.2 迁徙

已通过。

题目链接。

:::info[代码]

#include <bits/stdc++.h>
using namespace std;

namespace tymxz{
	const int N=2e6+7;
	typedef long long ll;
	int n;
	ll d;
	ll x[N];
	int f1[N],f2[N],dp[N];
	int fir[N],las[N],mx;
	int c1[N],c2[N];
	int d1[N],d2[N],h[N];
	vector<int> g1[N],g2[N];
	ll ans;
	void solve(int c){
		if(c==mx) return;
		for(int i=fir[c]+1;i<=las[c];i++){
			if(i>fir[c]+1&&f1[i]==fir[c]){
				ans+=c2[fir[c]+1];
				if(f2[fir[c+1]]==fir[c]+1) ans+=d2[fir[c+1]];
			}
			if(f1[i]==las[c+1]){
				if(f1[las[c+1]]==fir[c+1]) ans+=d2[fir[c]];
				else ans+=c2[fir[c]];
			}
		}
	}
	void subt(){
		cin>>n>>d;
		for(int i=1;i<=n;i++) cin>>x[i];
		for(int i=1;i<=n;i++){
			f1[i]=lower_bound(x+1,x+n+1,x[i]-d)-x;
			f2[i]=upper_bound(x+1,x+n+1,x[i]+d)-x-1;
		}
		x[n+1]=x[n];
		mx=0;
		for(int i=n;i>=1;){
			int f=i;
			if(x[i]>=x[i+1]-d){
				while(f>=1&&x[f]>=x[i+1]-d) f--;
			}else{
				while(f>=1&&x[f]>=x[i]-d) f--;
			}
			mx++;
			fir[mx]=f+1,las[mx]=i;
			for(int j=fir[mx];j<=las[mx];j++) dp[j]=mx;
			//cout<<fir[mx]<<" "<<las[mx]<<endl;
			i=f;
		}
		//for(int i=1;i<=n;i++) cout<<dp[i]<<" ";
		//cout<<endl;
		for(int i=1;i<=n;i++) g1[i].clear(),g2[i].clear();
		for(int i=1;i<=n;i++){
			g1[f1[i]].push_back(i);
			g2[f2[i]].push_back(i);
		}
		for(int i=n;i>=1;i--){
			d1[i]=c1[i]=h[i]=0;
			for(int j:g1[i]){
				if(dp[i]!=dp[j]) c1[i]++,d1[i]+=(f2[i]==j?d1[j]+h[j]:0);
				else if(i!=j) h[i]++;
			}
			d1[i]+=c1[i];
		}
		for(int i=1;i<=n;i++){
			d2[i]=c2[i]=0;
			for(int j:g2[i]) if(dp[i]!=dp[j]) c2[i]++,d2[i]+=(f1[i]==j?d2[j]:0);
			d2[i]+=c2[i];
		}
		ans=0;
		for(int i=1;i<=mx;i++) solve(i);
		for(int i=1;i<n;i++) if(dp[i]==dp[i+1]){
			int a=0,b=0;
			for(int j:g1[i]) if(dp[i]!=dp[j]){
				a++;
				if(f2[i+1]==j) a+=d1[j]+h[j];
			}
			for(int j:g2[i+1]) if(dp[i+1]!=dp[j]){
				b++;
				if(f1[i]==j) b+=d2[j];
			}
			ans+=1ll*a*b;
		}
		cout<<ans<<endl;
	}
}
int c,t;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>c>>t;
	while(t--) tymxz::subt();
	return 0;
} 

:::

11.2 研发基地*

已通过。

题目链接。

二分答案,现在一个点可以覆盖距离它 \(\le x\) 的点。

在树上断掉一条边,分成的两个连通块独立求。

考虑怎么求,有一个比较简单的方法:考虑所有叶子,叶子都要求它的父亲距离 \(\le x-1\) 的地方有点。

那么答案就是删除 \(x\) 轮叶子后剩下的点数。

考虑一个点何时会被删。以其为根的子树有很多种,取高度最小的那种,如果高度 \(\le x\) 那就会被删。这是一个下界,我们觉得这也是上界。

直径中点一定在当前根的长链上,显然有两个部分:直径中点的祖先,和其他点。其他点的贡献显然可以直接计算,直径中点到根的直链上只有一段是合法的。

那么我们记录子树里黑点的最高处,如果发现短儿子有限制,现在的黑点够不到,那你就直接把黑点往上抬!虽然这很弱智,但是确实是对的。当然你不能抬到这个点上。

设 \(f\) 是根不一定是黑点的答案,\(g\) 是子树全部是子树的答案。\(h\) 是长儿子黑点的最高处。除了这个还需要维护长儿子的 \(f\) 和 \(g\),短儿子的最大 \(d\),短儿子的 \(g\)。

处理子树的补,直接换根 dp 即可。有结合律,维护前后缀信息。

:::info[代码]

#include <bits/stdc++.h>
using namespace std;

namespace tymxz{
	const int N=1e6+7;
	struct info{
		int d1,d2;
		int f,g;
		int h;
	};
	info operator + (info a,info b){
		if(a.d1<b.d1) swap(a,b);
		a.d2=max(a.d2,b.d1);
		a.g+=b.g;
		return a;
	}
	int x;
	info turn(info a){
		info c;
		c.d2=-1,c.g=0;
		c.d1=a.d1+1,a.d2++;
		c.g=a.g+(c.d1>=x);
		c.f=0x3f3f3f3f,c.h=0;
		if(a.d2<x){
			a.h++;
			int to=x-a.d2;
			c.f=(a.h>to?a.h-to:0)+a.f;
			c.h=min(a.h,to);
		}
		if(c.f>=max(1,c.g)) c.f=max(1,c.g),c.h=0;
		return c;
	}
	int n,k;
	vector<int> g[N],t[N]; 
	int dfn[N],tot;
	void dfs(int u,int fa){
		dfn[++tot]=u;
		for(int v:g[u]){
			if(v==fa) continue;
			dfs(v,u);
			t[u].push_back(v);
		}
	}
	info z=(info){-1,-1,1,0,0};
	info tmp1[N],tmp2[N];
	info dp1[N],dp2[N];
	void solve1(int u){
		dp1[u]=z;
		for(int v:t[u]){
			dp1[u]=dp1[u]+dp1[v];
		}
		dp1[u]=turn(dp1[u]);
	}
	bool solve2(int u){
		if(dp1[u].f+dp2[u].f<=k) return 1;
		int s=(int)(t[u].size());
		if(s==0) return 0;
		tmp1[0]=z;
		for(int i=1;i<=s;i++) tmp1[i]=tmp1[i-1]+dp1[t[u][i-1]];
		tmp2[s+1]=z;
		for(int i=s;i>=1;i--) tmp2[i]=tmp2[i+1]+dp1[t[u][i-1]];
		for(int i=1;i<=s;i++) dp2[t[u][i-1]]=turn(tmp1[i-1]+tmp2[i+1]+dp2[u]);
		return 0;
	}
	bool chk(int mid){
		x=mid;
		for(int i=n;i>=1;i--) solve1(dfn[i]),dp2[i]=z;
		for(int i=1;i<=n;i++) if(solve2(dfn[i])) return 1;
		return 0;
	}
	void subt(){
		cin>>n>>k;
		for(int i=1;i<=n;i++) g[i].clear(),t[i].clear();
		for(int i=1,u,v;i<n;i++){
			cin>>u>>v;
			g[u].push_back(v);
			g[v].push_back(u);
		}
		tot=0;
		dfs(1,0);
		int lb=0,ub=n,ans=1;
		while(lb<=ub){
			int mid=(lb+ub)>>1;
			if(chk(mid)){
				ub=mid-1,ans=mid;
			}
			else{
				lb=mid+1;
			}
		}
		cout<<ans<<endl;
	}
}
int c,t;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>c>>t;
	while(t--){
		tymxz::subt();
	}
	return 0;
} 

:::

11.3 Count of Euclid Function Call

题目链接。

\(n=0\) 时特判。

\(n>0\) 时,辗转相除可以倒过来,而不需要考虑 \(n=m\) 时无法转移的情况。

手玩一下发现最终得到的 \((n',m')\) 中,\(m'=cn+dm\)。要求 \(cn+dm\le N\) 的数量,注意不是 \((c,d)\) 的数量而是有多少种方法到达 \((c,d)\)。

\((c,d)\) 的合法转移是 \((c,d)\to (d,c+kd)\),其中 \(k\ge 1\)。从 \((0,1)\) 开始。

发现对于 \((c,d)\) 倒过来还原,只要 \(\gcd(c,d)=1\),大部分情况下都能唯一还原,只有最后几步是 \((0,1,1,a,...)\) 还是 \((0,1,a,...)\)。特判即可。

问题还是变成 \(c<d\),\(\gcd(c,d)=1\) 且 \(cn+dm\le N\) 的数量。莫反即可。

11.3 货运通道

题目链接。

题意:一条链上有 \(n+2\) 个点,\(n+1\) 条边。

连接 \(i\) 和 \(i+1\) 的边有边权 \(B_i\)。规定 \(B_0=B_{n}=\infty\)。

对于 \(1\le i\le n\),\(A_i\) 表示到达点 \(i\) 时货物量增加 \(A_i\)。任何时候货物量不能 \(<0\)。

经过第 \(i\) 条边时货物量超过 \(B_i\),多余的货物会被丢弃。可以花费一点代价给任意一个 \(B_i\) 增加 \(1\)。

有两种操作,第一种是单点给 \(A_i\) 加上一个正数,第二种是查询初始货物为 \(k\) 从点 \(0\) 出发到达 \(n+1\) 的最小代价。

设 \(h_i\) 表示至少要携带多少货物到达点 \(i\)(还没有卸货)才能度过 \(i\to n+1\)。

显然 \(h_{n+1}=0\),且 \(h_i=\max(h_{i+1}-A_i,0)\)。若 \(B_i<h_{i+1}\) 则一定要付出 \(h_{i+1}-B_i\) 的代价来抬高。

考虑如何维护单点给 \(A_i\) 加上正数。显然 \(h_i\) 只减不增,付出的代价也只减少。

先对 \(h_i\) 减少 \(y\),再对 \(h_{i-1}\) 减少 \(h_i\) 实际减少的量。类似单侧递归,先递归右侧,如果右侧减少完还剩 \(x>0\),就再到左侧减少。对整个区间都 \(\ge y\) 的情况特判。

问题变成在 \(h_{i}-B_{i-1}\) 的序列上做区间减法,但是要对 \(0\) 取 \(\max\)。

维护非 \(0\) 最小值,如果一个区间内有会被减完的,就要递归下去,显然复杂度是 \(O(n\log n)\) 的。

这玩意显然没有细节,懒得写了。

11.3 Splitting Haybales P

已通过。

题目链接。

题意相当于每次到 \(i\) 时,如果 \(x>0\) 则 \(x\leftarrow x-a_i\),如果 \(x\le 0\) 则 \(x\leftarrow x+a_i\)。

在 \(l\) 开始前加入询问,在 \(r\) 结束时删除询问。把平衡树按 \(0\) 分裂,两边打加法 tag 即可。

但是对于相等值的处理需要思考一下:要么给相等值赋一个第二关键字,要么把相等值缩起来使得不存在相等值。

11.3 树的价值*

已通过。

题目链接。

首先考虑一个 \(O(n^3)\) 的做法。设 \(f_{i,j,k}\) 表示 \(i\) 子树里的 \(\mathrm{mex}\) 恰好是 \(j\),\(i\) 子树里有 \(k\) 个点还没有确定填什么数。

转移时会获得 \(i\) 的儿子中最大的 \(j\) 和所有的 \(k\),并且根也还没有确定填什么数。然后再计算 \(i\) 的 \(\mathrm{mex}\) 是多少:在儿子的基础上,每消耗一个未填的数就可以令其增加 \(1\)。


提前钦定 \(i\) 的儿子中 \(j\) 最大的是 \(\mathrm{son}_i\),用其剖分,如果钦定错误显然不优秀。

上面的那个 dp,在根处会获得一个新的数待填,假设点 \(u\) 在其祖先 \(\mathrm{anc}\) 时被填上数,贡献会持续到链顶。

设 \(f_{i,j,k}\) 表示点 \(i\) 的祖先上,最长的同链连续段长度为 \(j\),当前的同链连续段长度为 \(k\),子树内的答案。做到 \(O(nm^2)\)。


设 \(f_{i,j}\) 表示 \(i\) 的祖先上最长的同链连续段长度为 \(j\),\(i\) 到父亲的边是轻边,子树内的答案。

显然当子树高度不足时,整个子树的答案就是 \(\mathrm{siz}_i\times j\) 了,可以减枝。

找到 \(i\) 子树里的点 \(v\),且 \(i\) 到 \(v\) 之间的点数恰好是 \(j\)。钦定 \(i\) 到 \(v\) 是一条长链,那么这条链旁边的轻边贡献都是 \(f_{*,j}\),而 \(v\) 的子树的贡献是 \(g_{i,j}\)。

\(g_{i,j}\) 意味着 \(i\) 所在的链长度为 \(j\),转移时直接选择一个点从 \(g_{\mathrm{son}_i,j+1}\) 转移来,其他点从 \(f_{*,j}\) 转移来即可。

考虑如何在线算完所有的数值。也就是所有轻边的 \(f\) 之和。要对每一对 \((i,v)\) 做计算。

要长链剖分或者树状数组。

11.4 Equal Mex*

已通过。

题目链接。

介绍一个简单的极小 mex 区间求法。这篇博客。这里复读一下。

从左到右扫描线,设当前扫到 \(i\)。

维护 \(\mathrm{las}_x\) 表示考虑了 \(1\sim i\) 的数,\(x\) 这个数最后一次出现是在 \(\mathrm{las}_x\)。对 \(\mathrm{las}\) 建立线段树。

右端点是 \(i\) 的第一个极小 mex 区间的左端点是 \(j\),且 \([j,i]\) 的 mex 要 \(>a_i\)。\(j\) 显然就是 \(\mathrm{las}\) 数组上 \([0,a_i]\) 的最小值。

接下来要找其他的极小 mex 区间,设上一个极小 mex 区间的 mex 是 \(w\),则这一个极小 mex 区间的 \(j\) 显然是 \(\mathrm{las}_w\)。

然后如果 \(j\) 小于等于 \(a_i\) 上一次出现的位置,则同样不是极小 mex 区间,查询 mex 使用线段树二分,写个 zkw 就能维护。

11.4 古代机器 2

已通过。

题目链接。

题解待补。

11.6 大订单

已通过。

题目描述:给你一个长度为 \(n\) 的序列 \(a\),对于 \(1\le i\le k\),请你把这个序列切成 \(i\) 段并重新排列,求新序列最长不下降子序列的长度的最大值。

\(1\le n\le 1000\),\(1\le k\le 100\),\(0\le a_i\le 5\)。


假设 \(f_{i,j,x,y}\) 表示保留 \([i,j]\) 中值域在 \([x,y]\) 的数后,最长不下降子序列的长度。

把序列切成几段之后,每一段都可以指定一个 \([x,y]\)。这些区间只在端点处相交,没有包含关系,也不能重叠。

直接刻画不好刻画,考虑先枚举端点集合 \(S\subseteq \{0,1,2,3,4,5\}\),重新标号之后,区间变成了两类:\([i,i]\) 形和 \([i,i+1]\) 形。那条件就很简单了,\([i,i+1]\) 型每种只出现一次即可。

枚举上一段的末尾 \(l\),记 \(g_{i,j,T}\) 表示前 \(i\) 个数分 \(j\) 段,已选的 \([i,i+1]\) 集合为 \(T\)。复杂度为 \(O(n^2k)\) 乘上状压复杂度。

考虑优化掉一个 \(n\),发现根本没必要记上一段的末尾,设 \(h_{i,j,x,y,T}\) 表示继承了 \(l\) 处的 \(g\) 数组后任务为选取 \([x,y]\) 的数,并拿取最长不下降子序列。转移完成任务即可。

显然 \([x,y]\) 的个数只有 \(2v\) 个,而状压复杂度也最多 \(3^v\)。而且 \(S\) 不取 \(0\) 和 \(5\) 不优,做一些剪枝就能过了。

11.6 人造序列题

题目链接。

这题也有一个相对平凡的 Clubstep 做法,有时间再补。

把问题转化成给你一个序列 \(a\),你可以给第 \(i\) 个位置 \(+2\),如果存在第 \(i+1\) 个位置可以让它也 \(+1\),让整个序列非负。

显然这是一个贪心题,从左到右依次操作使得每个位置非负即可。

单点修改时,设修改量为 \(\Delta\),下一个位置的修改量大致是 \(-\frac{\Delta}{2}\),那么我们做完了。。。吗?

不难发现还有上取整,有时上取整会反馈到 \(\Delta\) 上,那么如果 \(|\Delta|=1\),可能会一直传递下去。

形式化,设 \(b_{i}\) 表示点 \(i\) 收到上一个位置的多少数,转移是 \(b_i=\max(\lceil \dfrac{-a_{i-1}-b_{i-1}}{2}\rceil,0)\)。

设 \(d_{i-1}\) 是 \(a_{i-1}+b_{i-1}\) 的变化量,那么 \(d_i=\max(\lceil \dfrac{-a_{i-1}-b_{i-1}-d_{i-1}}{2}\rceil,0)-\max(\lceil \dfrac{-a_{i-1}-b_{i-1}}{2}\rceil,0)\)。

若 \(a_{i-1}+b_{i-1}\) 是奇数,且 \(d_{i-1}=1\),那么 \(d_{i}=-1\)。但是若 \(d_{i-1}=-1\) 则 \(d_i=0\)。

若 \(a_{i-1}+b_{i-1}\) 是偶数,且 \(d_{i-1}=-1\),那么 \(d_{i}=1\)。但是若 \(d_{i-1}=1\) 则 \(d_i=0\)。

那么在线段树上,如果操作考虑到了整个线段树区间,且这个区间是奇偶交替的,且 \(d_{i-1}=1\),那么就可以快速跳过,并且给 \(b_i\) 奇数位 \(-1\) 偶数位 \(+1\)。

如果这个区间是偶奇交替的,且 \(d_{i-1}=-1\),也可以快速跳过。随便在线段树上实现一下就好了。

11.7 houran 和芒果

题目链接。

\(m=n\) 时,每个点选择到儿子的边中最大的,树上的每个叶子恰好对应一条链。

一般情况,相当于选择 \(m\) 个叶子,对于一个非叶子,能够选择一个儿子,当其子树里有被选的叶子。所以直接选能选择的儿子里最大的。

假设有 \(4\) 条到儿子的边。设到儿子的边从小到大排序后是 \(w_1,w_2,w_3,w_4\)。构造如下的检测器:

  • 若 \(1,2,3,4\) 中存在点,则 \(+w_1\)。
  • 若 \(2,3,4\) 中存在点,则 \(+w_2-w_1\)。
  • 若 \(3,4\) 中存在点,则 \(+w_3-w_2\)。
  • 若 \(4\) 中存在点,则 \(+w_4-w_3\)。

建 \(4\) 个虚点表示这些决策,问题转化为选择 \(m\) 条链覆盖的点的权值最大化。这是模版题。

11.10 小丑大师的荣耀*

已通过。

题目链接。

将一张卡牌 \((x,y)\) 建边,每个连通块是独立的。

如果一个连通块是基环树,显然可以让整个连通块都被覆盖。如果一个连通块是树,会有一个点没法被覆盖。所以只有 \([mn,mx]\subseteq [l,r]\) 时才会不合法。

首先我们应该思考如何找出所有限制。对于时间维线段树分治,把删除改成撤销,可以找出所有限制。

注意这样会找到 \(O(n\log n)\) 个限制,但是把一些限制合并后,有效的限制其实只有 \(O(n)\) 个。

然后考虑给出 \(n\) 个在 \([tl,tr]\) 时间出现的限制 \([l,r]\),查询某个时刻 \(t\) 时,有多少个 \(s\le L\le t\) 且 \(u\le R\le v\) 满足 \([L,R]\) 不包含任何一个限制。

设 \(f_i\) 表示左端点为 \(i\),最小的非法右端点是 \(f_i\)。初始时 \(f_i=m+1\),显然加入一个限制时 \(f[1,l]\) 需要对 \(r\) 取 \(\min\)。\(f\) 是单调递减的。

把查询差分成 \([s,t],[u,m]\) 的形式。显然合法的个数是 \(\displaystyle \sum_{i=s}^t \max(f_i-u,0)\),因为 \(f\) 是单调递增的,容易查询。

再次线段树分治,并用线段树维护 \(f\)。操作撤销就记录操作栈即可。

线段树操作只需要记录区间最大值,最小值,防止无用递归即可。

第二种做法,考虑把 \(f\) 看成 \(f'\) 的后缀最小值,\(f'\) 的删除,只需要维护单点修改。实际上限制不会重复,如果限制重复了就用堆支持删除。这是楼房重建板子。

但是你真的会楼房重建吗?设 \(w(l,r,x)\) 表示区间 \([l,r]\) 从右侧来了一个对 \(x\) 取 min,此时的和。

若 \(\min(f[mid+1,r])\le x\),则 \(w(l,r,x)=w(l,mid,\min(f[mid+1,r]))+w(mid+1,r,x)\)。其中 \(w(l,mid,\min(f[mid+1,r]))=w(l,r,\inf)-w(mid+1,r,\inf)\)。

若 \(\min(f[mid+1,r])> x\),则 \(w(l,r,x)=w(l,mid,x)+(r-mid)x\)。

改成 \(-u\) 之后,如果 \(x\le u\) 就直接退出,如果 \(\min(f[l,r],x)\ge u\) 就取一整段。

td ARC Stamp

题目链接。

posted @ 2026-11-02 08:13  ty_mxzhn  阅读(9)  评论(0)    收藏  举报