10 月做题记录(二)

10.19 夜空*

被卡常 \(80\) 分。

题目链接。

感受一下,把序列顺时针写成一个环,环上有一个指针,每次指针向左或者向右移两格,把经过的数字合并。这里认为右是顺时针,左是逆时针。

首先,最终的 \(b\) 一定从指针开始,顺时针对应了原本 \(a\) 上的一个环上的连续段。问题变成给出一种分段与指针位置的合法判定,尽量简洁。

第一:如果这个指针没有遍历完所有的格子,理应会有一段连续的长为 \(1\) 的段。

第二:如果这个指针先向右走,产出了若干个 \(2\),接下来向左走,会把两个 \(2\) 合成 \(4\),而 \(4\) 在模 \(3\) 意义下还是 \(1\)。

第三:如果这个指针向左走的最后一步合并了 \(1\) 和 \(2\),那么才会产生 \(3\),这一步结束后,序列只像 \(1,1,1,1,1|3,4,4,4,4\) 这样。

再继续,中途当然会出现 \(4,4,4|2,2,3,4,4,4,4\) 这样的情况。

然而只有这个 \(3\) 是跨过了起点的,也就是考察初始的 \(a\),这个 \(3\) 包含了首尾。

于是我们猜测,判定形如:

1

  • 粉色部分形如若干个长为 \(3k+4\) 的段。红色部分形如若干个长为 \(1\) 的段。
  • 橙色部分是一个段,假设序列开头那段长度为 \(l\),结尾那段长度为 \(r\),则这个形态要求 \(l=r=0\) 或 \((l-r)\bmod 3=1\)。需要对称的再做一次(黄色部分在橙色部分顺时针 / 逆时针)。
  • 黄色部分形如若干个长为 \(3k+2\) 的段。
  • end 是最终指针所在的位置。

可以证明这些都是必要条件。自然给出判定后还需要构造。容易发现长度为 \(3k+2\) 的段被跨过了恰好奇数次,而长度为 \(3k+4\) 的段被跨过了恰好偶数次,于是形如:

1

实际上,当指针在某个位置时,你先往右动 \(2\) 步,再往左动 \(1\) 步,就把右侧的 \(4\) 个数合成了 \(1\) 个数。

而从起点出发,右侧的粉色是怎么处理的呢?右 \(2\),操作剩余的粉色,然后左 \(1\)。所以现在问题变成构造橙色部分和黄色部分的操作。

我们在碰到橙粉交界线或者黄粉边界线时,直接开始处理粉色,处理完还可以回来。

这里认为黄色在橙色逆时针侧,左侧。处理黄色时不能回来,每次都得左 \(1\),所以相当于处理橙色部分,然后到达橙色部分的左侧。

橙色其实就是有两个数 \(l,r\)(虽然这里 \(r\) 是左侧的,\(l\) 是右侧的,有点绕),每次 \(l-2,r+1\) 或者 \(l+1,r-2\),这个暴搜都可以。如果 \(l\ge 4\) 或者 \(r\ge 4\) 就先把 \(4\) 个数合成 \(1\) 个数。然后贪心即可,在 \(l=0\) 时去操作粉色。

最后到 \(l=1,r=0\) 时就可以去操作黄色。


然后就可以开始把判定运用到题目上了,枚举终点的位置,终点右侧是黄段,如果有多种可能决策的话,显然选长度最短的决策不劣,直到达到橙色部分为止。

橙色部分确定之后,粉色部分还是选最短的段(但必须是 \(>1\)),因为如果选了较长的段收回来时把红色部分同化为了粉色部分,也是没有问题的。

1

对 \(b\) 来说长这样,中间开始红色之后,每步显然也是尽量红色,所以关键的位置只有两个,也就是开始橙色和开始红色,这样复杂度是 \(O(n^2m^2)\) 的。

仍然是枚举起点,这次 \(O(n^2m)\) 预处理出从 \(i\) 左侧开始匹配 \([j,m]\) 的最大左端点和对应的方案,这样就能做完全部 \(nm^2\) 种判定,复杂度 \(O(n^2m)\)。

具体的,预处理 \(f_{i,j,k}\) 表示 \([1,i]\) 的一个后缀匹配 \(b\) 中的 \([j,m]\),钦定红色区间的右端点是 \(k\),对应的匹配到的左端点就是 \(f_{i,j,k}\)。

10.20 木棉*

已通过。

题目链接。

考虑还原 Prufer 序列。

数字 \(i\) 的最后一次出现在 \(e_i\),那么在 \(e_i+1\) 时刻点 \(i\) 进入叶子队列。每个时刻,我们删除叶子队列中的最小值。

考虑数字 \(x\) 的最后一次出现在 \(e_x\),在 \(e_x+1\) 时刻点 \(x\) 进入叶子队列,然而等到时刻 \(y\),点 \(x\) 才被弹出。只需要考虑所有 \(<x\) 的数,等他们都被删完,点 \(x\) 就有机会了。

这样就可以写出正确的暴力了。

发现除了 \(e_i=l-1\) 以外,其余的 \(e\) 互不相同,这意味着序列可以被分成两个部分:

  • \([l,p]\) 的所有位置都被 \(<x\) 的数占用。
  • \([p+1,r]\) 虽然也有位置被占用,但是不会互相冲突。

把所以 \(e_i\neq l-1\) 的位置填上后,再从小到大填 \(e_i=l-1\) 的即可。

记序列 \(a\) 上,下一个和位置 \(i\) 相同的数在位置 \(nxt_i\)。若 \(nxt_i\ge r\),则位置 \(i+1\) 被填上。不过具体来说,位置 \(i+1\) 被填上有三个条件:

  • \(a_i<x\)
  • \(nxt_i\ge r\)
  • \(l\le i\)

且我们要找 \([l,r]\) 中不满足条件的第 \(k+1\) 个数。其中 \(k\) 是 \([0,x)\) 的数有几个没有在 \([l,r)\) 中。\(k\) 就只需要统计满足上面的条件的 \(i\) 的个数。

那么至少要做三维偏序,因为空间充裕,考虑树套树。我们按照 \(r\) 从大到小扫描线,每次加入所有 \(nxt_i\ge r\) 的数。

二分第 \(k+1\) 个数的位置,假设位置在 \(p\),求得 \(\le p\) 没填的位置有 \(s_p\) 个,则找到最小的 \(p\) 满足 \(s_p-s_{l-1}\ge k+1\) 即可。

考虑树状数组套线段树,在树状数组上倍增即可。找最大的 \(p\) 满足 \(s_p-s_{l-1}<k+1\),把 \(j\) 从大到小枚举,能跳就跳即可。

:::info[暴力]

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

const int N=2e5+6;
int a[N],ls[N],nxt[N];
bool solve(int l,int r,int x,int y){
	if(x==y||x==r-l+1) return 0;
	int k=x,e=l-1;
	for(int i=l;i<r;i++){
		if(a[i]<x&&nxt[i]>=r) k--;
		if(a[i]==x) e=i;
	}
	int p=r;
    if(k==0) p=l-1;
    else{  
    	for(int i=l;i<r;i++){
    		if(a[i]<x&&nxt[i]>=r) continue;
    		k--;
    		if(k<=0){
    			p=i;
    			break;
    		}
    	}
    }
    p++;
	p=max(p,e+1);
	//cout<<"pos: "<<x<<" "<<p<<endl;
	return (p==r?r-l+1==y:min(a[p],r-l+1)==y);
}
vector<bool> kapok(int c,int n,int m,vector<int> aa,vector<int> l,vector<int> r,vector<int> x,vector<int> y){
	vector<bool> ans;
	for(int i=0;i<n;i++) a[i]=aa[i];
	for(int i=0;i<n+2;i++) ls[i]=n;
	for(int i=n-1;i>=0;i--){
		nxt[i]=ls[a[i]];
		ls[a[i]]=i;
	}
	for(int i=0;i<m;i++){
		ans.push_back(solve(l[i],r[i],x[i],y[i])|solve(l[i],r[i],y[i],x[i]));
	}
	return ans;
}

/*
signed main(){
	int c,n,m;
	cin>>c>>n>>m;
	vector<int> a,l,r,x,y;
	for(int i=1,z;i<=n;i++) cin>>z,a.push_back(z);
	for(int i=1,L,R,X,Y;i<=m;i++){
		cin>>L>>R>>X>>Y;
		l.push_back(L);
		r.push_back(R);
		x.push_back(X);
		y.push_back(Y);
	}
	vector<bool> ans=kapok(c,n,m,a,l,r,x,y);
	for(int i=0;i<m;i++) cout<<ans[i]<<endl;
}*/

:::

10.20 树上查询

已通过。

题目链接。

一个关键性质是,设 \(a_i\) 为 \(\mathrm{dep}_{\mathrm{LCA}(i,i+1)}\),则 \(\mathrm{dep}_{\mathrm{LCA}(l,r)}=\min_{i=l}^{r-1} a_i\)。\(l=r\) 要特判。

问题变成在 \([l,r-1]\) 里选一个大小 \(\ge k-1\) 的子区间,使得最小值最大。

假设最小值是 \(a_p\),找到区间 \([L,R]\) 是一个极长子区间,\(L\le p\le R\),且 \([L,R]\) 的最小值是 \(a_p\)。问题变成对于查询 \([l,r]\),找到 \(|[L,R]\cap[l,r]|\ge k\) 中最大的 \(a_p\)。

分类讨论,有两种情况:

  • \(R\ge r\),且 \(L\le r-k+1\)。
  • \(l+k-1\le R\le r\),且 \(R-L+1\ge k\)。

第一个直接树状数组,第二个按照 \(k\) 从大到小扫描线,线段树即可。

10.21 序列变换*

已通过。

题目链接。

先考虑操作的形态再考虑如何最优化或计数。

一次操作必会置 \(0\),而 \(0\) 的两半是独立的。

更进一步,考察最终序列上的每一个非 \(0\) 数,都是由一个“封闭的区间”得来的。

若区间 \([l,r]\) 是一个封闭的区间,注意到,其中奇数位置的和减去偶数位置的和永远不变,所以最后留下的那个数的数值是唯一确定的。位置的奇偶性也被确定了。

然而我们仍然不知道有哪些位置 \(i\) 可以承载最后留下的数。不过可以这样考虑,从 \(l\) 开始往右推,最多可以推到第 \(x\) 个位置,从 \(r\) 开始往左推,最多可以推到第 \(y\) 个位置。那么 \([l,i]\) 要可以被推到 \(i\),\([i+1,r]\) 要可以被推到 \(i+1\)。那么只要 \(i\) 的奇偶性正确就必定可以被留下。

据此可以写出 \(40\) 分代码。找到 \([y-1,x]\) 中奇偶性正确的位置上的最小值即可。然后再做一个暴力 dp。

:::info[\(40\) 分代码]

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

namespace tymxz{
	const int N=5007,inf=1e9+7;
	int n,a[N],b[N],c[N];
	int L[N],R[N];
	int mn[2][N][20],Lo[N];
	typedef long long ll;
	ll su[N],f[N],v[N];
	void ST(int o){
		Lo[0]=-1;
		for(int i=1;i<=n;i++) Lo[i]=Lo[i>>1]+1;
		for(int i=1;i<=n;i++) mn[o][i][0]=(i%2==o?b[i]:inf);
		for(int j=1;j<=Lo[n];j++){
			for(int i=1;i+(1<<j)-1<=n;i++){
				mn[o][i][j]=min(mn[o][i][j-1],mn[o][i+(1<<j-1)][j-1]);
			}
		} 
	}
	int qry(int o,int a,int b){
		if(a>b) return inf;
		int k=Lo[b-a+1];
		return min(mn[o][a][k],mn[o][b-(1<<k)+1][k]);
	}
	void subt(){
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		for(int i=1;i<=n;i++) cin>>b[i];
		for(int i=1;i<=n;i++) cin>>c[i];
		for(int i=1;i<=n;i++){
			L[i]=i;
			int x=a[i];
			for(int j=i+1;j<=n;j++){
				if(x<=a[j]){
					L[i]=j;
					x=a[j]-x;
				}else break;
			}
		}
		for(int i=n;i>=1;i--){
			R[i]=i;
			int x=a[i];
			for(int j=i-1;j>=1;j--){
				if(x<=a[j]){
					R[i]=j;
					x=a[j]-x;
				}else break;
			}
		}
		ST(0),ST(1);
		for(int i=1;i<=n;i++) su[i]=su[i-1]+(i&1?a[i]:-a[i]),v[i]=v[i-1]+b[i];
		for(int i=1;i<=n;i++){
			f[i]=0;
			for(int j=0;j<i;j++){
				int l=R[i]-1,r=L[j+1];
				l=max(l,j+1),r=min(r,i);
				ll s=su[i]-su[j];
				if(s==0) continue;
				int w=qry(s>0?1:0,l,r);
				if(w==inf) continue;
				f[i]=max(f[i],f[j]+v[i]-v[j]-w);
			}
		}
		cout<<f[n]<<" "<<0<<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;
}

:::

如果直接把最优化的代码套到计数,显然会算重。但是我们要考虑什么时候才会算重。

假设 \(r\) 可以从 \(r_1\) 扩张成 \(r_2\),那么 \([r_1+1,r_2]\) 一定是一个可以完全消除的区间。

钦定右端点需要最小,再加上一些特判即可。

:::info[\(100\) 分代码]

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

namespace tymxz{
	const int N=5007,mod=1e9+7;
	int n,a[N],b[N],c[N];
	int L[N],R[N];
	int mn[2][N][20],Lo[N];
	typedef long long ll;
	ll su[N],f[N],v[N];
	int deg[2][N],g[N];
	
	int qpow(int a,int b){
		int r=1;
		while(b){
			if(b&1) r=1ll*r*a%mod;
			a=1ll*a*a%mod;
			b=b>>1;
		}
		return r;
	}
	void ST(int o){
		Lo[0]=-1;
		for(int i=1;i<=n;i++) Lo[i]=Lo[i>>1]+1;
		for(int i=1;i<=n;i++) mn[o][i][0]=(i%2==o?b[i]:mod);
		for(int j=1;j<=Lo[n];j++){
			for(int i=1;i+(1<<j)-1<=n;i++){
				mn[o][i][j]=min(mn[o][i][j-1],mn[o][i+(1<<j-1)][j-1]);
			}
		}
		for(int i=1;i<=n;i++) deg[o][i]=(deg[o][i-1]+(i%2==o?qpow(c[i],mod-2):0))%mod;
	}
	int qry(int o,int a,int b){
		if(a>b) return mod;
		int k=Lo[b-a+1];
		return min(mn[o][a][k],mn[o][b-(1<<k)+1][k]);
	}
	void subt(){
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		for(int i=1;i<=n;i++) cin>>b[i];
		for(int i=1;i<=n;i++) cin>>c[i];
		for(int i=1;i<=n;i++){
			L[i]=i;
			int x=a[i];
			for(int j=i+1;j<=n;j++){
				if(x<=a[j]){
					L[i]=j;
					x=a[j]-x;
				}else break;
			}
		}
		for(int i=n;i>=1;i--){
			R[i]=i;
			int x=a[i];
			for(int j=i-1;j>=1;j--){
				if(x<=a[j]-(i!=n)){
					R[i]=j;
					x=a[j]-x;
				}else break;
			}
		}
		ST(0),ST(1);
		for(int i=1;i<=n;i++) su[i]=su[i-1]+(i&1?a[i]:-a[i]),v[i]=v[i-1]+b[i];
		int prod=1;
		for(int i=1;i<=n;i++) prod=1ll*prod*c[i]%mod;
		g[0]=1;
		for(int i=1;i<=n;i++){
			f[i]=g[i]=0;
			for(int j=0;j<i;j++){
				int l=R[i]-1,r=L[j+1];
				l=max(l,j+1),r=min(r,i);
				ll s=su[i]-su[j];
				if(s==0) continue;
				int o=(s>0?1:0);
				int w=qry(o,l,r);
				if(w==mod) continue;
				f[i]=max(f[i],f[j]+v[i]-v[j]-w);
				g[i]=(g[i]+1ll*g[j]*(deg[o][r]-deg[o][l-1]+mod))%mod;
			}
		}
		if(L[1]==n&&su[n]==0) g[n]=(g[n]+1)%mod;
		g[n]=1ll*g[n]*prod%mod;
		cout<<f[n]<<" "<<g[n]<<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;
} 

:::

10.21 数字树

已通过。

题目链接。

题解待补。

10.21 大雨*

已通过。

题目链接。

考虑 \(a_n=0\) 的部分分,此时如果 \(a_n\) 不被修改,则所有数都要被改为 \(0\),这是平凡情况,判一下就好。

否则 \(a_n\) 会被修改。把每一段的极高位(\(>30\))修改使得我们不需要考虑相邻段之间的贡献。

段内,考虑任意的 \(a_i,a_{i+1}\),对于这一段涉及的异或 \(x\),只需要 \(x\) 的第 \(\mathrm{highbit}(a_i\oplus a_{i+1})\) 是 \(0/1\) 即可。限制是单调的,双指针即可。

记这样求得的答案为 \(w\),我们只需要判断真实答案是不是 \(w-1\) 即可。

更进一步,我们给前 \(i\) 个数做划分,最后一个数是 \(j\),段数是 \(g_{i,j}\)。显然 \(g_{i,j}\ge f_i\)。其中 \(f_i\) 是上面那个无限制的 dp 的段数。

若 \(g_{i,j}>f_i\),则后面就算无限制,答案也只会求得 \(w\) 的答案。

所以我们只考虑 \(g_i\) 表示前 \(i\) 个数段数 \(=f_i\) 的方案中,最后一位的最小值。

考虑如何从 \(g_j\to g_i\)。先找出 \([j+1,i]\) 中对 \(x\) 的限制。随后 \(g_j\le a_{j+1}\oplus x\)。在此基础上要最小化 \(a_i\oplus x\)。

钦定 \(g_j\) 和 \(a_{j+1}\oplus x\) 在高 \(p\) 位都是相同的,第 \(p+1\) 位 \(g_j\) 是 \(0\),\(a_{j+1}\oplus x\) 是 \(1\) 即可。这显然又是一些限制。

对于被钦定的部分的合并可以用位运算。所以 \(O(n^2\log V)\) 可以写。拼上 \(a_n=0\) 有 \(76\) 分,赛时做到这就可以跑路了。

看眼题解,发现怎么直接在 trie 树上打标记就做完了。

还是从查询出发。如果总节点数足够少,查询时,如果我们走到一棵子树里,发现这棵子树里居然没有合法点,那就可以直接把这整棵子树删除了。均摊就是对的。

修改时,我们有一些子树是满的,我们把满的子树缩成一个点,这个点下方只考虑满的情况即可。

对于一个满点,记录其左端点 \(j\),并且需要知道左端点 \(j\) 对应的限制。差分后,限制可以表示成两个二进制数,表示哪些位置不能是 \(0\),哪些位置不能是 \(1\)。满点可以快速判断内部有没有合法点。如果没有就可以删去这个满点。

所以其实根本不需要打标记,其实就是维护虚树。。。

现在我们要知道 \(j\) 是否可以转移到 \(i\)。需要处理出 \([j+1,i]\) 的所有限制,把禁用 \(0\) 和禁用 \(1\) 的限制分别找出即可,就是取 or。因为 \(dp_i=dp_j+1\) 所以这部分时间复杂度 \(O(n)\)。

:::info[代码]

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

namespace tymxz{
	const int N=1e6+7,LG=30;
	int n;
	int a[N],vs[N],L[N];
	int v[30][2],cnt;
	void work(int p,int x){
		int j=vs[p];
		if(j==-1) return;
		int o=(a[p]>>j)&1;
		if(v[j][0]&&v[j][1]) cnt--;
		v[j][o]+=x;
		if(v[j][0]&&v[j][1]) cnt++;
	}
	int dp[N],g[N];
	int trie[N*LG][2],tot;
	int c[N*LG];
	void chmax(int &x,int y){
		if(x<y) x=y;
	}
	void init(){
		tot=1;
		c[tot]=0;
	}
	void ins(int &r,int a,int b,int id){//a<=b^x
		if(r==0) r=tot=1,trie[tot][0]=trie[tot][1]=c[tot]=0;
		int p=1;
		for(int i=LG-1;i>=0;i--){
			int a1=(a>>i)&1,b1=(b>>i)&1;
			if(a1==0){
				if(!trie[p][b1^1]) trie[p][b1^1]=++tot,trie[tot][0]=trie[tot][1]=c[tot]=0;
				chmax(c[trie[p][b1^1]],id+1);
			}
			if(!trie[p][a1^b1]) trie[p][a1^b1]=++tot,trie[tot][0]=trie[tot][1]=c[tot]=0;
			p=trie[p][a1^b1];
		}
		chmax(c[p],id+1);
	}
	const int inf=(1<<30)+1;
	int dl[N][2],dr[N][2],nw,X,ans;
	int solve(int u,int v,int x,int y){
		if(dp[u]+1!=dp[v]) return inf;
		x|=dl[u][0]|dr[v][0];
		y|=dl[u][1]|dr[v][1];
		if(x&y) return inf;
		return (x^X)&(x|y);
	}
	void qry(int &p,int dep,int x,int y){
		if(p==0) return;
		if(c[p]>0){
			int j=c[p]-1;
			int w=solve(j,nw,x,y);
			if(w<inf) ans=min(ans,w);
			else c[p]=0;
		}
		if(dep==-1){
			if(c[p]==0) p=0;
			return;
		}
		int i=(X>>dep)&1;
		qry(trie[p][i],dep-1,x|((i!=0)<<dep),y|((i!=1)<<dep));
		if(trie[p][i]==0){
			i^=1;
			qry(trie[p][i],dep-1,x|((i!=0)<<dep),y|((i!=1)<<dep));		
		}
		if(trie[p][0]==0&&trie[p][1]==0&&c[p]==0) p=0;
		return;
	}
	inline int turn(int p,int o){
		p++;
		if(vs[p]==-1) return 0;
		if(o!=((a[p]>>vs[p])&1)) return 0;
		return (1<<vs[p]);
	}
	bool chk(){
		tot=0;
        int rt=0;
		for(int l=0;l<=n;){
			int r=l;
			while(r<=n&&dp[r]==dp[l]) r++;
			for(int i=l;i<r;i++){
				if(i>0){
					nw=i,ans=inf,X=a[i];
					qry(rt,LG-1,0,0);
					g[i]=ans;
				}
			}
			rt=0;
			for(int i=l;i<r;i++){
				if(i<n){
					if(g[i]<inf) ins(rt,g[i],a[i+1],i);
				}
			}
			l=r;
		}
		for(int i=n-1;i>=0;i--){
			if(dp[i]<dp[n]&&g[i]<=a[i+1]) return 1;
			if(a[i]>a[i+1]) return 0;
		}
		return 0;
	}
	void subt(){
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		cnt=0;
		for(int i=0;i<LG;i++) v[i][0]=v[i][1]=0;
		for(int i=2;i<=n;i++){
			if(a[i]!=a[i-1]) vs[i]=__lg(a[i]^a[i-1]);
			else vs[i]=-1;
		}
		int l=1;
		for(int i=1;i<=n;i++){
			if(i>1) work(i,1);
			while(l<i&&cnt>0) work(l+1,-1),l++;
			dp[i]=dp[l-1]+1;
			L[i]=l;
		}
		for(int i=0;i<=n;i++) for(int o=0;o<2;o++){
			if(i>1&&dp[i]==dp[i-1]) dr[i][o]=dr[i-1][o]|turn(i-1,o);
			else dr[i][o]=0;
		}
		for(int i=n;i>=0;i--) for(int o=0;o<2;o++){
			if(i<n&&dp[i]==dp[i+1]) dl[i][o]=dl[i+1][o]|turn(i+1,o);
			else dl[i][o]=0;
		}
		int ans=dp[n];
		if(chk()) ans--;
		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;
}

:::

10.22 传送

已通过。

题目链接。

策略显然是对于深度前 \(k\) 小的点往根走,其他点随机跳。

步数是 \(\frac{n}{k}+\frac{s}{k}\),其中 \(s\) 表示这些点的深度之和。

这显然是分数规划问题,设 \(\frac{n+s}{k}\le p\),则 \(n+s-pk\le 0\)。选的是一个前缀,而且容易发现同一个深度的点只会被一起选。

设深度 \(\le i\) 的深度和为 \(s_i\),点数为 \(a_i\),则要最优化 \(\frac{n+s_i}{a_i}\) 的值。如果取 \(i< p\le i+1\),让 \(\le i\) 的点都能被选而剩下的都不被选,且分数规划式 \(\le 0\),取最小的这样的 \(i\) 即可。

查询邻域用点分治,这样是 \(O(n\log^2 n)\)。注意到相邻两点的答案差距不超过 \(1\),先求出根的答案,贡献到儿子时,在父亲的答案左右范围内搜一下即可。

时间复杂度 \(O(n\log n)\)。

10.22 Two in the Range

题目链接。

你觉得你是题吗?

赛时做法:

首先排序之后扫一遍,假设 \([x,y] \subseteq [l,r]\),那么 \([l,x-1]\) 和 \([y+1,r]\) 全都是 \(0\)。把 \([l,r]\) 丢掉。

这样剩下的区间只有相交和相离的关系。把剩下的区间排序并重新标号,把所有已经是 \(0\) 的位置删除。

转置一下,选择位置 \(i\) 赋值为 \(1\),会导致一个区间的查询都被 \(+1\),要让每个查询都是 \(2\),也就是一个覆盖问题。

这个和 NOI D1T1 是差不多的,其实就是分成两组,每一组都是一些区间 \([x,y]\),能够不重不漏的覆盖 \([1,n]\)。连边 \(x\to y+1\),相当于找两条 \(1\) 到 \(m+1\) 的路径不经过重复的边。可以跑流。只要能够增广两次即可。

正解还不会。

10.22 信号汇聚

要让所有信号汇聚到一点,相当于找到一棵内向树。容易发现,问题其实等价于构造一棵树,使得存在一点到所有点的距离 \(\le h\)。

对其模拟广搜,第 \(i\) 层的点要到达前 \(i-1\) 层的点,在序列上,前 \(i-1\) 层的点把序列划分成了若干个区间,每个区间内是独立的,所以考虑区间 dp。

设 \(f_{i,j,k,0/1}\) 表示区间 \([i,j]\) 有 \(k\) 层,是否可以连到 \(i-1\) 或 \(j+1\)。

假设可以连到右边,找到最左的根,使其连到右边,那么在这个根右边仍然可以连到右边,两半是同级的。但是,这个根的右边分出来的小区间要可以连到这个根。

那么我们按照 \(k-1\to k\) 转移,容易发现小区间一定可以连到左右两边,大区间 \([l,r]\),\([l,c]\) 的根到左边,\([c+1,r]\) 的根到右边即可。

时间复杂度 \(O(n^4)\)。

10.24 工业系统*

已通过。

题目链接。

有性质:只接受儿子的产物即可。证明很简单。

先求出以 \(R\) 为根,所有子树从小到大的排名 \(rk_i\),用于后续的比较。

有性质:子树高度越高,排名越大。证明可以归纳证明。

按照高度分层求排名,先求出高度为 \(d\) 的子树的排名,再求出 \(d+1\) 的。相当于给字符串排序,用 stable_sort,复杂度 \(O(n\log n)\)。

后续要考虑反向的子树,那么我们以直径中点为根。如果直径中点在边上,建一个虚点。有性质:反向的子树比正向的子树要大。证明:因为反向的子树的子树高度更大。

再求出以 \(x\) 为根,\(y\) 的排名。

若以 \(R\) 为根 \(y\) 是 \(x\) 的祖先,则答案显然是 \(dep_x-dep_y+1\)。

否则,除了原本就比 \(y\) 的排名大的子树以外,还要加上 \(x\) 祖先链上,原本小于等于 \(y\) 的子树的贡献。

当 \(o_x=o_y=0\) 时,\(x\) 祖先链上的大小关系显然是单调的,倍增即可。

当 \(o_xo_y=0\) 时,先把查询拆成直链,那么 \(f(x,y)\) 也是有单调性的,可以二分。\(O(\log^2 n)\)。

当 \(o_x=o_y=1\) 时,判掉 \(y\) 是 \(x\) 的祖先和 \(x\) 是 \(y\) 的祖先的情况。具体来说,把 \(x\) 拆成到根的直链,如果 \(y\) 在直链上,直接计算贡献。

若 \(f(R,y)>r\),则一定有 \(f(x,y)>r\)。判掉这些点。

考虑观察固定 \(y\),哪些 \(f(x,y)\le r\)。所有 \(rk_o>rk_y\) 构成一个包含 \(R\) 的连通块。\(x\) 的 \(r-f(R,y)\) 级祖先 \(o\) 一定要是 \(rk_o>rk_y\)。

\(y\) 在一条直链上随时间增加从上到下滑动,\(f(R,y)\) 增大,合法的 \(x\) 一定减少。若考察包含 \(R\) 的直链,前一段时间内全部 \(x\) 都合法,后一段时间内,贡献分成 \(r-f(R,y)\) 和 \(rk_x>rk_y\) 两种贡献。前一个可以直接求,后一个是重点。

形如给你两条到根链 \(A,B\),求 \(\sum_{i\in A}\sum_{j\in B} rk_i>rk_j\)。按照 \(rk\) 的值来分块,有不同块和相同块两种贡献。不同块,只需要在树上做从根到每个点的前缀和。相同块,取出每一对点,在 dfn 上对应了关于 \(A,B\) 的两个区间,

逐块处理,容易划分等价类,二维前缀和,并对询问快速查询。

代码不想写,那咋办。

写完 \(o_xo_y=0\) 可以获得 \(60\) 分。

:::info[\(60\) 分代码]

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

namespace tymxz{
	const int N=1e5+7;
	int n,m,rt,xu;
	vector<int> g[N];
	namespace radi{
		int dp1[N],dp2[N];
		void dfs1(int u,int fa){
			for(int v:g[u]){
				if(v==fa) continue;
				dfs1(v,u);
				dp1[u]=max(dp1[u],dp1[v]+1);
			}
		}
		void dfs2(int u,int fa){
			int mx=dp2[u],se=dp2[u],mxp=0;
			for(int v:g[u]){
				if(v==fa) continue;
				if(dp1[v]+1>mx) se=mx,mx=dp1[v]+1,mxp=v;
				else if(dp1[v]+1>se) se=dp1[v]+1; 
			}
			for(int v:g[u]){
				if(v==fa) continue;
				if(v==mxp) dp2[v]=se+1;
				else dp2[v]=mx+1;
				dfs2(v,u);
			}
		}
		void del(int u,int v){
			vector<int> h;
			for(int t:g[u]) if(v!=t) h.push_back(t);
			g[u].swap(h);
		}
		void q(){
			dfs1(1,0);
			dfs2(1,0);
			int mx=0;
			for(int i=1;i<=n;i++) dp1[i]=max(dp1[i],dp2[i]),mx=max(mx,dp1[i]);
			//cerr<<mx<<endl;
			mx=(mx+1)/2;
			vector<int> h;
			for(int i=1;i<=n;i++) if(dp1[i]==mx) h.push_back(i);
			int s=(int)(h.size());
			if(s==1) rt=h[0];
			else{
				int u=h[0],v=h[1];
				del(u,v),del(v,u);
				g[n+1].push_back(u);
				g[n+1].push_back(v);
				rt=n+1;
				n++;
				xu=1;
			}
			//for(int i=1;i<=n;i++) for(int j:g[i]) cerr<<i<<" "<<j<<endl;
			//cerr<<rt<<endl;
			cerr<<xu<<endl;
		}
	}
	const int LG=19;
	int dep[N],mxd[N],dfn[N],rnk[N],rk[N],siz[N];
	int f[N][LG],fR[N]; 
	namespace c{
		int tot;
		void dfs(int u,int fa){
			dfn[u]=++tot;
			rnk[tot]=u;
			dep[u]=dep[fa]+1;
			f[u][0]=fa;
			siz[u]=1;
			for(int j=1;j<LG;j++) f[u][j]=f[f[u][j-1]][j-1];
			for(int v:g[u]){
				if(v==fa) continue;
				dfs(v,u);
				siz[u]+=siz[v];
				mxd[u]=max(mxd[u],mxd[v]+1);
			}
		}
	}
	namespace s{
		vector<int> tr[N];
		vector<int> gen[N],gn[N];
		int comp(int x,int y){
			int nx=(int)(tr[x].size()),ny=(int)(tr[y].size());
			for(int i=0;i<min(nx,ny);i++){
				if(tr[x][i]<tr[y][i]) return -1;
				if(tr[x][i]>tr[y][i]) return 1;
			}
			if(nx!=ny) return (nx<ny?-1:1);
			return 0;
		}
		bool cmp(int x,int y){
			return comp(x,y)==-1;
		}
		void q(){
			for(int i=1;i<=n;i++) gen[mxd[i]].push_back(i);
			int nw=0;
			for(int i=0;i<=n;i++){
				for(int j:gen[i]){
					for(int k:g[j]) if(mxd[k]<mxd[j]) tr[j].push_back(rk[k]);
					sort(tr[j].begin(),tr[j].end(),greater<int>());
				}
				stable_sort(gen[i].begin(),gen[i].end(),cmp);
				int lst=0;
				for(int j:gen[i]){
					if(lst==0||comp(lst,j)==-1) nw++;
					rk[j]=nw,lst=j;
				}				
			}
			for(int i=1;i<=n;i++) gn[rk[i]].push_back(i);
			int sum=0;
			for(int i=nw;i>=1;i--){
				for(int j:gn[i]) fR[j]=1+sum-xu;
				sum+=(int)(gn[i].size());
			}
		}
	}
	namespace work{
		int qk(int u,int k){
			for(int j=LG-1;j>=0;j--){
				if((1<<j)<=k){
					u=f[u][j];
					k-=(1<<j);
				}
			}
			return u;
		}
		int lca(int u,int v){
			if(dep[u]<dep[v]) swap(u,v);
			for(int j=LG-1;j>=0;j--){
				if(dep[f[u][j]]>=dep[v]){
					u=f[u][j];
				}
			}
			if(u==v) return u;
			for(int j=LG-1;j>=0;j--){
				if(f[u][j]!=f[v][j]){
					u=f[u][j];
					v=f[v][j];
				}
			}
			return f[u][0];
		}
		int chk(int x,int y,int r){
			if(fR[y]>r) return 0;
			int o=qk(x,r-fR[y]);
			return (o==0||rk[o]>rk[y]);
		}
		int q00(int x,int y,int r){
			if(dfn[y]<=dfn[x]&&dfn[x]<=dfn[y]+siz[y]-1) return (dep[x]-dep[y]+1)<=r;
			return chk(x,y,r);
		}
		int q01(int x,int t,int r){
			int l=lca(x,t);
			int ans=min(dep[x]-dep[l]+1,r);
			for(int j=LG-1;j>=0;j--){
				int p=f[t][j];
				if(p==0) continue;
				if(dfn[p]<=dfn[l]&&dfn[l]<=dfn[p]+siz[p]-1) continue;
				if(chk(x,p,r)) continue;
				t=p; 
			}
			if(t==l||chk(x,t,r)) ans+=dep[t]-dep[l];
			else ans+=dep[t]-dep[l]-1;
			if(xu&&l==rt&&q00(x,rt,r)) ans--;
			return ans;
		}
		int q10(int s,int y,int r){
			int l=lca(s,y);
			int ans=(dep[y]-dep[l])*(fR[y]<=r)+1;
			if(y==l){
				ans=min(dep[s]-dep[l]+1,r);
				return ans;
			}
			for(int j=LG-1;j>=0;j--){
				int p=f[s][j];
				if(p==0) continue;
				if(dfn[p]<=dfn[l]&&dfn[l]<=dfn[p]+siz[p]-1) continue;
				if(chk(p,y,r)) continue;
				s=p; 
			}
			if(s==l||chk(s,y,r)) ans+=dep[s]-dep[l];
			else ans+=dep[s]-dep[l]-1;
			if(xu&&l==rt&&fR[y]<=r) ans--;
			return ans;
		}
	}
	void subt(){
		cin>>n;
		for(int i=1,u,v;i<n;i++){
			cin>>u>>v;
			g[u].push_back(v);
			g[v].push_back(u);
		}
		radi::q();
		c::dfs(rt,0);
		s::q();
		cin>>m;
		for(int i=1,s,t,ox,oy,r;i<=m;i++){
			cin>>s>>t>>ox>>oy>>r;
			if(ox==0&&oy==0) cout<<work::q00(s,t,r)<<endl;
			else if(ox==0&&oy==1) cout<<work::q01(s,t,r)<<endl;
			else if(ox==1&&oy==0) cout<<work::q10(s,t,r)<<endl;
			else cout<<0<<endl;
		}
	}
}
int c;
signed main(){
	//freopen("industry6.in","r",stdin);
	//freopen("industry.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>c;
	tymxz::subt();
	return 0;
} 

:::

:::info[\(100\) 分代码]

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

namespace tymxz{
	const int N=1e5+7;
	int n,m,rt,xu;
	vector<int> g[N];
	namespace radi{
		int dp1[N],dp2[N];
		void dfs1(int u,int fa){
			for(int v:g[u]){
				if(v==fa) continue;
				dfs1(v,u);
				dp1[u]=max(dp1[u],dp1[v]+1);
			}
		}
		void dfs2(int u,int fa){
			int mx=dp2[u],se=dp2[u],mxp=0;
			for(int v:g[u]){
				if(v==fa) continue;
				if(dp1[v]+1>mx) se=mx,mx=dp1[v]+1,mxp=v;
				else if(dp1[v]+1>se) se=dp1[v]+1; 
			}
			for(int v:g[u]){
				if(v==fa) continue;
				if(v==mxp) dp2[v]=se+1;
				else dp2[v]=mx+1;
				dfs2(v,u);
			}
		}
		void del(int u,int v){
			vector<int> h;
			for(int t:g[u]) if(v!=t) h.push_back(t);
			g[u].swap(h);
		}
		void q(){
			dfs1(1,0);
			dfs2(1,0);
			int mx=0;
			for(int i=1;i<=n;i++) dp1[i]=max(dp1[i],dp2[i]),mx=max(mx,dp1[i]);
			//cerr<<mx<<endl;
			mx=(mx+1)/2;
			vector<int> h;
			for(int i=1;i<=n;i++) if(dp1[i]==mx) h.push_back(i);
			int s=(int)(h.size());
			if(s==1) rt=h[0];
			else{
				int u=h[0],v=h[1];
				del(u,v),del(v,u);
				g[n+1].push_back(u);
				g[n+1].push_back(v);
				rt=n+1;
				n++;
				xu=1;
			}
			//for(int i=1;i<=n;i++) for(int j:g[i]) cerr<<i<<" "<<j<<endl;
			//cerr<<rt<<endl;
			cerr<<xu<<endl;
		}
	}
	const int LG=19;
	int dep[N],mxd[N],dfn[N],rnk[N],siz[N];
	int f[N][LG],fR[N];
	namespace c{
		int tot;
		void dfs(int u,int fa){
			dfn[u]=++tot;
			rnk[tot]=u;
			dep[u]=dep[fa]+1;
			f[u][0]=fa;
			siz[u]=1;
			for(int j=1;j<LG;j++) f[u][j]=f[f[u][j-1]][j-1];
			for(int v:g[u]){
				if(v==fa) continue;
				dfs(v,u);
				siz[u]+=siz[v];
				mxd[u]=max(mxd[u],mxd[v]+1);
			}
		}
	}
	namespace s{
		int rk[N];
		vector<int> tr[N];
		vector<int> gen[N],gn[N];
		int comp(int x,int y){
			int nx=(int)(tr[x].size()),ny=(int)(tr[y].size());
			for(int i=0;i<min(nx,ny);i++){
				if(tr[x][i]<tr[y][i]) return -1;
				if(tr[x][i]>tr[y][i]) return 1;
			}
			if(nx!=ny) return (nx<ny?-1:1);
			return 0;
		}
		bool cmp(int x,int y){
			return comp(x,y)==-1;
		}
		void q(){
			for(int i=1;i<=n;i++) gen[mxd[i]].push_back(i);
			int nw=0;
			for(int i=0;i<=n;i++){
				for(int j:gen[i]){
					for(int k:g[j]) if(mxd[k]<mxd[j]) tr[j].push_back(rk[k]);
					sort(tr[j].begin(),tr[j].end(),greater<int>());
				}
				stable_sort(gen[i].begin(),gen[i].end(),cmp);
				int lst=0;
				for(int j:gen[i]){
					if(lst==0||comp(lst,j)==-1) nw++;
					rk[j]=nw,lst=j;
				}				
			}
			for(int i=1;i<=n;i++) gn[rk[i]].push_back(i);
			int sum=0;
			for(int i=nw;i>=1;i--){
				for(int j:gn[i]) fR[j]=1+sum-xu;
				sum+=(int)(gn[i].size());
			}
		}
	}
	using s::rk;
	using s::gn;
	typedef long long ll;
	ll res[N];
	namespace work{
		int U,V;
		int qk(int u,int k){
			for(int j=LG-1;j>=0;j--){
				if((1<<j)<=k){
					u=f[u][j];
					k-=(1<<j);
				}
			}
			return u;
		}
		int lca(int u,int v){
			bool fl=0;
			if(dep[u]<dep[v]) swap(u,v),fl=1;
			for(int j=LG-1;j>=0;j--){
				if(dep[f[u][j]]>=dep[v]){
					u=f[u][j];
				}
			}
			if(u==v) return u;
			for(int j=LG-1;j>=0;j--){
				if(f[u][j]!=f[v][j]){
					u=f[u][j];
					v=f[v][j];
				}
			}
			U=u,V=v;
			if(fl) swap(U,V);
			return f[u][0];
		}
		int chk(int x,int y,int r){
			if(fR[y]>r) return 0;
			int o=qk(x,r-fR[y]);
			return (o==0||rk[o]>rk[y]);
		}
		int q00(int x,int y,int r){
			if(dfn[y]<=dfn[x]&&dfn[x]<=dfn[y]+siz[y]-1) return (dep[x]-dep[y]+1)<=r;
			return chk(x,y,r);
		}
		int q01(int x,int t,int r){
			int l=lca(x,t);
			int ans=min(dep[x]-dep[l]+1,r);
			for(int j=LG-1;j>=0;j--){
				int p=f[t][j];
				if(p==0) continue;
				if(dfn[p]<=dfn[l]&&dfn[l]<=dfn[p]+siz[p]-1) continue;
				if(chk(x,p,r)) continue;
				t=p; 
			}
			if(t==l||chk(x,t,r)) ans+=dep[t]-dep[l];
			else ans+=dep[t]-dep[l]-1;
			if(xu&&l==rt&&q00(x,rt,r)) ans--;
			return ans;
		}
		int q10(int s,int y,int r){
			int l=lca(s,y);
			int ans=(dep[y]-dep[l])*(fR[y]<=r)+1;
			if(y==l){
				ans=min(dep[s]-dep[l]+1,r);
				return ans;
			}
			for(int j=LG-1;j>=0;j--){
				int p=f[s][j];
				if(p==0) continue;
				if(dfn[p]<=dfn[l]&&dfn[l]<=dfn[p]+siz[p]-1) continue;
				if(chk(p,y,r)) continue;
				s=p; 
			}
			if(s==l||chk(s,y,r)) ans+=dep[s]-dep[l];
			else ans+=dep[s]-dep[l]-1;
			if(xu&&l==rt&&fR[y]<=r) ans--;
			return ans;
		}
		ll chain(int a,int b,int r,int v=0,int u=0){
			ll ans=0;
			int len=dep[b]-dep[a]+1;
			if(len<=r) ans+=1ll*len*(len+1)/2;
			else ans+=1ll*r*(r+1)/2+1ll*(len-r)*r;
			for(int j=LG-1;j>=0;j--){
				int p=f[b][j];
				if(p==0) continue;
				if(fR[p]<=r) continue;
				b=f[b][j];
			}
			if(fR[b]>r) b=f[b][0];
			if(b&&dep[b]>=dep[a]){
				len=dep[b]-dep[a];
				ans+=1ll*len*(len+1)/2;
			} 
			if(v&&b&&dep[b]>=dep[u]) ans-=1ll*(dep[b]-dep[u])*dep[u];
			return ans;
		}
		struct qy{
			int a,b,id,w;
		}qr[N*4];
		int tot;
		ll we[N];
		void solve(int t,int s,int l,int r,int id){
			for(int j=LG-1;j>=0;j--){
				int p=f[t][j];
				if(p==0) continue;
				if(fR[p]<=r) continue;
				t=p;
			}
			if(t&&fR[t]>r) t=f[t][0];
			if(t==0) return;
			if(dfn[t]<=dfn[l]&&dfn[l]<=dfn[t]+siz[t]-1) return;
			int p=t;
			for(int j=LG-1;j>=0;j--){
				int q=f[p][j];
				if(q==0) continue;
				if(dfn[q]<=dfn[l]) continue;
				if(chk(s,q,r)) continue;
				p=q;
			}
			if(!chk(s,p,r)) p=f[p][0];
			//cerr<<t<<" "<<p<<" "<<l<<endl;
			res[id]+=1ll*(dep[p]-dep[l])*dep[s];
			res[id]-=1ll*(we[t]-we[p]);
			res[id]+=1ll*(dep[t]-dep[p])*r;
			qr[++tot]=(qy){s,t,id,1};
			qr[++tot]=(qy){s,p,id,-1};
		}
		int fa[N];
		void init(){
			for(int i=1;i<=n;i++){
				int u=rnk[i];
				fa[u]=f[u][0];
				we[u]=fR[u]+we[fa[u]];
			}
		}
		void q11(int s,int t,int r,int id){
			if(s==t){
				res[id]=1;
				return;	
			}
			int l=lca(s,t);
			if(s==l){
				res[id]=chain(s,t,r);
				return;
			}
			if(t==l){
				res[id]=chain(t,s,r);
				return;
			}
			if(xu&&l==rt) res[id]=chain(U,s,r,1,l)+chain(V,t,r,1,l);
			else res[id]=chain(l,s,r,1,l)+chain(l,t,r,1,l)-1;
			solve(t,s,l,r,id);
			solve(s,t,l,r,id);
			return;
		}
		const int B=400;
		int s1[N],s2[N],fl[N];
		int su[B*2+7][B*2+7];
		void solve(int L,int R){
			//cerr<<"chk: "<<L<<" "<<R<<endl;
			for(int i=1;i<=n;i++) s2[i]=0;
			for(int i=L;i<=R;i++) for(int u:gn[i]) s2[u]++;
			for(int i=1;i<=n;i++) s2[rnk[i]]+=s2[fa[rnk[i]]];
			for(int i=1;i<=tot;i++){
				ll ans=1ll*s2[qr[i].a]*s1[qr[i].b];
				res[qr[i].id]+=(qr[i].w==1?ans:-ans);
			}
			for(int i=1;i<=n;i++) s1[i]+=s2[i];
			if(L==R) return;
			for(int i=1;i<=n+1;i++) fl[i]=0;
			fl[n+1]=1;
			for(int i=L;i<=R;i++) for(int u:gn[i]){
				fl[dfn[u]]=1,fl[dfn[u]+siz[u]]=1;
				//cerr<<dfn[u]<<" "<<dfn[u]+siz[u]<<endl;
			}
			for(int i=1;i<=n+1;i++) fl[i]+=fl[i-1];
			//for(int i=1;i<=n+1;i++) cerr<<i<<" "<<fl[i]<<endl;
			//cerr<<"?"<<fl[n]<<endl;
			for(int i=1;i<=fl[n];i++){
				for(int j=1;j<=fl[n];j++){
					su[i][j]=0;
				}
			}
			for(int i=L;i<=R;i++) for(int u:gn[i]){
				for(int j=i+1;j<=R;j++) for(int v:gn[j]){
					//cerr<<"pair: "<<u<<" "<<v<<endl;
					int a=dfn[v],b=dfn[v]+siz[v];
					int c=dfn[u],d=dfn[u]+siz[u];
					a=fl[a],b=fl[b],c=fl[c],d=fl[d];
					su[a][c]++;
					su[b][c]--;
					su[a][d]--;
					su[b][d]++;
				}
			}
			for(int i=1;i<=fl[n];i++){
				for(int j=1;j<=fl[n];j++){
					su[i][j]=su[i][j]+su[i-1][j]+su[i][j-1]-su[i-1][j-1];
				}
			}
			for(int i=1;i<=tot;i++){
				int ans=su[fl[dfn[qr[i].a]]][fl[dfn[qr[i].b]]];
				//cerr<<"qry: "<<fl[qr[i].a]<<" "<<fl[qr[i].b]<<endl;
				res[qr[i].id]+=(qr[i].w==1?ans:-ans);
			}
		}
		void train(){
			int L=0,R=0;
			for(int i=1;i<=n;i++){
				L=R+1;
				R=L;
				int s=(int)gn[R].size();
				while(R<n&&s+(int)gn[R+1].size()<=B) s+=(int)gn[R+1].size(),R++;
				solve(L,R);
				if(R==n) break;
			}
		}
	}
	void subt(){
		cin>>n;
		for(int i=1,u,v;i<n;i++){
			cin>>u>>v;
			g[u].push_back(v);
			g[v].push_back(u);
		}
		radi::q();
		c::dfs(rt,0);
		s::q();
		work::init();
		cin>>m;
		for(int i=1,s,t,ox,oy,r;i<=m;i++){
			cin>>s>>t>>ox>>oy>>r;
			if(ox==0&&oy==0) res[i]=work::q00(s,t,r);
			else if(ox==0&&oy==1) res[i]=work::q01(s,t,r);
			else if(ox==1&&oy==0) res[i]=work::q10(s,t,r);
			else work::q11(s,t,r,i);
		}
		work::train();
		for(int i=1;i<=m;i++) cout<<res[i]<<endl;
	}
}
int c;
signed main(){
	//freopen("industry7.in","r",stdin);
	//freopen("industry.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>c;
	tymxz::subt();
	return 0;
} 

:::

10.27 Equal Degree Deletion

已通过。

题目链接。

观察一会,注意到合法的树,可以把树上的每条边赋一个权值,使得每个点相邻的边构成一个 \(0\sim \mathrm{deg}-1\) 的排列。

考虑证明这是充要的,且只会有一种赋权方案。只有一种方案是肯定的,每次把所有叶子删除即可。每次删掉一条权值最大的边,也可以使得赋权合法。

从低往高树形 dp,设 \(f_{u,i}\) 表示点 \(u\) 到父亲的边的权值是 \(i\)。转移时需要写一个 \(2^d\) 的状压。

设 \(u\) 到父亲的权值最大是 \(g_u\),把儿子按照 \(g_v\) 从小到大排序,状压时复杂度可以压到 \(\sum 2^{g_v}g_v\)。容易发现 \(g_v\le \log n\)。

进一步 \(g_u\) 要想得到 \(x\),必须要有 \(0\sim x-1\) 各一个,感受到这个应该用轻子树相关知识即可证明,不过我不会。反正能过。

哦,显然是轻子树大小的复杂度,所以是 \(O(n\log^2 n)\) 的。

10.28 Develop

题目链接。

问题看成可以放球,如果 \(x-2\) 和 \(x+k\) 有球就把那里的球扔掉,显然是一样的。

首先是 \(k\) 为偶数的情况。显然奇数位和偶数位独立,分别做最后乘起来就好。现在问题变成 \(-1\),\(+k\)。

发现当每个连续段长度 \(\le k\) 时合法。证明:考虑一个长度为 \(k+1\) 的窗口,你无法操作完它。构造方案就不同的连续段从小到大操作,同个连续段从大到小操作即可。

随便 dp 一下就好了。

但是正解和 \(k\) 为偶数没什么关系。先建图 \(x\to x-2\) 和 \(x\to x+k\),设球是黑点,否则是白点,然后考虑一个环上的黑点是操作不完的。

所以这个图对于黑点的导出子图一定是个 DAG。我们按照拓扑序倒着操作就可以构造。问题变成计数无环点集。

像这样把图对齐:

从上到下 dp。成环一定需要“偶数到奇数”的边的参与,所以我们需要检查这两个点的可达性。如果两个这样的黑点可达,就会成环。

一条合法路径形如这样,发现只要能到达 \(\le x-k\) 的点就可以了。

从上往下转移,设当前转移了第 \(i\) 行的第 \(o\) 个点,第 \(1\) 列的最后一个点可以到的最远位置是 \(j\),第 \(2\) 列的最后一个点可以到的最远位置是 \(k\)。

posted @ 2026-10-18 20:32  ty_mxzhn  阅读(104)  评论(0)    收藏  举报