2026.9 做题记录2

因为文章太长会有点卡所以不放在一起了。

9.7

~K Perm Counting

题意

给定 \(n,k\),求长度为 \(n\) 对于所有 \(i\) 均满足 \(\left\lvert p_i-i \right\rvert \ne k\) 的排列 \(p\) 数量。

\(1\le k<n\le 2000\)。

solution

经典的容斥。

令 \(f(S)\) 为钦定位置集合 \(S\) 使得 \(i\in S\) 均有 \(\left\lvert p_i-i \right\rvert = k\),其他位置随便填的排列 \(p\) 数量。

答案为 \(\sum (-1)^{\left\lvert S \right\rvert}f(S)\)。

考虑将限制刻画在网格图上,相当于对于每个 \(i\),将 \((i,i-k)\) 和 \((i,i+k)\) 标记为黑色格子,我们需要在 \(j\) 个黑色格子放车使得它们互不攻击,令这样的方案数为 \(g_j\),那么答案为 \(\sum\limits_{j=0}^{n} (-1)^j \times g_j\times (n-j)!\),其中 \((n-j)!\) 的意义是剩下的随便填的方案数。

考虑如何刻画互不攻击的限制,发现将两个不能同时选中的黑色格子用一条边连接起来,\(g_j\) 等价于大小为 \(j\) 的独立集个数,而由于上面染色的特殊性,这张图一定由若干条链构成,每条链做一次 dp,整体做一次背包即可。

时空复杂度 \(O(n^2)\)。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=4000+10;
constexpr int inf=1e9+10;

constexpr int mod=924844033;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,k,cnt,sz;
int id[N][2],vis[N],fac[N];
int f[N],g[N][N][2],h[N];
vector<int> e[N],mp[N];

inline int dfs(int now,int dep) {
	vis[now]=1;
	for (int v:e[now]) if (!vis[v]) return dfs(v,dep+1);
	return dep;
}

inline int solve(int m) {
	g[0][0][0]=1;
	f(i,1,m) f(j,0,i) {
		add(g[i][j][0]=g[i-1][j][0],g[i-1][j][1]);
		if (j) g[i][j][1]=g[i-1][j-1][0];
	}
	f(i,0,sz) h[i]=f[i],f[i]=0;
	f(i,0,sz) f(j,0,m) add(f[i+j],1ll*h[i]*(g[m][j][0]+g[m][j][1])%mod);
	f(i,0,m) f(j,0,i) g[i][j][0]=g[i][j][1]=0;
	return m;
}

int main() {
	read(n);read(k);
	f(i,1,n) {
		if (i-k>0) {
			id[i][0]=++cnt;
			mp[i-k].push_back(cnt);
		}
		if (i+k<=n) {
			id[i][1]=++cnt;
			mp[i+k].push_back(cnt);
		}
		if (id[i][0]&&id[i][1]) {
			e[id[i][0]].push_back(id[i][1]);
			e[id[i][1]].push_back(id[i][0]);
		}
	}
	f(i,1,n) if (mp[i].size()>1) {
		int x=mp[i].front(),y=mp[i].back();
		e[x].push_back(y);
		e[y].push_back(x);
	}
	f[0]=fac[0]=1;
	f(i,1,cnt) {
		if (vis[i]) continue;
		sz+=solve(dfs(i,1));
	}
	f(i,1,n) fac[i]=1ll*fac[i-1]*i%mod;
	ll ans=0;
	f(i,0,n) ans+=1ll*((i&1)?-1:1)*f[i]*fac[n-i]%mod;
	printf("%d\n",(int)((ans%mod+mod)%mod));
	return 0;
}

Arrays and Palindrome

题意

给定长度为 \(m\) 的序列 \(a\),\(a\) 中元素和为 \(n\),你需要重排数组 \(a\) 并构造数组 \(b\),满足:

  • \(b\) 中元素和为 \(n\);
  • 对于满足以下条件的序列 \(c\),\(c\) 中所有元素均相等。

条件:\(c\) 的长度为 \(n\),且对于前 \(a_1\) 个数组成的子数组回文,子数组 \([a_1+1,a_1+a_2]\),以此类推,子数组 \([(\sum_{i=1}^{i-1}a_i)+1,\sum_{i=1}^{i}a_i]\) 回文,同理,还需要 \([(\sum_{i=1}^{i-1}b_i)+1,\sum_{i=1}^{i}b_i]\) 回文。

\(1\le m\le 100,1\le n\le 10^5\)。

solution

题意等价于对于回文的子数组,对应相等的位置连边后整张图连通。

首先使图连通至少需要 \(n-1\) 条边,所以如果 \(a\) 中有大于 \(2\) 个奇数,则边数最多有 \(n-2\) 条,无解。

考虑一种构造方式:\(b_i=a_i-[i=1]+[i=m]\),意义是第一组 \(b\) 比 \(a\) 小 \(1\),中间相等,最后一组比 \(a_m\) 大 \(1\),发现这样元素之间的空隙正好错开(可以画个图理解一下),正好可以做到使图连通。

这样做的要求是:对于 \(i\ne 1\land i\ne m\),\(b_i\) 为偶数,否则无限制,所以将奇数移到前后两侧即可。

时间复杂度 \(O(m)\)。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,cnt;
int a[N];

int main() {
	read(m);read(n);
	f(i,1,n) read(a[i]),cnt+=(a[i]&1);
	if (n==1) {
		if (m==1) return puts("1\n1\n1"),0;
		printf("%d\n2\n%d %d",m,1,m-1);
		return 0;
	}
	if (cnt>2) return puts("Impossible"),0;
	f(i,1,n) if (a[i]&1) {
		if (a[1]&1) swap(a[i],a[n]);
		else swap(a[i],a[1]);
	}
	f(i,1,n) printf("%d ",a[i]);
	putchar(10);
	printf("%d\n",n-(a[1]==1));
	f(i,1,n) {
		if (a[i]==1&&i==1) continue;
		printf("%d ",a[i]-(i==1)+(i==n));
	}
	return 0;
}

9.8

Matrix Elimination

题意

给定一个 \(n\times m\) 的矩阵 \(v\),定义一个格子 \((x,y)\) 为峰值格当且仅当这个格子上的值不小于 \(x\) 行其他格子与 \(y\) 列其他格子上的值之和,即满足:

\[v_{x,y} \ge \sum_{\substack{1 \le i \le n \\ i \neq x}} v_{i,y} + \sum_{\substack{1 \le j \le m \\ j \neq y}} v_{x,j} \]

每次操作可以对一个矩形内所有位置的值减 \(1\),求至少需要多少次操作才能使矩阵中至少有 \(k\) 个峰值格,无解输出 \(-1\)。

\(1\le k\le n\cdot m\le 10^5\)。

solution

令 \(a_{x,y}=v_{x,y} - \sum\limits_{\substack{1 \le i \le n \\ i \neq x}} v_{i,y} - \sum\limits_{\substack{1 \le j \le m \\ j \neq y}} v_{x,j}\),那么我们需要使矩阵 \(a\) 中至少有 \(k\) 个非负整数。

首先发现全局减 \(1\) 这个操作非常优秀,它可以使每个格子上的 \(a\) 值均加上 \(n+m-3\),不难发现当 \(n\ge 2\land m\ge 2\) 时,一次操作最多能使一个格子 \(a\) 值增加 \(n+m-3\),所以在 \(n\ge 2\land m\ge 2\) 时全局减 \(1\) 一定不劣,找到 \(a\) 中第 \(k\) 大的元素并判断它需要增加多少次才能变成非负整数即可。

\(n=1\land m=1\) 时,若这个格子上的元素为负则无解,否则操作数为 \(0\)。

那么只需要考虑 \(n=1\lor m=1\) 的情况,这两种情况处理方式相同,以 \(n=1\) 为例,矩阵退化为一个序列,所以下文中将这个矩阵当成序列考虑。

考虑 \(n=1\) 时全局减 \(1\) 为什么不优,原因是我们可以对 \([2,m]\) 内减去 \(1\),这样第一个位置 \(a\) 值增加了 \(m-1\),其他位置 \(a\) 值增加了 \(m-3\),第一个位置增加的值大于 \(n+m-3=m-2\),但是我们发现,我们能够进行的操作其实只有对 \([1,m],[2,m],[1,m-1]\) 这三个区间中的一个区间进行减 \(1\),其他区间能够对每个位置造成 \(a\) 值增加量显然不会超过 \(m-2\),一定不优于全局减 \(1\)。

考虑进行 \([2,m]\) 的减 \(1\) 带来的影响,它使第一个位置的 \(a\) 值增加了 \(m-1\),其他位置增加 \(m-3\),其他位置增加的值比全局减 \(1\) 的更少,所以这个操作实际上是为了更多地对第一个位置 \(a\) 值进行增加,那么这个操作的意义就是为了让第一个位置成为峰值格。

那么令初始时第一个位置 \(a\) 值为 \(x\),其他位置 \(a\) 值的第 \(k-1\) 小值为 \(y\),我们需要将 \(x,y\) 都变为非负整数,能进行的操作有:

  • \(x\leftarrow x+m-1,y\leftarrow y+m-3\);
  • \(x\leftarrow x+m-2,y\leftarrow y+m-2\)。

若 \(x>y\) 则一定不会进行第一种操作,否则一定会进行若干次第一种操作,使得操作后 \(x+m-1>y+m-3\),意思是原先将两个数变成非负整数的瓶颈由 \(x\) 造成,所以尽量多地使用 \(1\) 操作,让 \(x\) 增加的速度更快,但是需要保证操作后瓶颈仍由 \(x\) 造成。

令 \(t=\min(\left\lfloor \frac{y-x}{2} \right\rfloor ,\left\lceil \frac{-x}{m-1} \right\rceil)\),那么进行 \(t\) 次操作 \(1\) 后进行操作 \(2\) 直到 \(x,y\) 均变成非负整数即可。

对 \(x\) 为最后一个位置的 \(a\) 值,\(y\) 为其他位置 \(a\) 值的第 \(k-1\) 小值,进行操作 \([1,m-1]\) 同理。

特殊处理 \(m=2\) 和 \(k=1\) 的情况。

时间复杂度 \(O(nm)\)。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)

inline void read(int &x) {
	x=0;int f=1;
	char ch=getchar();
	while (ch<48) {if(ch=='-')f=-1;ch=getchar();} 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();x*=f;
}

inline ll ceil(ll x,ll y) {return x/y+(x%y!=0);}
inline ll solve(ll x,ll y,int m) {
	if (x<y) return ceil(y,m-2);
	if (y<0) return ceil(x,m-1);
	ll k=min((x-y)/2,ceil(x,m-1));
	x-=k*(m-1);y-=k*(m-3);
	if (x<=0&&y<=0) return k;
	return k+ceil(max(x,y),m-2);
}

inline ll solve(vector<ll> w,int m,int k) {
	w.erase(w.begin());w.pop_back();
	if (k==1) return ceil(min(-w.front(),-w.back()),m-1);
	if (m==2) return max(w.front(),w.back());
	vector<ll> w1=w,w2=w;k--;
	w1.erase(w1.begin());
	w2.pop_back();
	nth_element(w1.begin(),w1.end()-k,w1.end());
	nth_element(w2.begin(),w2.end()-k,w2.end());
	return min(solve(-w.front(),-(*(w1.end()-k)),m),solve(-w.back(),-(*(w2.end()-k)),m));
}

inline void solve() {
	int n,m,k;read(n);read(m);read(k);
	if (m==1) swap(n,m);
	vector<vector<int>> a(n+2,vector<int>(m+2,0));
	f(i,1,n) f(j,1,m) read(a[i][j]);
	if (n==1&&m==1) return puts(a[1][1]>=0?"0":"-1"),void();
	vector<vector<ll>> v(n+2,vector<ll>(m+2,0));
	f(i,1,n) {
		ll sum=0;
		f(j,1,m) sum+=a[i][j];
		f(j,1,m) v[i][j]+=sum;
	}
	f(j,1,m) {
		ll sum=0;
		f(i,1,n) sum+=a[i][j];
		f(i,1,n) v[i][j]+=sum;
	}
	vector<ll> w;
	f(i,1,n) f(j,1,m) w.push_back(v[i][j]=(a[i][j]*3ll-v[i][j]));
	nth_element(w.begin(),w.end()-k,w.end());
	ll ans=1e18;
	if (*(w.end()-k)>=0) return puts("0"),void();
	if (n+m>3) ans=ceil(-*(w.end()-k),n+m-3);
	if (n==1) ans=min(ans,solve(v[1],m,k));
	printf("%lld\n",ans);
}

int main() {
	int T;
	read(T);
	while (T--) solve();
	return 0;
}

Leftmost Ball

题意

你有 \(n\) 种颜色,每种颜色有 \(k\) 个球,你需要将其放入一个 \(n\times k\) 的序列中,放入后,每种颜色的第一个球都会变成与之前所有颜色不相同的一种颜色 \(0\),你需要求出最终有多少种可能的颜色序列。

\(1\le n,k\le 2000\)。

solution

我好糖这个题想了一万年都没做出来。

设 \(f_{i,j}\) 为放了 \(i\) 个 \(0\) 颜色的球和 \(j\) 中颜色的球得到序列的方案数(有些位置可能为空),显然要求 \(i\ge j\)。

考虑第一个没有放球的位置,这个位置可以放颜色为 \(0\) 的球,也可以从 \(n-j+1\) 种颜色中选择一种颜色,将这种颜色的第一个球放到这个位置,剩下的球在后面的空位随便放,那么放后面的球的方案数为 \(\binom{n\times k-(j-1)\times (k-1)-i-1}{k-2}\),所以转移:

\[f_{i,j}\leftarrow f_{i-1}{j} \]

\[f_{i,j}\leftarrow f_{i}{j-1}\times (n-j+1)\times \binom{n\times k-(j-1)\times (k-1)-i-1}{k-2} \]

为什么我没有做出来呢?应该是因为没有想到只需要考虑第一个没有放球的位置就可以解决前缀中 \(0\) 的个数大于其他颜色数的限制吧,也可能是因为我 dp 水平堪比我旁边的一根有线耳机吧。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2000+10;
constexpr int inf=1e9+10;

constexpr int mod=1e9+7;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline ll qpow(ll a,int b) {
	ll res=1;
	while (b) {
		if (b&1) res=res*a%mod;
		a=a*a%mod;b>>=1;
	}
	return res;
}

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

ll fac[N*N],inv[N*N];
inline void init(int n) {
	fac[0]=inv[0]=1;
	f(i,1,n) fac[i]=fac[i-1]*i%mod;
	inv[n]=qpow(fac[n],mod-2);
	F(i,n-1,1) inv[i]=inv[i+1]*(i+1)%mod;
}

inline ll C(int n,int m) {
	if (n<m||n<0||m<0) return 0;
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}

int n,k;
int f[N][N];

int main() {
	read(n);read(k);init(n*k);
	if (k==1) return puts("1"),0;
	f[0][0]=1;
	f(i,1,n) f(j,0,i) {
		add(f[i][j],f[i-1][j]);
		if (j) add(f[i][j],1ll*f[i][j-1]*(n-j+1)%mod*C(n*k-(j-1)*(k-1)-i-1,k-2)%mod);
	}
	printf("%d\n",f[n][n]);
	return 0;
}

9.9

Many Easy Problems

solution

算好做的多项式。

首先对于一个固定的 \(k\),考虑每个节点,这个节点不在虚树上当且仅当选择的所有点在这个点的同一侧,也就是考虑删去这个点形成若干连通分量的大小,令 \(sz_v\) 表示与它相邻节点 \(v\) 所在连通分量大小,那么这个点对 \(k\) 的贡献为 \(\binom{n}{k}-\sum \binom{sz_v}{k}\),令 \(t_i\) 为考虑所有点,删去这个点后得到若干连通分量大小为 \(i\) 的数量和,\(f_i\) 为 \(i\) 对应答案,那么有:

\[f_i=n\times \binom{n}{i} - \sum_{j=i}^{n} t_j\times \binom{j}{i} \]

拆一下组合数并简化式子,可以得到:

\[f_i=n\times \binom{n}{i} - \frac{1}{i!} \times \sum_{j=i}^{n} \frac{t_j\times j!}{(j-i)!} \]

令 \(F(x)=\sum\limits_{i=1}^{n} t_i i! x^i\),\(G(x)=\sum\limits_{i=1}^{n} \frac{1}{(n-i)!} x^i\),\(H(x)=F(x)\cdot G(x)\),那么 \(f_i\) 即为 \(H(x)\) 的 \(x^{i+n}\) 系数。

NTT 即可,时间复杂度 \(O(n\log n)\)。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;
constexpr int inf=1e9+10;

namespace Poly {
	constexpr int N=4e6+10;
	constexpr double pi=acos(-1.0);
	constexpr int mod=924844033,gen=5;
	
	inline ll qpow(ll a,int b) {
		ll res=1;
		while (b) {
			if (b&1) res=res*a%mod;
			a=a*a%mod;b>>=1;
		}
		return res;
	}
	
	static int len,rev[N];
	inline void initrev() {
		const int lt=log(len)/log(2)-1;
		f(i,1,len-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<lt));
	}
	
	static int g[30],flag_init_gen;
	inline void init_gen() {f(i,1,21) g[i]=qpow(gen,(mod-1)>>i);}
	inline int ad(int x,int y) {return (x+y>=mod)?(x+y-mod):(x+y);}
	inline void add(int &x,int y) {x=ad(x,y);}
	
	inline void NTT(int *a,int type) {
		if (!flag_init_gen) init_gen(),flag_init_gen=1;
		f(i,1,len-1) if (rev[i]<i) swap(a[i],a[rev[i]]);
		for (int mid=1,t=1;mid<len;mid<<=1,t++) {
			int Wn=g[t];
			for (int i=mid<<1,j=0;j<len;j+=i) {
				int w=1;
				for (int k=0;k<mid;k++,w=(ll)(w)*Wn%mod) {
					int x=a[j+k],y=(ll)(w)*a[j+k+mid]%mod;
					a[j+k]=ad(x,y);a[j+k+mid]=ad(x,mod-y);
				}
			}
		}
		if (type==-1) {
			reverse(a+1,a+len);
			const ll invn=qpow(len,mod-2);
			f(i,0,len-1) a[i]=invn*a[i]%mod;
		}
	}
	
	inline void poly_px(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=(ll)(f[i])*g[i]%mod;} 
	inline void poly_cpy(int *f,int *g,int len_mx) {for(int i=0;i<len_mx;i++) f[i]=g[i];}
	inline void poly_clr(int *f,int l,int r) {f(i,l,r) f[i]=0;}
	
	inline void NTT_mul(int *f,int *g,int *ret,int lenf,int leng) {
		static int X0[N],Y0[N];
		for (len=1;len<lenf+leng;len<<=1);
		initrev();
		poly_cpy(X0,f,lenf);
		poly_cpy(Y0,g,leng);
		NTT(X0,1);NTT(Y0,1);poly_px(X0,Y0,len);NTT(X0,-1);
		f(i,0,lenf+leng) ret[i]=X0[i];
		f(i,0,len) X0[i]=Y0[i]=0;
	}
}

constexpr int mod=924844033;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline ll qpow(ll a,int b) {
	ll res=1;
	while (b) {
		if (b&1) res=res*a%mod;
		a=a*a%mod;b>>=1;
	}
	return res;
}

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

ll fac[N],inv[N];
inline void init(int n) {
	fac[0]=inv[0]=1;
	f(i,1,n) fac[i]=fac[i-1]*i%mod;
	inv[n]=qpow(fac[n],mod-2);
	F(i,n-1,1) inv[i]=inv[i+1]*(i+1)%mod;
}

inline ll C(int n,int m) {
	if (n<m||n<0||m<0) return 0;
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}

int n;
int t[N],a[N],b[N],ans[N];
vector<int> e[N];

inline int dfs(int now,int fa) {
	int sz=1;
	for (int v:e[now]) if (v^fa) {
		int k=dfs(v,now);
		t[k]++;sz+=k;
	}
	if (fa) t[n-sz]++;
	return sz;
}

int main() {
	read(n);init(n);
	f(i,2,n) {
		int u,v;read(u);read(v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1,0);
	f(i,1,n) a[i]=t[i]*fac[i]%mod;
	f(i,1,n) b[i]=inv[n-i];
	Poly::NTT_mul(a,b,ans,n+1,n+1);
	f(i,1,n) printf("%d\n",(int)(((n*C(n,i)-inv[i]*ans[i+n])%mod+mod)%mod));
	return 0;
}

Blackout

solution

图论好题。

考虑每个点 \((x,y)\) 表示 \(x\) 向 \(y\) 连一条边,若 \(x\rightarrow y,y\rightarrow z\) 均有边,则 \(z\) 向 \(x\) 连一条边,求最后边数。

考虑每个弱连通分量,将当前分量视为新的一张图,答案为每个弱连通分量边数和。

如果这个图中存在二元环或自环,则这个图一定可以变成完全图,证明可以从在完全图上加一个点,不难发现一定可以变成点数加 \(1\) 的完全图。

考虑四元环和五元环,不难发现它们也可以构造出一个二元环,这样就说明了存在四元环和五元环的图是可以构成完全图的。

若一张图存在 \(k\) 元环,不难发现考虑首尾的关系可以构造出一个 \((k-3)\) 元环,也就说明一张存在点数不为 \(3\) 的倍数的环一定可以形成完全图。

考虑对图进行三染色,染色失败则这张图可以形成完全图,否则如果没有出现三种颜色,则这个图不能增加任意一条边,否则令原来的边为 \(0\rightarrow 1,1\rightarrow 2,2\rightarrow 0\),那么所有颜色为 \(2\) 的点都能向颜色为 \(0\) 的点连边,同理 \(0\) 能向 \(1\) 连边,\(1\) 能向 \(2\) 连边,可以通过在三元环上递归构造证明,那么答案就显而易见了。

这道题的关键在于图论的转化以及对 \(k\) 元环性质的发现,还是挺有意思的。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,flag,siz,Eg;
int c[N],t[4];
vector<int> e[N],E[N];
bitset<N> vis;

inline void dfs(int now,int d) {	
	t[d]++;c[now]=d;
	vis.set(now);siz++;
	for (int v:e[now]) {
		Eg++;
		if (!vis[v]) dfs(v,(d+1)%3);
		else flag&=(c[v]==(d+1)%3);
	}
	for (int v:E[now]) {
		Eg++;
		if (!vis[v]) dfs(v,(d+2)%3);
		else flag&=(c[v]==(d+2)%3);
	}
}

int main() {
	read(n);read(m);
	f(i,1,m) {
		int u,v;read(u);read(v);
		e[u].push_back(v);
		E[v].push_back(u);
	}
	ll ans=0;
	f(i,1,n) if (!vis[i]) {
		flag=1;siz=Eg=0;dfs(i,0);
		if (!flag) ans+=1ll*siz*siz;
		else if (t[0]&&t[1]&&t[2]) ans+=1ll*t[0]*t[1]+1ll*t[1]*t[2]+1ll*t[2]*t[0];
		else ans+=Eg/2;
		t[0]=t[1]=t[2]=0;
	}
	printf("%lld\n",ans);
	return 0;
}

Rotate 3x3

solution

好题,终于有一个我会做的 E 了。

不难发现无论怎么旋转只会将不同列交换并且使列内的数颠倒,所以列内的数不连续或中间的数是同列的最值的情况无解,于是令 \(a_i\) 为第 \(i\) 列对应了原先的第几列,\(b_i\) 表示第 \(i\) 列顺序是否颠倒。

每次操作等价于选择 \(1<i<n\),交换 \(a_{i-1},a_{i+1}\) 和 \(b_{i-1},b_{i+1}\),并同时对 \(b_{i-1},b_i,b_{i+1}\) 取反,要使最后 \(a\) 序列升序并使 \(b\) 序列全为 \(0\)。

将 \(i\) 奇数时 \(a_i\) 构成的序列称为奇序列,\(i\) 为偶数时 \(a_i\) 构成的序列称为偶序列,那么每次交换 \(a\) 值只会在奇序列内部或偶序列内部交换,所以奇序列中出现偶数或偶序列中出现奇数显然无解。

发现每次操作不变量是 奇序列逆序对数与偶序列颠倒列数之和的奇偶性 和 偶序列逆序对数与奇序列颠倒列数之和的奇偶性(每次操作必然使一个序列逆序对奇偶性改变,另一个序列颠倒列数奇偶性改变),所以判断初始时这两个量是否都为 \(0\) 即可。

若均为 \(0\) 则必然有解,至于构造笔者并未细想,难度应该不大。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n;
int a[N],b[N];
int mp[N][3];

inline pair<int,int> check(int x,int y,int z) {
	int mx=max({x,y,z}),mn=min({x,y,z});
	if (mx-mn!=2) return make_pair(-1,0);
	if ((mx^mn^x^y^z)!=y) return make_pair(-1,0);
	return make_pair(mx/3,mn!=x);
}

struct BIT {
	int v[N];
	inline void add(int p,int k) {for(;p<=n;p+=(p&-p))v[p]+=k;}
	inline int qry(int p) {int r=0;for(;p;p-=(p&-p))r+=v[p];return r;}
}tr1,tr2;

int main() {
	read(n);
	f(j,0,2) f(i,1,n) read(mp[i][j]);
	f(i,1,n) {
		pair<int,int> p=check(mp[i][0],mp[i][1],mp[i][2]);
		a[i]=p.first,b[i]=p.second;
		if (a[i]==-1||(a[i]&1)!=(i&1)) return puts("No"),0;
	}
	int v1=0,v2=0,r1=0,r2=0;
	for (int i=n;i>=1;i-=2) {
		int res=tr1.qry(a[i]);
		v1^=(res&1);r1^=b[i];
		tr1.add(a[i],1);
	}
	for (int i=n-1;i>=1;i-=2) {
		int res=tr2.qry(a[i]);
		v2^=(res&1);r2^=b[i];
		tr2.add(a[i],1);
	}
	puts(((r1^v2)||(r2^v1))?"No":"Yes");
	return 0;
}

Anticube

solution

我脑子不好使但是暴力过了。

暴力分解质因数,时间复杂度 \(O(\frac{n\sqrt V}{\log V})\)。

每个数的质因数与其出现次数模 \(3\) 意义下的值存到一个 map 中,并存入模 \(3\) 意义下次数的相反数对应 map,最后两个 map 中只能选一个,考虑哪个 map 对应了更多元素即可。

特殊处理本身就是立方数的数。

时间复杂度 \(O(\frac{n\sqrt V}{\log V}+n\omega(V)\log n)\)。

其实只需要分解 \(\sqrt[3]{V}\) 内的质因数即可,其他因子不会造成影响。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
#include<cmath>
#include<map>
#include<set>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

inline void readl(ll &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

vector<int> P;
inline void init(int n) {
	bitset<N> vis;
	f(i,2,n) {
		if (vis[i]) continue;
		P.push_back(i);
		for (int j=i;j<=n;j+=i) vis.set(j);
	}
}

inline pair<map<ll,int>,map<ll,int>> solve(ll x) {
	vector<ll> tmp;
	for (int p:P) {
		while (x%p==0) tmp.push_back(p),x/=p;
		if (1ll*p*p>x) break;
	}
	if (x!=1) tmp.push_back(x);
	map<ll,int> ret1,ret2;
	for (int L=tmp.size(),i=0;i<L;i++) {
		if (i+2<L&&tmp[i]==tmp[i+1]&&tmp[i]==tmp[i+2]) {i=i+2;continue;}
		ret1[tmp[i]]++;
	}
	for (pair<ll,int> p:ret1) ret2[p.first]=3-p.second;
	return make_pair(ret1,ret2);
}

int n,ans,flag=1;
map<map<ll,int>,int> mp;
set<map<ll,int>> vis;
map<map<ll,int>,map<ll,int>> inv;

int main() {
	read(n);init(1e5);
	f(i,1,n) {
		ll x;readl(x);
		ll k=(ll)(pow(x,1.0/3)+0.5);
		if (k*k*k==x) {ans+=flag;flag=0;continue;}
		k++;
		if (k*k*k==x) {ans+=flag;flag=0;continue;}
		k-=2;
		if (k*k*k==x) {ans+=flag;flag=0;continue;}
		pair<map<ll,int>,map<ll,int>> p=solve(x);
		mp[p.first]++;vis.insert(p.first);
		inv[p.first]=p.second;
	}
	while (!vis.empty()) {
		map<ll,int> now=(*vis.begin());
		ans+=max(mp[now],mp[inv[now]]);
		vis.erase(now);
		vis.erase(inv[now]);
	}
	printf("%d\n",ans);
	return 0;
}

9.10

幸福刑( Love and Punishment) - LonePi feat.初音ミク

数论题

给你 \(n\) 个数 \((a_1, a_2, \dots, a_n)\)。求

\[\sum_{i=1}^{n} \sum_{j=i}^{n} \varphi(a_i + a_j) \]

\(1\le n\le 5000,1\le a_i\le 10^9\)。

solution

考虑每个不超过 \(\sqrt V\) 的质数 \(p\),按 \(a_i \bmod p\) 分类,即可对于每个 \(i\) 快速找到 \(p \mid (a_i+a_j)\) 的 \(j\),并每次除掉因子 \(p\)。

时间复杂度 \(O(n^2 \log V+\sqrt V+ \frac{n\sqrt V}{\log V})\)。

Code
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e3+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n;
int a[N],b[N*N],c[N*N];
bitset<N*10> vis;
vector<int> tmp[N*10];

inline int id(int x,int y) {return x*n+y;}
inline void solve(int p) {
	f(i,1,n) tmp[a[i]%p].push_back(i);
	f(i,1,n) {
		const int val=(p-a[i]%p)%p;
		for (int j:tmp[val]) if (j>=i) {
			c[id(i,j)]/=p;
			c[id(i,j)]*=(p-1);
			while (b[id(i,j)]%p==0) b[id(i,j)]/=p;
		}
	}
	f(i,1,n) tmp[a[i]%p].clear();
}

int main() {
	read(n);
	f(i,1,n) read(a[i]);
	f(i,1,n) f(j,i,n) b[id(i,j)]=c[id(i,j)]=a[i]+a[j];
	for (int i=2,V=2e9;i*i<=V;i++) {
		if (vis[i]) continue;
		solve(i);
		for (int j=i;1ll*j*j<=V;j+=i) vis.set(j);
	}
	ll ans=0;
	f(i,1,n) f(j,i,n) {
		if (b[id(i,j)]!=1) {
			c[id(i,j)]/=b[id(i,j)];
			c[id(i,j)]*=(b[id(i,j)]-1);
		}
		ans+=c[id(i,j)];
	}
	printf("%lld\n",ans);
	return 0;
}

갑천 자전거길

题意

给定 \(n\) 条线段,第 \(i\) 条线段两个端点分别为 \((0,a_i)\) 和 \((T,b_i)\),保证每条线段最多只与 \(k\) 条不同线段相交,且 \(a_i,b_i\) 两两不同,\(q\) 次询问,每次询问给定 \((c,d)\),求每条线段在 \(x=c\) 处 \(y\) 坐标与 \(d\) 最小距离为多少。

\(1\le n\le 10^6,1\le q\le 10^4,1\le k\le 10^3\)。

solution

太可恶了被卡常了,把分数类换成整型快了三倍不止。

考虑在原来的线段集合中找出一个两两不交的极大的线段集合,按左端点排序纵坐标可以轻松做到,每次询问找到在点 \((c,d)\) 上面的第一条线段和下面的第一条线段,显然能够更新答案的线段必然与这两条线段有交,用数据结构找到有交的线段并更新答案即可。

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

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

struct udb {ll x,y;};
inline ll gcd(ll x,ll y) {return y?gcd(y,x%y):x;}
inline udb H(udb k) {ll d=gcd(k.x,k.y);return {k.x/d,k.y/d};}
inline udb bd(const ll x,const ll y) {return H({x,y});}

int n,T,K;
int mpL[N],vis[N];

struct Line {
	int l,r,vl;
	inline Line (int l=0,int r=0,int vl=0): l(l),r(r),vl(vl) {}
	inline bool fx_db(int x,int y) {return 1ll*(r-l)*x+1ll*l*T<=1ll*y*T;}
	inline ll fx_gt(int x) {return 1ll*(r-l)*x+1ll*l*T;}
}p[N];

inline bool cmp1(Line x,Line y) {return x.l<y.l;}
inline bool cmp2(Line x,Line y) {return x.r<y.r;}
inline ll absl(ll x) {return x<0?-x:x;}

struct Segment_Tree {
	vector<Line> tmp[N<<2];
	inline int ls(int x) {return x<<1;}
	inline int rs(int x) {return x<<1|1;}
	
	inline void insert(int x,int l,int r,int now,Line k) {
		tmp[now].push_back(k);
		if (l==r) return;
		int mid=l+r>>1;
		if (x<=mid) insert(x,l,mid,ls(now),k);
		else insert(x,mid+1,r,rs(now),k);
	}
	
	inline void solve1(int x,int y,int l,int r,int now,int kv,int kx,int ky,ll &ret) {
		if (x<=l&&y>=r) {
			for (int i=tmp[now].size()-1;i>=0;i--) {
				Line k=tmp[now][i];
				if (k.r<=kv) break;
				ret=min(ret,absl(k.fx_gt(kx)-1ll*ky*T));
			}
			return;
		}
		int mid=l+r>>1;
		if (x<=mid) solve1(x,y,l,mid,ls(now),kv,kx,ky,ret);
		if (y>mid) solve1(x,y,mid+1,r,rs(now),kv,kx,ky,ret);
	}
	
	inline void solve2(int x,int y,int l,int r,int now,int kv,int kx,int ky,ll &ret) {
		if (x<=l&&y>=r) {
			for (Line k:tmp[now]) {
				if (k.r>=kv) break;
				ret=min(ret,absl(k.fx_gt(kx)-1ll*ky*T));
			}
			return;
		}
		int mid=l+r>>1;
		if (x<=mid) solve2(x,y,l,mid,ls(now),kv,kx,ky,ret);
		if (y>mid) solve2(x,y,mid+1,r,rs(now),kv,kx,ky,ret);
	}
}tr;

Line S[N];int Stp;
inline pair<Line,Line> work(int x,int y) {
	int l=0,r=Stp,res=-1;
	while (l<=r) {
		int mid=l+r>>1;
		if (S[mid].fx_db(x,y)) res=mid,l=mid+1;
		else r=mid-1;
	}
	if (res==-1) return make_pair(S[0],Line());
	if (res==Stp) return make_pair(S[res],Line());
	return make_pair(S[res],S[res+1]);
}

inline void solve(Line k,ll &ret,int x,int y) {
	ret=min(ret,absl(k.fx_gt(x)-1ll*T*y));
	if (k.vl!=1) tr.solve1(1,k.vl-1,1,n,1,k.r,x,y,ret);
	if (k.vl!=n) tr.solve2(k.vl+1,n,1,n,1,k.r,x,y,ret);
}

inline udb query(int x,int y) {
	pair<Line,Line> p=work(x,y);
	ll ret=1e18;
	solve(p.first,ret,x,y);
	if (p.second.l) solve(p.second,ret,x,y);
	return H(bd(ret,T));
}

int main() {
	read(n);read(T);read(K);
	f(i,1,n) {
		int l,r;read(l);read(r);
		p[i]={l,r};mpL[i]=l;
	}
	sort(p+1,p+1+n,cmp1);
	sort(mpL+1,mpL+1+n);
	int lst_r=0;
	f(i,1,n) {
		p[i].vl=lower_bound(mpL+1,mpL+1+n,p[i].l)-mpL;
		if (p[i].r>lst_r) S[Stp++]=p[i],lst_r=p[i].r,vis[p[i].vl]=1;
	}Stp--;
	sort(p+1,p+1+n,cmp2);
	f(i,1,n) if (!vis[p[i].vl]) tr.insert(p[i].vl,1,n,1,p[i]);
	int m;read(m);
	while (m--) {
		int x,y;read(x);read(y);
		udb res=query(x,y);
		printf("%lld/%lld\n",res.x,res.y);
	}
	return 0;
}

Sequential operations on Sequence

solution

不难发现每次操作都会形成一个形如一个循环节重复多次再加上一段这个循环节的一段前缀的序列,那么用栈维护:循环节长度,循环节出现次数,剩余的前缀长度,当前序列长度,对于栈内相邻两个元素,上面的元素的循环节是下面的元素。

每次操作是简单的,若栈顶元素长度超过所需要长度就弹出栈顶(若栈顶为初始时的 $1,2,3,\dots $ 序列则更改这个序列的长度),然后以栈顶元素为循环节根据所需要长度生成对应元素推入栈即可。

操作完成后,我们需要求出栈顶元素中 \(i\) 出现了多少次,那么考虑维护:当前元素需要计入多少次,当前元素有长度为多少的前缀需要额外计入以及需要计入多少次,用堆维护所有的前缀,堆中存储的是“待处理的额外前缀”及其出现次数;每次取出最长的前缀,若其长度大于当前循环节长度,则将其中完整的循环节部分累加,剩余部分继续放回堆中,由于每次取模后前缀长度至少减半,所以每个前缀最多发生 \(O(\log V)\) 次拆分。

转化到初始时的 \(1,2,3,\dots\) 序列就可以利用差分计算每个前缀带来的贡献了。

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

Code
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void readl(ll &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,cnt;
struct Node{ll x,t,y,len;}p[N];
int st[N],top;

struct Pre {
	ll len,tim;
	inline bool operator <(const Pre &a)const {return len<a.len;}
};
ll d[N];

int main() {
	scanf("%d%d",&n,&m);
	p[++cnt]={n,1,0,n};
	st[++top]=cnt;
	while (m--) {
		ll len;readl(len);
		while (top!=1&&p[st[top]].len>=len) top--;
		if (top==1&&p[1].len>=len) p[st[top]]={len,1,0,len};
		else {
			Node k=p[st[top]];
			p[++cnt]={k.len,len/k.len,len%k.len,len};
			st[++top]=cnt;
		}
	}
	ll tim=1;
	priority_queue<Pre> q;
	F(c,top,2) {
		Node now=p[st[c]];
		if (now.y) q.push({now.y,tim});
		tim*=now.t;
		while (!q.empty()&&q.top().len>now.x) {
			Pre tp=q.top();
			q.pop();
			tim+=(tp.len/now.x)*tp.tim;
			if (tp.len%now.x) q.push({tp.len%now.x,tp.tim});
		}
	}
	while (!q.empty()) {
		Pre now=q.top();
		q.pop();
		tim+=now.tim;
		d[now.len+1]-=now.tim;
	}
	f(i,1,p[1].len) printf("%lld\n",tim+=d[i]);
	f(i,p[1].len+1,n) puts("0");
	return 0;
}

9.11

篱莘龙

solution

考虑维护将奶龙 \((a_1,b_1)\) 作为所有奶龙中攻击力最大的奶龙时,最多能够选择多少只奶龙。

考虑另一只奶龙 \((a_2,b_2)\),它需要满足 \(a_2<a_1<b_2\land a_2<b_1\) 才能和 \((a_1,b_1)\) 共存,又因为 \((a_1,b_1)\) 攻击力最大,所以它与其他选中的奶龙必然可以共存。

先考虑如何加入一只奶龙 \((a_2,b_2)\) 对原有的 \((a_1,b_1)\) 的贡献。

若 \(a_1<b_1\),则将这只奶龙挂到 \(a_1\) 上,考虑到奶龙 \((a_2,b_2)\) 时对 \([a_2,b_2]\) 区间加即可。

否则 \(b_1<a_1\),此时需要满足的是 \(a_2<b_1<a_1<b_2\),直接维护二维平面肯定不可行,由于只需要维护选择奶龙数量最大值,所以如果有 \(b_1<a_2<a_1<b_2\) 则 \((a_2,b_2)\) 一定优于 \((a_1,b_1)\),因为所有能够给 \((a_1,b_1)\) 带来贡献的奶龙必然能够给 \((a_2,b_2)\) 带来贡献,那么加入 \((a_2,b_2)\) 贡献时,找出所有满足 \(b_1<a_2<a_1<b_2\) 的奶龙 \((a_1,b_1)\) 并将其删除,不计入后续的贡献,那么将奶龙 \((a_1,b_1)\) 挂在 \(a_1\) 上,对 \([a_2,b_2]\) 区间加即可。

再考虑如何加入一只新的奶龙 \((a_1,b_1)\),记录每个位置被 \([a_2,b_2]\) 贡献的次数,挂到 \(a_1\) 上时激活这个位置的贡献即可,注意插入 \(b_1<a_1\) 的奶龙时需要考虑曾经是否有一只奶龙 \((a_2,b_2)\) 满足 \(b_1<a_2<a_1<b_2\),若有则不能加入。

整理一下,我们需要用线段树维护:已经激活的位置的贡献最大值,未激活位置的贡献,当前节点 \(b_1<a_1\) 的 \(b_1\) 最小值(用于删除 \(b_1<a_1\) 的奶龙),当前节点被 \((a_2,b_2)\) 覆盖中 \(a_2\) 的最大值(用来防止不能加入的 \(b_1<a_1\) 的奶龙被加入)。

每只奶龙最多被删除一次,时间复杂度 \(O(n\log n)\),代码挺好写的就是常数有点大。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e6+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

struct Segment_Tree1 {
	int mn[N<<2],mx1[N<<2],mx2[N<<2],mx3[N<<2],tg[N<<2];
	inline int ls(int x) {return x<<1;}
	inline int rs(int x) {return x<<1|1;}
	
	inline void build(int n) {f(i,1,(n<<2))mx1[i]=-inf,mn[i]=inf;}
	inline void modify(int x,int k) {mx1[x]+=k;mx2[x]+=k;tg[x]+=k;}
	
	inline void push_down(int x) {
		if (!tg[x]) return;
		int k=tg[x];tg[x]=0;
		modify(ls(x),k);
		modify(rs(x),k);
	}
	
	inline void update(int x,int y,int l,int r,int now) {
		if (x<=l&&y>=r) mx3[now]=max(mx3[now],x);
		if (x<=l&&y>=r&&mn[now]>x) return modify(now,1);
		if (l==r) return mx1[now]=-inf,mn[now]=inf,void();
		push_down(now);
		int mid=l+r>>1;
		if (x<=mid) update(x,y,l,mid,ls(now));
		if (y>mid) update(x,y,mid+1,r,rs(now));
		mx1[now]=max(mx1[ls(now)],mx1[rs(now)]);
	}
	
	inline void insert(int x,int l,int r,int now,int k) {
		if (k<mx3[now]) return;
		if (l==r) return mn[now]=k,mx1[now]=mx2[now]+1,void();
		push_down(now);
		int mid=l+r>>1;
		if (x<=mid) insert(x,l,mid,ls(now),k);
		else insert(x,mid+1,r,rs(now),k);
		mx1[now]=max(mx1[ls(now)],mx1[rs(now)]);
		mn[now]=min(mn[ls(now)],mn[rs(now)]);
	}
}tr1,tr2;

int main() {
	int n;read(n);read(n);
	tr1.build(n*2);
	tr2.build(n*2);
	const int L=1,R=n*2;
	f(i,1,n) {
		int a,b;read(a);read(b);
		if (a<b) {
			tr1.update(a+1,b,L,R,1);
			tr2.update(a,b,L,R,1);
			tr1.insert(a,L,R,1,inf);
		}
		else tr2.insert(a,L,R,1,b);
		printf("%d\n",max(tr1.mx1[1],tr2.mx1[1]));
	}
	return 0;
}

9.13

【MYCOI R1】那猫猫城的集市

solution

和 [省选联考 2021 A/B 卷] 宝石 没有本质区别。

点分治,每次处理跨过分治中心的询问,配合可撤销数组即可做到 \(O(n\log n)\)。

上面那个题的题解已经详细写过一遍了,偷个懒不写了。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m;
int a[N],b[N],ans[N],md[N];
vector<int> e[N];

int siz[N],wgh[N],rt,sum;
bitset<N> vis;

inline void get_rt(int now,int fa) {
	siz[now]=1;wgh[now]=0;
	for (int v:e[now]) {
		if (v==fa||vis[v]) continue;
		get_rt(v,now);
		siz[now]+=siz[v];
		wgh[now]=max(wgh[now],siz[v]);
	}
	wgh[now]=max(wgh[now],sum-siz[now]);
	if (wgh[now]<wgh[rt]) rt=now;
}

int col[N];
struct ques{int u,v,x;}p[N];
vector<int> st[N],ed[N],rem[N];

int mp[N],rk[N],cnt;
inline void add_mp(int x) {if (mp[x]) return;mp[x]=++cnt;rk[cnt]=x;}
inline void clear_mp() {f(i,1,cnt)mp[rk[i]]=0;cnt=0;}

inline void get_col(int now,int fa,int c) {
	col[now]=c;add_mp(a[now]);add_mp(b[now]);
	for (int &v:e[now]) if (v!=fa&&!vis[v]) get_col(v,now,c);
}

struct Ctrl_Z_Data {
	int val[N],st1[N],st2[N],top;
	inline void update(int x,int k) {st1[++top]=x;st2[top]=val[x];val[x]=k;}
	inline void remove() {int x=st1[top];val[x]=st2[top--];}
	inline int operator [] (const int &x)const {return val[x];}
}f,g;

inline void solve_st(int now,int fa) {
	int x=mp[a[now]],y=mp[b[now]],v1=f[x],v2=f[y];
	f.update(x,v2);f.update(y,v1);
	for (int &id:st[now]) md[id]=f[mp[p[id].x]];
	for (int &v:e[now]) if (v!=fa&&!vis[v]) solve_st(v,now);
	f.remove();f.remove();
}

inline void solve_ed(int now,int fa) {
	if (fa) {
		int x=mp[a[now]],y=mp[b[now]],p1=g[x],p2=g[y];
		g.update(x,p2);g.update(y,p1);
		f.update(p1,y);f.update(p2,x);
	}
	for (int &id:ed[now]) ans[id]=rk[f[md[id]]];
	for (int &v:e[now]) if (v!=fa&&!vis[v]) solve_ed(v,now);
	if (fa) {
		f.remove(),f.remove();
		g.remove(),g.remove();
	}
}

inline void calc(int now,vector<int> &tmp) {
	if (tmp.empty()) return clear_mp();
	for (int id:tmp) {
		int x=p[id].x,u=p[id].u,v=p[id].v;
		if (!mp[x]) ans[id]=x;
		else {
			st[u].push_back(id);
			ed[v].push_back(id);
		}
	}
	f(i,1,cnt) f.val[i]=g.val[i]=i;
	solve_st(now,0);
	solve_ed(now,0);
	clear_mp();
	for (int id:tmp) st[p[id].u].clear(),ed[p[id].v].clear();
	tmp.clear();
}

inline void solve(int now,vector<int> &tmp) {
	if (tmp.empty()) return;
	vis.set(now);
	vector<vector<int>> nxt;
	add_mp(a[now]);add_mp(b[now]);
	for (int v:e[now]) if (!vis[v]) get_col(v,now,v);
	vector<int> now_que;
	for (int id:tmp) {
		int x=p[id].x,u=p[id].u,v=p[id].v;
		if (col[u]!=col[v]||u==now||v==now) now_que.push_back(id);
		else rem[col[u]].push_back(id);
	}
	for (int v:e[now]) if (!vis[v]) {
		nxt.push_back(rem[v]);
		rem[v].clear();
	}
	calc(now,now_que);
	int idx=0;
	for (int v:e[now]) {
		if (vis[v]) continue;
		sum=siz[v];rt=0;
		get_rt(v,now);
		solve(rt,nxt[idx++]);
	}
}

int main() {
	read(n);read(m);
	f(i,1,n) read(a[i]);
	f(i,1,n) read(b[i]);
	f(i,2,n) {
		int u,v;read(u);read(v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	vector<int> st;
	f(i,1,m) {
		int u,v,x;
		read(u);read(v);read(x);
		p[i]={u,v,x};
		st.push_back(i);
	}
	wgh[0]=inf;sum=n;
	get_rt(1,0);solve(rt,st);
	f(i,1,m) printf("%d\n",ans[i]);
	return 0;
}
posted @ 2026-09-08 16:23  Ff472130  阅读(16)  评论(0)    收藏  举报