计数与递推

计数与递推

公式

排列组合

排列公式 P(n,k)=n!(nk)!

组合公式 C(n,k)=P(n,k)P(k,k)=n!(nk)!k!

组合公式的性质

  • C(0,0)=C(n,0)=C(n,n)=1
  • C(n,k)=C(n,nk)
  • C(n,k)=C(n1,k)+C(n1,k1)
  • C(n,k)=C(n,k1)(nk+1)/k

递推

Catalen 数

给一个凸 n 边形,用 n3 条不相交的对角线把它分成 n2 个三角形,求不同的方法数目。

题目

[UVA11538] Chess Queen

国际象棋中,在同一行、列、对角线上的皇后是可以互相攻击的,求在 n×m 的棋盘上摆放两个可以互相攻击的皇后的方法数。

n,m106

首先,在同一行的方案数很容易想到: nm(m1) ,那么同一列的就是 mn(n1)

对于对角线上的情况,我们发现对角线的长度依次为:

1,2,3,,n1,nmn+1 times,n,n1,n2,,2,1

考虑到对角线有两个方向,需要乘 2
2(2i=1n1i(i1)+(mn+1)n(n1))=2n(n1)(3mn1)3

那么答案为
mn(m+n2)+2n(n1)(3mn1)3

#include<iostream>
#include<cstdio>
using namespace std;
typedef unsigned long long ull;

int main()
{
    ull n,m;
    while(cin>>n>>m)
    {
        if(!n&&!m) break;
        if(n>m) swap(n,m);
        cout<<n*m*(m+n-2)+2*n*(n-1)*(3*m-n-1)/3<<endl;
    }
    return 0;
}

[UVA11401] Triangle Counting

给定正整数 n ,求边长为不大于 n 的不同正整数的三角形的个数。

n106

设最大边长为 a 的三角形有 f(a) 个,另外两条边为 b,c ,则有 b+c>x ,所以 ab<c<a

可以暂时认为 fy 是一个等差数列, fb=0+1+2+3++(a2)=(a1)(a2)2

然而这样我们计算了 b=c 的情况,由于 b[a2+1,a1] ,则需要减去 a12

又由于 b,c 的对称性,还需要除以 2


f(a)=12((a1)(a2)2a12)

而最长的边可以在 [1,n] 之间的整数中选择,故答案为 ans(n)=i=1nf(n)

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int MAXN=1e6;

ll f[MAXN+5];
inline void preTab()
{
    for(ll i=4;i<=MAXN;i++) 
        f[i]=f[i-1]+((i-1)*(i-2)/2-(i-1)/2)/2;
}
int main()
{
    preTab();
    int n;
    while(cin>>n)
    {
        if(n<3) break;
        cout<<f[n]<<endl;
    }
    return 0;
}

[UVA11806] Cheerleaders

在一个 m×n 的矩形网格中放 k 个相同的石子,要求每个格子例最多放一个,石子必须用完,且第一行、最后一行、第一列、最后一列都要放石子。

T50,2m,n20,k500

如果没有最后一个条件的限制,答案可以很轻易用组合公式求出。有了限制之后可以考虑容斥原理,二进制枚举即可。

#include<iostream>
#include<cstdio>

const int MAXK=500,P=1e6+7;

int n,m,k;

int C[MAXK+5][MAXK+5];
inline void preTab()
{
    int i,j;
    C[0][0]=1;
    for(i=1;i<=MAXK;i++)
    {
        C[i][0]=C[i][i]=1;
        for(j=1;j<i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
    }
}

int main()
{
    preTab();
    int Case,T;scanf("%d",&T);
    for(Case=1;Case<=T;Case++)
    {
        int ans=0;
        scanf("%d%d%d",&n,&m,&k);
        for(int S=0;S<16;S++)
        {
            int cnt=0,r=n,c=m;
            if(S&1) r--,cnt++;if(S&2) r--,cnt++;
            if(S&4) c--,cnt++;if(S&8) c--,cnt++;
            if(cnt&1) ans=(ans-C[r*c][k]+P)%P;
            else ans=(ans+C[r*c][k])%P;
        }
        printf("Case %d: %d\n",Case,ans);
    }
    return 0;
}

[POJ1737] Connected Graph

给定正整数 n ,求有 n 个节点的连通图个数。

n50

直接计算答案并不容易,我们考虑求出所有的可能性后减去不连通的个数。

f(n) n 个节点的连通图个数(答案), g(n) n 个节点的非连通图个数, h(n) n 个节点的图的个数。

对于 h(n) ,一张 n 个节点的图中最多有 n×(n1)2 条边,每条边都可以选或不选,根据乘法原理: h(n)=2n×(n1)/2

接下来我们希望找到一个递推式。

考虑 g(n) 的求法,对于一号节点,枚举其所在连通块的节点数 i ,那么有 (n1i1)f(i)h(ni) 种,求和即可。

由于显而易见的结论: f(n)+g(n)=h(n)

那么可得:

f(n)=h(n)i=1n1(n1i1)f(i)h(ni)

用 python 计算后我们发现结果很长,所以需要用高精度。

#include<iostream>
#include<cstdio>
#include<cstring>

const int MAXN=55;

struct BN
{
    int num[1005],len;
    BN(int x=0)
    {
        len=0;
        memset(num,0,sizeof(num));
        if(x) len--;
        while(x) num[++len]=x%10,x/=10;
    }
};
BN operator +(BN a,BN b)
{
    BN res;res.len=std::max(a.len,b.len);
    for(int i=0;i<=std::max(a.len,b.len);i++)
    {
        res.num[i]+=a.num[i]+b.num[i];
        if(res.num[i]>=10)
            res.num[i]-=10,res.num[i+1]++;
    }
    while(true)
    {
        res.num[res.len+1]+=res.num[res.len]/10,res.num[res.len]%=10;
        if(res.num[res.len+1]) res.len++;
        else break;
    }
    return res;
}
BN operator -(BN a,BN b)
{
    BN res;res.len=std::max(a.len,b.len);
    for(int i=0;i<=std::max(a.len,b.len);i++)
    {
        res.num[i]+=a.num[i]-b.num[i];
        if(res.num[i]<0)
            res.num[i]+=10,res.num[i+1]--;
    }
    while(!res.num[res.len]) res.len--;
    return res;
}

BN t[1005];
BN operator *(BN a,BN b)
{
    int i,j;
    for(i=0;i<=a.len;i++) t[i]=BN();
    for(i=0;i<=a.len;i++)
    {
        for(j=0;j<=b.len;j++)
            t[i].num[i+j]=a.num[i]*b.num[j];
        for(j=0;j<=b.len;j++)
            t[i].num[i+j+1]+=t[i].num[i+j]/10,t[i].num[i+j]%=10;
        t[i].len=i+b.len; 
        while(true)
        {
            t[i].num[t[i].len+1]+=t[i].num[t[i].len]/10,t[i].num[t[i].len]%=10;
            if(t[i].num[t[i].len+1]) t[i].len++;
            else break;
        }
    }
    BN res;
    for(i=0;i<=a.len;i++) res=res+t[i];
    return res;
}

BN qpow(BN x,int n)
{
    int i;
    BN res=BN(1);
    for(i=1;i<=n;i<<=1,x=x*x)
        if(i&n) res=res*x;
    return res;
}

BN f[MAXN],C[MAXN][MAXN],h[MAXN];

inline void initTab()
{
    int i,j;
    for(i=0;i<MAXN;i++) 
        h[i]=qpow(BN(2),i*(i-1)/2);
    C[0][0]=BN(1);
    for(i=0;i<MAXN;i++)
    {
        C[i][0]=C[i][i]=BN(1);
        for(j=1;j<i;j++)
            C[i][j]=C[i-1][j]+C[i-1][j-1];
    }
    for(i=1;i<MAXN;i++)
    {
        f[i]=h[i];
        for(j=1;j<i;j++)
            f[i]=f[i]-(C[i-1][j-1]*f[j]*h[i-j]);
    }
}

void print(BN x)
{
    for(int i=x.len;i>=0;i--)
        putchar(x.num[i]+'0');
}

int main()
{
    int i,n;
    initTab();
    while(scanf("%d",&n)!=EOF&&n)
    {
        print(f[n]);
        putchar('\n');
    }
}

[2017.08.25] 锦标赛游戏

n 个人进行循环赛,每两人进行比赛的胜率平分。如果两个人互相直接或间接赢过对方,那么成这两人水平相当。对于一个人,若有 k 个人与其水平相当,那么可以获得 di 奖金。求选手中期望奖金的最大值对 998244353 取模的结果。

n3000

f[n] 为所有种类的竞赛图的所有点的期望奖金之和,由于一种竞赛图中的所有点等价,所以所有种类的竞赛图中所有的点求平均即为答案。


h(n)=2n(n1)2


f(n)=i=1n(ni)g(i)(f(ni)+h(ni)dii)

h(n)g(n)=i=1n(ni)h(ni)g(i)=h(n)i=1n1(ni)h(ni)g(i)

#include<iostream>
#include<cstdio>
typedef long long ll;
const int MAXN=3e3+5,P=998244353;

int n;
int d[MAXN],f[MAXN],g[MAXN],ans[MAXN];

int C[MAXN][MAXN],pow2[MAXN*(MAXN-1)/2];
inline void preTab()
{
    int i,j;C[0][0]=1;
    for(i=1;i<=n;i++)
    {
        C[i][0]=C[i][i]=1;
        for(j=1;j<i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
    }
    pow2[0]=1;
    for(i=1;i<=n*(n-1)/2;i++) pow2[i]=pow2[i-1]*2%P;
}

int qpow(int x,int y)
{
    int res=1;
    for(ll i=1;i<=y;i<<=1,x=(ll)x*x%P)
        if(y&i) res=(ll)res*x%P;
    return res;
}

int inv(int x){return qpow(x,P-2);}

int main()
{
    freopen("tournament.in","r",stdin);
    freopen("tournament.out","w",stdout);
    int i,j;
    scanf("%d",&n);
    preTab();
    for(i=1;i<=n;i++) scanf("%d",d+i);
    for(i=1;i<=n;i++)
    {
        g[i]=pow2[i*(i-1)/2];
        for(j=1;j<i;j++) g[i]=(g[i]-(ll)g[j]*C[i][j]%P*pow2[(i-j)*(i-j-1)/2]%P+P)%P;
    }
    for(i=1;i<=n;i++) for(j=1;j<=i;j++)
        f[i]=(f[i]+(ll)g[j]*C[i][j]%P*(f[i-j]+(ll)d[j]*j%P*pow2[(i-j)*(i-j-1)/2]%P)%P)%P;
    for(i=1;i<=n;i++) ans[i]=(ll)f[i]*inv(i)%P*inv(pow2[i*(i-1)/2])%P;
    for(i=1;i<=n;i++) printf("%d\n",ans[i]);
}

[2017.09.10] Braid

统计在所有长度为 n 的环排列中, 满足顺时针看不出现 [i,i+1](1i<n) [n,1] 这样的子串的环排列个数。答案对 998244353 取模。多组数据。

n106,T20

考虑使用容斥原理进行计数.

包含至少一个形如 [i,i+1] [n,1] 这样的子串的环排列个数是 (n1)(n2)! 个;

可以推广为包含至少 k(0k<n) 个的环排列个数是 (nk)(nk1)! ,
同时注意到包含 n 个的环排列个数一定是 1 个.

所以最终答案就是 (1)n+k=0n1(1)n(nk)(nk1)!

这个序列 (OEIS A000757) 还有一个具有组合意义的递推式:

A(n)=(n2)A(n1)+(n1)A(n2)(1)n

#include<iostream>
#include<cstdio>
typedef long long ll;
const int MAXN=1e5,P=998244353;

int f[MAXN+5];

int main()
{
    std::ios::sync_with_stdio(false);
    freopen("braid.in","r",stdin);
    freopen("braid.out","w",stdout);
    for(int i=3;i<=MAXN;i++) f[i]=((ll)(i-2)*f[i-1]%P+(ll)(i-1)*f[i-2]%P+((i&1)?1:-1))%P;
    int T;std::cin>>T;
    while(T--)
    {
        int n;std::cin>>n;
        std::cout<<f[n]<<std::endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值