2019.11.05【NOIP提高组】模拟 A 组

今天的比赛T1和T3是结论题,T2是大难题。

T1:首先(1,2)~(1,n)一定是按顺序放在1~n-1列的,然后我们考虑后面的怎样放。

其实结论就是按顺序枚举当前列是否能放,如果能就直接放就好了。

证明我也不会,据说比赛当场A的人是找规律的。

代码如下:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<bitset>
#include<ctime>
#define MAXN 1010
using namespace std;

bitset<MAXN>s[MAXN];
int a[MAXN][MAXN],len[MAXN],n;
int main()
{
//freopen("problem.in","r",stdin);
//freopen("problem.out","w",stdout);
int i,j,h=1;
scanf("%d",&n);
srand(time(0));
for(i=1;i<=n;i++)
	for(j=i+1;j<=n;j++)
	{
		while(s[i][h]==1||s[j][h]==1)
		{
			h++;
			if(h>n-1)h=1;
		}
		len[h]++;a[h][len[h]]=i;
		len[h]++;a[h][len[h]]=j;
		s[i][h]=1;s[j][h]=1;
		h++;if(h>n-1)h=1;
	}
for(i=1;i<=n-1;i++)
{
	for(j=1;j<=n;j++)printf("%d ",a[i][j]);
	printf("\n");
}
}

T2:这是一道计数难题。

  • 首先我们算恰好c个不方便,所以我们要转化成求至少c个。

转化方式如下:k^{c}=(k-1+1)^{c}=\sum \binom{c}{i}*(k-1)^{i}*1^{c-i}=\sum \binom{c}{i}*(k-1)^{i}

有上式看出任何一个恰好c个的方案都会对每一个恰好i个(i<=c)的方案产生(k-1)^i的贡献。所以我们只需将k变成k-1,然后在求每一个至少方案的贡献即可。

  • 设f[x][j]表示在以x为根的子树中,权值最小的好点的权值的相对大小为j的贡献。
  • 首先我们要把x的所有子树都合并起来。假设当前要合并x和y,那么枚举p、q,考虑f[x][p]和f[y][q]产生的贡献。

分类讨论p和q那个是合并之后的子树的全职最小的好点。假设是p,枚举一个k(k<q),表示y中1~k都插在p之前,k+1~size[y]都插在p之后,那么贡献就是\binom{k+p-1}{k}*\binom{size[y]-k+size[x]-p}{size[y]-k}*f[x][p]*f[y][q]

。q为最小时同理。

注意到这里我们没有考虑一棵子树内没有好点的情况,要把这种情况算上(即随便编号即可)。

  • 合并完x的子树之后,我们还需要讨论x的情况。

1、若x不是好点,则x的值一定要比第一个好点的值小,否则它会挡住第一个好点。这种情况的方程式是f1[i]+=f[x][i-1]*(i-1)

2、若x是好点,那么如果x的子孙中也有好点,则f1[i]+=f[x][p]*(k-1)(p>=i);如果x的子孙中没有好点,则f[x][i]+=(size[x]-1)!*(k-1)

  • 上述情况其实都很好推,最终计算时可以采用前缀和优化。
  • 最后ans=\sum f[1][i],不要忘记加上整棵子树都没有好点的情况,即n!。

贴一下未加前缀和优化的代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define ll long long
#define MAXN 5010
#define MAXM 10010
#define mod 998244353

struct map
{
	ll x;
	ll y;
};
map way[MAXM];
ll first[MAXN],nxt[MAXM],f[MAXN][MAXN],f1[MAXN],fa[MAXN],size[MAXN],jc[MAXN],b[MAXN],n,m;
ll bz[MAXN],kk,ans,p,q,k;
ll sqr(ll x,ll y)
{
	ll j=x,s=1,w=y;
	while(w>=1)
	{
		if(w%2==1)s=s*j%mod;
		j=j*j%mod;w/=2;
	}
	return s;
}
ll ny(ll x){return sqr(x,mod-2);}
ll C(ll n,ll m){return jc[n]*b[n-m]%mod*b[m]%mod;}
int dfs_pre(ll z)
{
	ll i;
	bz[z]=1;size[z]=1;
	for(i=first[z];i>=1&&i<=m;i=nxt[i])
		if(bz[way[i].y]==0){fa[way[i].y]=z;dfs_pre(way[i].y);size[z]+=size[way[i].y];}
}
int dfs(ll x)
{
	ll i,sx=0;
	for(i=first[x];i>=1&&i<=m;i=nxt[i])
		if(fa[way[i].y]==x)
		{
			dfs(way[i].y);
			if(sx==0)
			{
				for(p=1;p<=n;p++)f[x][p]=f[way[i].y][p];
				sx=size[way[i].y];
			}
			else
			{
				memset(f1,0,sizeof(f1));
				for(p=1;p<=sx;p++)
					for(q=1;q<=size[way[i].y];q++)
					{
						for(k=0;k<q;k++)
						{
							f1[p+k]=(f1[p+k]+C(k+p-1,k)
							*C(size[way[i].y]-k+sx-p,size[way[i].y]-k)%mod
							*f[x][p]%mod*f[way[i].y][q])%mod;
						}
						for(k=0;k<p;k++)
						{
							f1[q+k]=(f1[q+k]+C(k+q-1,k)
							*C(sx-k+size[way[i].y]-q,sx-k)%mod
							*f[x][p]%mod*f[way[i].y][q])%mod;
						}
					}
				for(p=1;p<=sx;p++)
					for(k=0;k<=size[way[i].y];k++)
					{
						f1[p+k]=(f1[p+k]+C(k+p-1,k)
						*C(size[way[i].y]-k+sx-p,size[way[i].y]-k)%mod
						*f[x][p]%mod*jc[size[way[i].y]])%mod;
					}
				for(q=1;q<=size[way[i].y];q++)
					for(k=0;k<=sx;k++)
					{
						f1[q+k]=(f1[q+k]+C(k+q-1,k)
						*C(sx-k+size[way[i].y]-q,sx-k)%mod
						*f[way[i].y][q]%mod*jc[sx])%mod;
					}
				sx+=size[way[i].y];
				for(p=1;p<=sx;p++)f[x][p]=f1[p];
			}
		}
	memset(f1,0,sizeof(f1));
	for(i=2;i<=size[x];i++)f1[i]=(f1[i]+f[x][i-1]*(i-1))%mod;
	for(i=1;i<=size[x];i++)
	{
		for(p=i;p<=size[x];p++)
		{
			f1[i]=(f1[i]+f[x][p]*(kk-1))%mod;
		}
		f1[i]=(f1[i]+jc[size[x]-1]*(kk-1))%mod;
	}
	for(i=1;i<=size[x];i++)f[x][i]=f1[i];
}
int main()
{
freopen("random.in","r",stdin);
freopen("random.out","w",stdout);
ll i,j;
scanf("%lld %lld",&n,&kk);
m=n-1;
for(i=1;i<=m;i++)
{
	scanf("%lld %lld",&way[i].x,&way[i].y);
	way[i+m].x=way[i].y;way[i+m].y=way[i].x;
}
m*=2;for(i=m;i>=1;i--)nxt[i]=first[way[i].x],first[way[i].x]=i;
dfs_pre(1);
jc[0]=1;b[0]=ny(jc[0]);
for(i=1;i<=n;i++)jc[i]=jc[i-1]*i%mod,b[i]=ny(jc[i]);
dfs(1);
for(i=1;i<=n;i++)ans=(ans+f[1][i])%mod;
ans=(ans+jc[n])%mod;
printf("%lld",ans);
}

T3:还是一道结论题。

首先我们发现如果一个序列是可以通过变换得到的话,那么它一定要满足两个条件:

1、假设a为0、b为1、c为2。那么最终序列的和与一开始序列的和在模3的意义下相等。这时因为每一次变换对整个序列的和在模3的意义下是没有影响的。

2、最终序列至少要有一对相邻的位置相等。(如果变换的至少一次的话)

那么现在就得出结论:满足上述两个条件的序列一定是可以通过变换得到的。这个可以通过数学归纳法来证(但是我不会)。

最终的程序就是首先特判掉n<=3以及整个序列都相同的情况,然后设f[i][j][k][l]表示最终序列到第i为,和在模3的意义下为j,前一位为k,是否有相邻的位置相同(l)的方案数。这是一个简单的dp,转移时可以枚举下一位填什么。

最终不要忘记判断是否需要加上一次也不变换的情况。

发布了149 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/chiyankuan/article/details/102924427