ZJOI2022 题解

ZJOI2022 部分题目题解

D1T1 [ZJOI2022] 树

题意

按照如下方式生成两棵树:

  • 第一棵树:节点 \(1\) 作为树的根,\(\forall i\in[2,n]\),从 \([1,i-1]\) 中选取一个点作为 \(i\) 的父亲。
  • 第二棵树:节点 \(n\) 作为树的根,\(\forall i\in[1,n-1]\),从 \([i+1,n]\) 中选取一个点作为 \(i\) 的父亲。

要求对于所有节点 \(i​\)\(i​\) 恰好是两棵树中的一个的叶子节点。一个节点被称为叶子当且仅当没有节点的父亲是它。两种方案不同当且仅当存在一棵树中的一个节点 \(i​\),两种方案中它的父亲不同。

对于 \(n\in[2,N]\),计算方案数 \(\bmod m\)

数据范围\(n\le 500\)

solution

\(f(S)\) 表示第一棵树中,叶子集合恰好为 \(S\) 的方案数,\(g(S)\) 表示第二棵树中,叶子集合恰好为 \(S\) 的方案数。为了方便容斥,设 \(f'(S)\) 表示第一棵树中,叶子集合为 \(S\) 的超集的方案数,\(g'(S)\) 表示第二棵树中,叶子集合为 \(S\) 的超集的方案数。

那么

\[f(S)=\sum_{S\subseteq s}(-1)^{|s|-|S|}f'(s)\\ g(S)=\sum_{S\subseteq s}(-1)^{|s|-|S|}g'(s)\\ \]

答案是(设 \(U\) 为全集 \(1\sim n\)

\[\begin{aligned} ans&=\sum_S f(S)g(U\setminus S)\\ &=\sum_S\left(\sum_{S\subseteq s}(-1)^{|s|-|S|}f'(s)\right)\left(\sum_{U\setminus S\subseteq t}(-1)^{|U\setminus S|-|t|}g'(t)\right)\\ &=\sum_S\left(\sum_{S\subseteq s}(-1)^{|s|-|S|}f'(s)\right)\left(\sum_{t\in S}(-1)^{|S|-|t|}g'(U\setminus t)\right)\\ &=\sum_{s}f'(s)\sum_{t\subseteq s}g’(U\setminus t)\sum_{t\subseteq S\subseteq s}(-1)^{|S|-|s|}(-1)^{|S|-|t|}\\ &=\sum_{s}f'(s)\sum_{t\subseteq s}g'(U\setminus t)(-2)^{|s|-|t|} \end{aligned} \]

最后一步转化是 \((-1)^{|S|-|s|}(-1)^{|S|-|t|}=(-1)^{|s|-|t|}\),合法的 \(S\)\(2^{|s|-|t|}\) 个。

考虑 DP 维护转移系数。\(i\)\(f'(s)\) 的贡献是 \(j\in[1,i-1],j\not \in s\)\(j\) 的数量,\(i\)\(g'(U\setminus s)\) 的贡献是 \(j\in[i+1,n],j\in s\)\(j\) 的数量。那么设 DP 状态 \(f_{i,x,y}\) 表示考虑完前 \(i\) 个点,已经放在 \(s\) 中的点的数量为 \(x\),钦定后面有 \(y\) 个点在 \(t\) 集合内的 \(\sum_{s}f'(s)\sum_{t\subseteq s}g'(U\setminus t)(-2)^{|s|-|t|}\)

转移有:

\[f_{i,x,y}\gets f_{i-1,x,y}\times x\times y\\ f_{i,x+1,y-1}\gets f_{i-1,x,y}\times x\times (y-1)\\ f_{i,x+1,y}\gets -2\times f_{i-1,x,y}\times x\times y \]

分别表示 不属于 \(s\)、属于 \(s\) 也属于 \(t\),只属于 \(s\) 不属于 \(t\)。空间可以滚动一下。

复杂度 \(O(n^3)\)

view code
#include <bits/stdc++.h>
using namespace std;
const int N=505;
int mod;
int f[2][N][N],n;
int main(){
	cin>>n>>mod;
	int pre=0,cur=1;
	for(int i=1;i<=n;++i)f[0][0][i]=1;
	for(int i=1;i<=n;pre^=1,cur^=1,++i){
		memset(f[cur],0,sizeof(f[cur]));
		int ans=0;
		for(int j=0;j<i;++j){
			for(int k=0;k<=n-i+1;++k){
				int l=(i==1)?1:i-1-j;
				f[cur][j][k]=(f[cur][j][k]+1ll*f[pre][j][k]*l*k)%mod;
				if(k==1)ans=(ans+1ll*f[pre][j][k]*l)%mod;
				if(i>1){
					if(k)f[cur][j+1][k-1]=(f[cur][j+1][k-1]+1ll*f[pre][j][k]*l*(k-1))%mod;
					f[cur][j+1][k]=(f[cur][j+1][k]-2ll*f[pre][j][k]*l*k)%mod;
				}
			}
		}
		if(i>1)printf("%d\n",(ans+mod)%mod);
	}
	return 0;
}

D1T2 [ZJOI2022] 众数

题意

给你序列 \(a_1,a_2,\cdots a_n\)。你可以进行一次操作,选择一个区间 \([l,r](1\le l\le r\le n)\) 和一个整数 \(k\),将区间 \([l,r]\) 的数 \(+k\)。询问操作之后众数的出现次数的最大值,并输出这个众数的所有可能取值。

数据范围\(T\) 组数据,\(T\le 20,2\le n\le 2\times 10^5,\sum n\le 5\times 10^5,1\le a_i\le 10^9,\) 所有 \(a_i\) 不全相等。

solution

我们最终的答案一定是选择一个有序对 \((x,y)\),然后选择一个区间,把这个区间内的 \(y\) 全变成 \(x\)(此时这个区间内的 \(x\) 会变成一个 \(\neq x\) 的数),让众数变成 \(x\)

考虑根号分治。

  • \((x,y)\)\(x\) 的出现次数 \(\ge \sqrt{n}\)\(y\) 的出现次数 \(\ge \sqrt n\)

枚举所有出现次数 \(\ge \sqrt{n}\)\(i\)。现在我们要对所有 \(j\) 求出以下两个东西:

  1. 原序列中,\(i\) 的权值为 \(1\)\(j\) 的权值为 \(-1\),其它位置权值为 \(0\) 的最大子段和(此时众数为 \(j\))。
  2. 原序列中,\(i\) 的权值为 \(-1\)\(j\) 的权值为 \(1\),其它位置权值为 \(0\) 的最大子段和(此时众数为 \(i\))。

这两个东西都可以 \(O(n)\) 扫一遍,维护每个不同的 \(j\) 的前缀和的最小值求出。

因为出现次数 \(\ge \sqrt{n}\) 的数的数量 \(\le \sqrt{n}\),这一部分的复杂度是 \(O(n\sqrt{n})\)

  • \((x,y)\)\(x,y\) 的出现次数均 \(\le \sqrt{n}\)

我们要求的还是:原序列中,\(i\) 的权值为 \(-1\)\(j\) 的权值为 \(1\),其它位置权值为 \(0\) 的最大子段和(此时众数为 \(j\))。

因为 \(i\) 的出现次数 \(\le \sqrt{n}\),我们找出 \(i\) 的所有区间(这样我们知道里面有多少个 \(-1\),这样的区间最多 \(O(n\sqrt{n})\) 个),现在的问题是 \(O(n\sqrt{n})\) 个区间的区间众数。

直接求区间众数没法快速求出,注意到这个区间众数一定 \(\le \sqrt{n}\)(因为 \(y\) 的出现次数 \(\le\sqrt{n}\)),我们暴力枚举这个区间众数是多少,设为 \(p\),那么我们对每个右端点 \(r\),双指针找到一个最靠右的左端点 \(l\),满足 \([l,r]\) 的区间众数为 \(p\)。我们就能回答所有右端点为 \(r\),左端点 \(\le l\) 的所有询问了。

这一部分的复杂度也是 \(O(n\sqrt{n})\)

总复杂度 \(O(n\sqrt{n})\)

view code
#include <bits/stdc++.h>
using namespace std;
inline int read(){
	int s=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
	return s*f;
}
const int N=2e5+5;
int a[N],b[N],n,B,T,mx[N],s[N],tcnt[N],tot;
vector<int> ans;
template<typename T>inline void Max(T&a,T b){if(a<b)a=b;}
template<typename T>inline void Min(T&a,T b){if(a>b)a=b;}
namespace sub1{
	int buc[N],t,s[N],mn[N],mn1[N];
	bool tag[N];
	inline void work(int c){
		t=0;
		for(int i=1;i<=tot;++i)tag[i]=1,mn[i]=0,mn1[i]=0,s[i]=0,buc[i]=i;
		t=tot;
		int cnt=0;
		for(int i=1;i<=n;++i){
			if(a[i]==c){
				for(int j=1;j<=t;++j)tag[buc[j]]=0;
				++cnt;t=0;
			}else{
				const int col=a[i];
				++s[col];
				if(!tag[col]){
					Min(mn[col],s[col]-1-cnt);
					tag[col]=1;
					buc[++t]=col;
				}
				Max(mx[col],cnt-s[col]+1-mn1[col]+tcnt[col]);
				Max(mx[c],s[col]-cnt-mn[col]+tcnt[c]);
				Min(mn[col],s[col]-cnt);
				Min(mn1[col],cnt-s[col]);
			}
		}
		for(int i=1;i<=tot;++i)if(i!=c)Max(mx[i],cnt-s[i]-mn1[i]+tcnt[i]);
	}
}
int curmx;
namespace sub2{
	vector<int> buc[N];
	int cnt[N],ccnt[N],app;
	inline void insert(int x){
		--ccnt[cnt[x]];
		++ccnt[++cnt[x]];
		Max(app,cnt[x]);
	}
	inline void del(int x){
		--ccnt[cnt[x]];
		++ccnt[--cnt[x]];
		if(!ccnt[app])--app;
	}
	int L[N],rk[N],L1[N];
	inline void work(){
		for(int i=B;i>=1&&i+B>=curmx;--i){
			int r=0,l=1;
			app=0;
			memset(cnt+1,0,tot<<2);
			memset(ccnt+1,0,n<<2);
			ccnt[0]=tot;
			while(r<=n&&app<i)
				insert(a[++r]);
			if(app<i)continue;
			del(a[r]);
			for(;r<=n;++r){
				insert(a[r]);
				while(app>=i)del(a[l++]);
				insert(a[--l]);
				if(r==n){
					for(int c=1;c<=tot;++c){
						if(tcnt[c]>=B)continue;
						while(L1[c]<=tcnt[c]&&buc[c][L1[c]]<=l){
							Max(mx[c],app-(tcnt[c]-L1[c])+tcnt[c]);
							Max(curmx,mx[c]);
							++L1[c];
						}
					}
					break;
				}
				int c=a[r+1];
				if(tcnt[c]>=B)continue;
				while(L[r+1]<rk[r+1]&&buc[c][L[r+1]]<=l){
					Max(mx[c],app-(rk[r+1]-L[r+1]-1)+tcnt[c]);
					Max(curmx,mx[c]);
					++L[r+1];
				}
			}
		}
	}
	inline void init(){
		for(int i=1;i<=tot;++i){
			buc[i].clear();
			buc[i].push_back(0);
			L1[i]=0;
		}
		for(int i=1;i<=n;++i)L[i]=0,buc[a[i]].push_back(i),rk[i]=buc[a[i]].size()-1;
	}
}
int main(){
	T=read();
	while(T--){
		n=read();
		for(int i=1;i<=n;++i)a[i]=read(),b[i]=a[i];
		tot=n;curmx=0;
		sort(b+1,b+1+tot);
		tot=unique(b+1,b+1+tot)-b-1;
		for(int i=1;i<=n;++i)
			a[i]=lower_bound(b+1,b+1+tot,a[i])-b;
		for(int i=1;i<=tot;++i)mx[i]=0,tcnt[i]=0;
		for(int i=1;i<=n;++i)++tcnt[a[i]];
		B=sqrt(n);
		for(int i=1;i<=tot;++i)if(tcnt[i]>=B)sub1::work(i);
		for(int i=1;i<=tot;++i)Max(curmx,mx[i]);
		sub2::init();
		sub2::work();
		int maxx=0;
		for(int i=1;i<=tot;++i)Max(maxx,mx[i]);
		printf("%d\n",maxx);
		for(int i=1;i<=tot;++i)
			if(mx[i]==maxx)printf("%d\n",b[i]);
	}
	return 0;
}

D2T2 [ZJOI2022] 计算几何

题意

(写不动简要题意了)

九条可怜是一个喜欢计算几何的女孩子,她画了一个特别的平面坐标系,其中 xx 轴正半轴与 yy 轴正半轴夹角为 \(60\) 度。

从中,她取出所有横纵坐标不全为偶数,且满足 \(-2 a + 1 \le x \le 2 a - 1\)\(-2 b + 1 \le y \le 2 b - 1\)\(-2 c + 1 \le x + y \le 2 c - 1\) 的整点。

可怜想将其中一些点染色,但相邻的点不能同时染色。具体地,对于点 \((x,y)\),它和 \((x, y + 1), (x, y - 1), (x + 1, y), (x - 1, y), (x + 1, y - 1), (x - 1, y + 1)\) 六个点相邻,可结合样例解释理解。

可怜想知道在这个规则下最多能将多少点染色,以及染最多点的染色方案数。由于后者值可能很大,对于染色方案数,你只需要输出对 998244353 取模后的结果。注意不需要将最多染色点数取模。

(其中 A,Q,H 三点是被删除的,不能被选择)

数据范围\(1\le a,b,c\le 10^6\)\(T\) 组数据,\(1\le T\le 10\)

solution

把所有横纵坐标都是偶数的点连出变成为 \(2\) 的等边三角形,容易发现,所有没被删除的点都是一个等边三角形某一边的中点。把两个有公共边的等边三角形连边,这条边就表示这个边上的中点选不选

原问题中,两个点不能相邻的条件相当于新的图上,两条被选择的边不能有公共点。即一个最大匹配计数。

容易发现,这个题转化之后与 [Cnoi2021]六边形战士。其中原文题的 \(A=\max(0,a+b-c),B=\max(0,a+c-b),C=\max(0,b+c-a)\)。ZJOI 这题还需要求最大匹配的大小,这个最大匹配是完美匹配,所以最大匹配是 \(A\times B+B\times C+A\times C\)

view code
#include <bits/stdc++.h>
using namespace std;
const int N=1e7+5,mod=998244353;
int T;
inline int Inv(int a){
	int ret=1,b=mod-2;
	for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)ret=1ll*ret*a%mod;
	return ret;
}
int fac[N],h[N];
inline int calc(int a,int b,int c){
	int n=a+b+c;
	return 1ll*h[a]*h[b]%mod*h[c]%mod*h[n]%mod*Inv(1ll*h[a+b]*h[b+c]%mod*h[a+c]%mod)%mod;
}
int a[100],b[100],c[100],mx;
inline void init(int n){
	fac[0]=1;h[0]=1;
	for(int i=1;i<=n;++i)fac[i]=1ll*fac[i-1]*i%mod,h[i]=1ll*h[i-1]*fac[i-1]%mod;
}
int main(){
	cin>>T;
	for(int i=1;i<=T;++i){
		cin>>a[i]>>b[i]>>c[i];
		mx=max(mx,a[i]+b[i]+c[i]);
	}
	init(mx);
	for(int i=1;i<=T;++i){
		a[i]=min(a[i],b[i]+c[i]);
		b[i]=min(b[i],a[i]+c[i]);
		c[i]=min(c[i],a[i]+b[i]);
		int a1=a[i]+b[i]-c[i],b1=a[i]+c[i]-b[i],c1=b[i]+c[i]-a[i];
		printf("%lld %d\n",1ll*a1*b1+1ll*b1*c1+1ll*a1*c1,calc(a1,b1,c1));
	}
	return 0;
}
posted @ 2022-05-10 20:51  harryzhr  阅读(271)  评论(0编辑  收藏  举报