ZJOI2022选做

\(ZJOI2022\)

众数

发现并不存在\(poly(log(n))\)的做法,那么尝试\(n\sqrt n\)

套路的按照出现次数分组,分为大于\(\sqrt n\)和小于\(\sqrt n\)

然后分别维护,小数对小数的贡献为什么要算两次,我们贡献是一段区间众数\(-\)这个数出现次数,是因为貌似一边连续从开始一段颜色需要考虑

//根号分治比较显然,分块维护不是很可行 
//出现次数大于根号n,最多有根号n个,然后枚举哪些颜色变成这个颜色
//然后找一个合适的位置转移过去
//出现次数小于根号,枚举变化位置即可
//我们的贡献计算方式分为
//大于根号的向大于根号的贡献
//大于根号的向小于根号的贡献
//小于根号的向大于根号的贡献
//小于根号的向小于根号的贡献
//前三种都在第一次处理时解决
//考虑怎么贡献,我们可以枚举对别的的贡献
//那么我们每次暴力修改一段区间,更新内部的
//我们Max求的一直是,我们能最多多多少 
#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;
int a[MAXN],b[MAXN],t[MAXN],bc[MAXN],sum[MAXN],Max[MAXN],Lim,tot,n;
vector<int>poz[MAXN];
map<int,int>mp;
void solve()
{
	for(int i=1;i<=n;i++) sum[i]=0;
	for(int i=1;i<=n;i++)
	{
		if(t[a[i]]<=Lim)
		{
			
			int p=lower_bound(poz[a[i]].begin(),poz[a[i]].end(),i)-poz[a[i]].begin();
		    //我们这一步只需要求区间众数就好了
		    //贡献就是区间众数-本数字出现次数
//		    cout<<"now: "<<a[i]<<" "<<i<<" "<<p<<"\n";
			for(int j=p,l,r;j>=0;j--)
		    {
		    	if(j==0) l=1;
		    	else l=poz[a[i]][j-1]+1;
		    	r=poz[a[i]][j];
//		    	cout<<j<<" "<<poz[a[i]].size()<<" "<<l<<" "<<r<<"\n";
		    	Max[a[i]]=max(Max[a[i]],sum[l]-(p-j));
			    while(l<=r&&sum[r]<p-j+1) sum[r--]=p-j+1;
			}
		}
	}
}
void sol()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	mp.clear();tot=0;
	memset(t,0,sizeof(t));
	memset(bc,0,sizeof(bc));
	memset(Max,0,sizeof(Max));
	for(int i=1;i<=n;i++) poz[i].clear();
	sort(b+1,b+1+n);
	for(int i=1;i<=n;i++)
	{
		if(!mp[b[i]]) mp[b[i]]=++tot,bc[tot]=b[i];
//		t[mp[b[i]]]++;
//		poz[mp[b[i]]].push_back(i);
	}
	for(int i=1;i<=n;i++) a[i]=mp[a[i]],t[a[i]]++,poz[a[i]].push_back(i);
	Lim=sqrt(n);
	for(int i=1;i<=tot;i++)
	{
		if(t[i]>Lim)
		{
		   sum[0]=0;
		   for(int j=1;j<=n;j++) 
		   {
		   	   sum[j]=sum[j-1]+(int)(a[j]==i);
		   }
		   int l=0,s=0;
		   //我们其余的对当前贡献相当于一个最大子段和 
		   for(int j=1;j<=tot;j++)
		   {
		   	   int N=t[j],l=0,r,s=0;
		   	   for(int k=0;k<N;k++)
		   	   {
		   	   	   r=poz[j][k];
				   s=max(0,s-(sum[r]-sum[l]))+1;
		   	   	   Max[i]=max(Max[i],s);
		   	   	   l=r;
			   }
			   l=s=0;
			   for(int k=0;k<N;k++)
			   {
			   	   r=poz[j][k];
			   	   s=max(s,0)+sum[r]-sum[l];
			   	   Max[j]=max(Max[j],s--);
			   	   l=r;
			   }
		   }
		}
	}
	solve();
	reverse(a+1,a+1+n);
	for(int i=1;i<=tot;i++)
	{
		reverse(poz[i].begin(),poz[i].end());
		for(int j=0;j<poz[i].size();j++) poz[i][j]=n-poz[i][j]+1;
	}
	solve();
	int Ans=0;
	for(int i=1;i<=tot;i++)
	{
		Max[i]+=t[i];
		Ans=max(Ans,Max[i]);
	}
	cout<<Ans<<"\n";
	for(int i=1;i<=tot;i++)
	{
		if(Max[i]==Ans) cout<<bc[i]<<"\n";
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--) sol();
}

\(T2\)

计数题滚出OI

一道(对我)并不签到的(对大家而已的)签到题

首先我们可以枚举\(2^n\)的状态,表示第一个树的叶子集合,然后方案也比较好求

树的形态就是在保证每个点找一个前面的点然后把叶子不对的容斥出去,子集容斥就好了\(?\)

\(O(2^n n)\)的暴力直接枚举两树形态,转移时候直接把非叶子节点标记为\(1\)即可

然后发现合并的时候\(dp[n][x]=dp[n][x_{rev}]\)

就很\(nb\)了,意思就是,我们得到了两棵树状态确定之后,他们的情况数是相同的

我们就先确定一个树的状态

\(f(S)\)为恰好\(S\)不是叶子的方案数

\(g(S)\)为钦定除了\(S\)之外的点都是叶子(\(S\)之内可是可不是)的方案数

\(g(S)\)易得

\(g(S)=\Pi_{i=2}^n\sum_{j\in S}[j<i]\)

直接枚举前面的可以成为非叶子的去选

\(f(S)=\sum_{T\subset S,1\not\in T}(-1)^{|T|}g(S\backslash T)\)

我们要求\(\sum_S f^2(S)\)

需要拆开计算

\(\large\sum_S f^2(S)=\sum_S\sum_{T_1\in S}\sum_{T_2\in S}(-1)^{|T_1|+|T_2|}\large(\small \Pi_{i=2}^n\sum_{j\in S\backslash T_1}[j<i]\large)\large(\small \Pi_{i=2}^n\sum_{j\in S\backslash T_2}[j<i]\large)\)

化简一下

\(\sum_{S}\sum_{T_1\in S}\sum_{T_2\in S}(-1)^{|T_1|+|T_2|}\Pi_{i=2}^n (\sum_{j\in S\backslash T_1 [j<i]})(\sum_{j\in S\backslash T_1} [j<i])\)

到这就可以\(dp\)

具体的话,我们仅需要枚举我们当前的\(i\)在哪些里面做贡献就好了,把后面两个\(\sum\)压入状态

#include<bits/stdc++.h>
#define int long long
#define MAXN 505
using namespace std;
int dp[2][MAXN][MAXN];
int n,mod;
signed main()
{
	 scanf("%lld%lld",&n,&mod);
	 dp[0][1][1]=1;
	 for(int i=1;i<n;i++)
	 {
	 	 int now=i%2;
	 	 int pre=now^1;
	 	 memset(dp[now],0,sizeof(dp[now]));
		 for(int j=1;j<=i;j++)
	 	 {
	 	     for(int k=1;k<=i;k++)
			 {
			     int val=dp[pre][j][k]*j%mod*k%mod;
				 dp[now][j][k]+=2*val;
				 dp[now][j+1][k+1]+=val;
				 dp[now][j+1][k]-=val;
				 dp[now][j][k+1]-=val;
			 }	
	     }
	     int Ans=0;
	     for(int j=1;j<=i+1;j++)
	     {
	     	 for(int k=1;k<=i+1;k++)
	     	 {
	     	     (Ans+=dp[now][j][k])%=mod;	
			 }
		 }
		 cout<<(Ans+mod)%mod<<"\n";
	 }
}
posted @ 2022-05-24 22:03  Authentic_k  阅读(30)  评论(0编辑  收藏  举报