• #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\) 个点,它们出度和是 \(j\) 是否合法,转移时只需要时刻保证 \(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);
    }
    
  • 相关阅读:
    VMwareTools安装笔记
    Oracle常用命令(持续更新)
    window常用命令(持续更新)
    Oracle 中 sys和system帐号的区别
    决策树——排序算法的理论下界
    插入、选择、冒泡、梳排序性能比较
    插入、选择、冒泡排序的综述
    绝知此事要躬行之——插入排序
    Tree 和ls 的使用
    用户目录
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16323248.html
Copyright © 2020-2023  润新知