#21 CF830D & CF850D & CF896D

Singer House

题目描述

点此看题

解法

同时路径计数问题,本题可以和 这题对比起来理解。

基本方法都是一样的,首先考虑计数顺序应该是自底向上的树形 \(dp\),但是计数顺序却和我们考虑的状态——有向路径产生了冲突,因为按照这样的计数顺序,有向路径从某个点来看,可能就是若干个分散的有向链。

为了解决这样的冲突,我们在 \(dp\) 的过程中就需要维护一个有向链分散,合并的过程。这个过程的计数可以通过记录有向链的数量来实现,设 \(f_{n,k}\) 表示深度为 \(n\) 的子树内有 \(k\) 条有向链的方案数,转移:

  • 不选根节点:\(f_{n,k}\leftarrow f_{n-1,i}\cdot f_{n-1,k-i}\)
  • 让根节点成为单独的链:\(f_{n,k}\leftarrow f_{n-1,i}\cdot f_{n-1,k-i-1}\)
  • 让根节点拼接一条链,可以选择成为起点或者成为终点:\(f_{n,k}\leftarrow f_{n-1,i}\cdot f_{n-1,k-i}\cdot (2k)\)
  • 让根节点拼接两条链,方案是 \(A(k,2)\),因为有顺序:\(f_{n,k}\leftarrow f_{n-1,i}\cdot f_{n-1,k-i+1}\cdot (k+1)\cdot k\)

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

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 405;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,f[M][M];
int F(int n,int k)
{
	if(!k) return 1;
	if(n==1) return k==1;
	if(~f[n][k]) return f[n][k];
	int r=0;
	for(int i=0;i<=k;i++)
		r=(r+F(n-1,i)*F(n-1,k-i))%MOD;
	for(int i=0;i<k;i++)
		r=(r+F(n-1,i)*F(n-1,k-i-1))%MOD;
	for(int i=0;i<=k;i++)
		r=(r+2*k*F(n-1,i)%MOD*F(n-1,k-i))%MOD;
	for(int i=0;i<=k+1;i++)
		r=(r+k*(k+1)*F(n-1,i)%MOD*F(n-1,k-i+1))%MOD;
	return f[n][k]=r;
}
signed main()
{
	n=read();
	memset(f,-1,sizeof f);
	printf("%d\n",F(n,1));
}

Tournament Construction

题目描述

点此看题

解法

以前学了个假的兰道定理,但是发现解决这个题是完全足够的。

真的兰道定理:我们把出度序列从小到大排序 \(d_1,d_2...d_n\),那么这个出度序列对应到竞赛图的充要条件是:

\[\forall k\in[1,n],\sum_{i=1}^k d_i\geq{k\choose 2} \]

可以类似 \(\tt Hall\) 定理来理解,它的本质就是对于每个子集,出度和不能小于其导出子图的度数和。

回到本题,首先考虑判断有无解,显然可以那个背包来做。设 \(dp[i][j][k]\) 表示考虑了出度集合的前 \(i\) 种,已经选出了 \(j\) 个点,它们出度和是 \(k\) 是否合法,转移时只需要时刻保证 \(j\geq i,k\geq{j\choose 2}\) 即可。

构造答案有一种 \(\tt naive\) 的方法,我们取出当前最小的点 \(u\),然后把较小的 \(d_u\) 个点 \(v\),我们设置边 \((u,v)\),它们的出度不变;对于剩下较大的点 \(v\),我们设置边 \((v,u)\),它们的出度会减少 \(1\)

上述构造方法为什么正确呢?\(n=1\) 时显然正确,操作后会从 \(n\) 阶竞赛图变成 \(n-1\) 阶竞赛图,发现考虑最小点的连边之后仍然满足 \(\sum_{i=1}^kd_i\geq {k\choose 2}\) 的条件,那么我们就归纳到了更小的情况。

时间复杂度 \(O(n^5)\)

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 62;
const int M = 1900;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[N],d[N],p[N],e[N][N],f[N][N][M],g[N][N][M];
signed main()
{
	m=read();
	for(int i=1;i<=m;i++) a[i]=read();
	sort(a+1,a+1+m);
	f[0][0][0]=1;
	for(int i=1;i<=m;i++) for(int j=i;j<N;j++)
		for(int k=i-1;k<j;k++) for(int x=(k-1)*k/2;x<M;x++)
		{
			int y=x+(j-k)*a[i];
			if(y>=M) continue;
			if(f[i-1][k][x]) f[i][j][y]=1,g[i][j][y]=j-k;
		}
	for(n=m;n<N;n++)
		if(f[m][n][n*(n-1)/2]) break;
	if(n==N) {puts("=(");return 0;}
	printf("%d\n",n);
	for(int i=m,j=n,k=n*(n-1)/2;i;i--)
	{
		int x=g[i][j][k];
		for(int p=0;p<=x;p++) d[j-p]=a[i];
		j-=x;k-=x*a[i];
	}
	for(int i=1;i<=n;i++) p[i]=i;
	for(int i=1;i<=n;i++)
	{
		sort(p+i,p+1+n,[&](int i,int j)
		{return d[i]<d[j];});
		int u=p[i];
		for(int j=i+1;j<=i+d[u];j++)
			e[u][p[j]]=1;
		for(int j=i+d[u]+1;j<=n;j++)
			e[p[j]][u]=1,d[p[j]]--;
	}
	for(int i=1;i<=n;i++,puts(""))
		for(int j=1;j<=n;j++)
			printf("%d",e[i][j]);
}

Nephren Runs a Cinema

题目描述

点此看题

解法

这道 *2900 的题竟然被我手切了,虽然是水题但还是写篇题解纪念一下。

首先枚举 \(\tt VIP\) 用户的个数(哇爆率真的很高),再枚举获得 \(50\) 元钞票的总数 \(x\),就可以转化成这样的问题:从 \((0,0)\) 走到 \((n-x,x)\),不越过 \(y=x\) 的方案数,根据卡特兰数的知识可以知道方案数是 \({n\choose n-x}-{n\choose n-x+1}\)

其中 \(x\) 需要满足 \(l\leq n-2x\leq r\),可以解出 \(x\in [x_{1},x_{2}]\),那么我们把方案数求和看看:

\[\sum_{x=x_1}^{x_2}{n\choose n-x}-{n\choose n-x+1}={n\choose n-x_1}-{n\choose n-x_2+1} \]

发现只剩下两项组合数了!那么剩下的问题是预处理所有 \({n\choose i}\),可以把模数的所有质数次幂拆分出来,然后分两部分计算即可(模数质数和非模数质数),时间复杂度 \(O(n\log n)\)

有一个容易错的小细节是 \(i\) 的枚举范围应该是 \([0,n-l]\),要不然会出问题。

#include <cstdio>
#define int long long
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,MOD,l,r,ans,fac[M],inv[M],c[M][20],p[20];
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
int C(int n,int m)
{
	if(m<0 || n<m) return 0;
	int x=fac[n]*inv[m]%MOD*inv[n-m]%MOD;
	for(int i=1;i<=k;i++)
		x=x*qkpow(p[i],c[n][i]-c[m][i]-c[n-m][i])%MOD;
	return x;
}
signed main()
{
	n=read();MOD=read();l=read();r=read();
	//part I : initialize
	int x=MOD,phi=x;
	for(int i=2;i*i<=x;i++) if(x%i==0)
	{
		x/=i;p[++k]=i;phi=phi/i*(i-1);
		while(x%i==0) x/=i;
	}
	if(x>1) p[++k]=x,phi=phi/x*(x-1);
	inv[0]=fac[0]=1;
	for(int i=1;i<=n;i++)
	{
		x=i;
		for(int j=1;j<=k;j++)
			while(x%p[j]==0) x/=p[j],c[i][j]++;
		fac[i]=x*fac[i-1]%MOD;
		inv[i]=qkpow(fac[i],phi-1);
		for(int j=1;j<=k;j++) c[i][j]+=c[i-1][j];
	}
	//part II : Catalan numbers
	for(int i=0;i<=n-l;i++)
	{
		int x1=(n-i-l)/2,x2=(n-i-r+1)/2;
		int h=C(n-i,n-i-x1)-C(n-i,n-i-x2+1);
		h=(h%MOD+MOD)%MOD;
		ans=(ans+h*C(n,i))%MOD;
	}
	printf("%lld\n",ans);
}
posted @ 2022-05-29 09:51  C202044zxy  阅读(188)  评论(0编辑  收藏  举报