• 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;
    }
    
  • 相关阅读:
    每日总结
    SQLServer2008/2005 生成数据字典SQL语句
    python一些utils
    python快速展示图片
    面向对象编程
    Arrays类讲解 冒泡排序
    方法的定义、方法的调用及方法的重载
    .Net6 连接 redis
    Stream流的基本使用
    uniapp 页面之前通讯传值 义美
  • 原文地址:https://www.cnblogs.com/harryzhr/p/16255302.html
Copyright © 2020-2023  润新知