组合数学专题

NC19788 Travel

题意为:将n个节点的树分成m个连通块,并对每个连通块标号的方案数。

思路:初看这道题像树形dp,但一想转移方程和数据范围,感觉不可解。
换成组合数学的角度,将分成m个连通块转化为删去m-1条边,显然删去m-1条边的方案与分成m个连通块的方案是一一对应的,再将连通块标号,就是乘m的阶乘了。
C(n-1,m-1)×m!

NC50039 kotori

题意:n个1-m之间的数排成一排,相邻数不能相等的方案数。

思路:直接给公式:m×pow(m-1,n-1)

关键是扩展:n个1-m之间的数排成一个圆,相邻数不能相等的方案数。

同样的断链成环

https://www.luogu.com.cn/problem/P1357

#include <iostream>
#include <cstring>
#define inf 2147483647
#define ll long long
using namespace std;

const ll mo=1e9+7;
ll n,m,k,N; 
ll ans[33][33],w[33][33],a[33][33];
//f0[i][j]:首项是0,到了第i项时为j的条件方案数。 


void mi()
{
	ll kp[33][33]={0};
	for (int i=0;i<N;i++)	for (int j=0;j<N;j++) {
		kp[i][j]=0;
		for (int k=0;k<N;k++) kp[i][j]=(kp[i][j]%mo+ans[i][k]*w[k][j]%mo)%mo;
	}
	for (int i=0;i<N;i++)
	for (int j=0;j<N;j++) ans[i][j]=kp[i][j];
}

void zc()
{
	ll kp[33][33]={0};
	for (int i=0;i<N;i++) for (int j=0;j<N;j++)
	{	kp[i][j]=0;
		for (int k=0;k<N;k++) kp[i][j]=(kp[i][j]%mo+w[i][k]*w[k][j]%mo)%mo;
	}
	for (int i=0;i<N;i++)
	for (int j=0;j<N;j++) w[i][j]=kp[i][j];
}

void ksm(ll k)
{
	for (int i=0;i<N;i++) ans[i][i]=1; 
	while (k){ if (k&1) mi(); k/=2; zc(); }
}

int check(int x)  //初赛学到的可快速算二进制中1的函数. 
{	int sm=0;
	while (x){ x= x&(x-1); sm++; }
	return sm;
}

int main()
{
	cin>>n>>m>>k; N=(1<<m);
	for (int i=0;i<N;i++) if (check(i)<=k)  //预处理构造矩阵 
	{
		int j=i>>1; w[j][i]=1; 
		j|=(1<<(m-1)); if (check(j)<=k) w[j][i]=1;
	}
	ksm(n); ll sm=0;
	for (int i=0;i<N;i++) 
		if (check(i)<=k) sm=(sm+ans[i][i])%mo;
	cout<<sm<<endl;
}

NC14735 美丽的项链

就是一个基础dp

#include<stdio.h>
#include<string.h>
const int maxn=100;
#define max(a,b) a>b?a:b
int n,m,a[maxn],l[maxn],r[maxn];
long long dp[maxn][maxn+1];
int main(){
    int i,j,k;
    //freopen("input.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        for(i=0;i<n;i++) scanf("%d%d",l+i,r+i);
        memset(dp,0,sizeof(dp));
        for(i=l[0];i<=r[0];i++) dp[0][i]=1;
        for(i=1;i<n;i++)
            for(j=1;j<=m;j++){
                int left=max(0,j-r[i]),right=max(0,j-l[i]);
                for(k=left;k<=right;k++) dp[i][j]+=dp[i-1][k];
            }
        printf("%lld\n",dp[n-1][m]);
    }
}//dp[i][j]表示用前i种宝石组成j颗宝石的项链的方案数
 //但是第i种只能是l[i]到r[i]之间的数量
 //所以dp[i][j]=dp[i-1][j-r[i]]+dp[i-1][j-r[i]+1]+...+dp[i-1][j-l[i]]

NC15251 白兔的式子

NC14599 子序列

NC15550 箱庭的股市

NC16543 NC20824

NC15077

https://ac.nowcoder.com/acm/problem/15077

因为要保证一定经过该点 所以将路线图分为两部分的卡特兰数

这个是我高中数学老师张刚讲组合数的时候讲的方法 我印象很深

NC16537

【C++】球盒问题总结(八种情况)https://blog.csdn.net/Ljnoit/article/details/102211595

namespace CNM {//组合数板子
    const int N = 2e6 + 5;
    ll quick(ll x, ll n)
    {
        ll res = 1;
        while (n)
        {
            if (n & 1) res = (res*x) % mod;
            x = x * x%mod;
            n >>= 1;
        }
        return res;
    }
    ll inv(ll x) { return quick(x, mod - 2); }
    ll fac[N], invfac[N];
    void init()
    {
        fac[0] = 1;
        for (int i = 1; i < N; ++i) fac[i] = (fac[i - 1] * i) % mod;
        invfac[N - 1] = inv(fac[N - 1]);
        for (int i = N - 2; i >= 0; --i) invfac[i] = (invfac[i + 1] * (i + 1)) % mod;
    }
    ll C(int n, int m)
    {
        if (n < m || m < 0) return 0;
        return fac[n] * invfac[m] % mod*invfac[n - m] % mod;
    }
}
vector<pair<int,int>> fac[MAXN];
int x, y;
void slove() {
    cin >> x >> y;
    ll ans = 1;
    for (auto p : fac[x]) {
        int n = p.second, m = y;
        ans *= CNM::C(n + m - 1,m - 1);
        ans %= mod;
    }
    ans *= ksm(2, y - 1, mod);
    ans %= mod;
    cout << ans << endl;
}
signed main() {
    CNM::init();
    for (int i = 2; i < MAXN; i++) {
        if (fac[i].size())continue;
        for (int j = i; j < MAXN; j += i) {
            int t = j, cnt = 0;
            while (t%i == 0)t /= i, cnt++;
            fac[j].push_back({ i,cnt });
        }
    }
    IOS;
    int T; cin >> T;
    while(T--)slove();
}

https://ac.nowcoder.com/acm/problem/14599

#include <cstdio>
#include <iostream>
#include <cstring>
#define int long long
const int N     = 1e6+10;
const int MOD   = 1000000007;
using namespace std;
 
long long bin[N];
void Init()
{
    bin[0]=1;
    bin[1]=1;
    for(int i=2;i<N;i++)
        bin[i]=i*bin[i-1]%MOD;
}
long long POW(long long a,long long b)
{
    long long res=1;
    while(b)
    {
        if(b&1)
        {
            res*=a, res%=MOD;
        }
        b>>=1;
        a*=a, a%=MOD;
    }
    return res;
}
long long C(long long n, long long m)//求C(n,m) n在下,m在上。注意在这之前加init函数
{
    return (bin[n]%MOD)*(POW(bin[m]*bin[n-m]%MOD,MOD-2))%MOD;
}
 
char str[N];
int n, m;
 
void Solve()
{
    int ans = 0;
    for (int i=1; i<=m-n+1; i++)
    {
        ans += C(m-i,n-1) * POW(25, m-i-n+1) % MOD * POW(26, i-1) % MOD, ans%=MOD;
    }
    printf("%lld\n",ans);
}
 
signed main()
{
    Init();
    scanf("%s",str+1);
    n = strlen(str+1);
    scanf("%lld",&m);
    Solve();
    return 0;
}

牛客33187K多校 - Link with Bracket Sequence I

   dp[0][0][0] = 1;
	for (int i=1; i<=m; i++)
	{
		for (int j=0; j<=i; j++)
		{
            //从你决定开始匹配S第一个字符之前,放什么都行(只要左括号数量>=右括号)
			dp[i][0][j] += (j-1<0?0:dp[i-1][0][j-1]) + dp[i-1][0][j+1], dp[i][0][j]%=MOD;
		}
	}
	
	for (int i=1; i<=m; i++)
	{
		for (int j=1; j<=min(m, n); j++)
		{
			for (int k=0; k<=i; k++)
			{
				int d = -1 + 2*(str[j]=='(');//左括号为1,右括号为-1

            //对于T的第 i 个字符,有两种情况:
            //一种是第i个字符匹配上原串的第j个字符,有1种放法
            //一种是第i个字符在前i-1个已经匹配上原串的第j个字符的情况下,有1种放法。
				if(k-d>=0)	dp[i][j][k] += dp[i-1][j-1][k-d];
				if(k+d<=m)	dp[i][j][k] += dp[i-1][j][k+d];
				dp[i][j][k] %= MOD;
			}
		}
	}
	printf("%lld\n",dp[m][n][0]);

posted @ 2022-07-28 11:34  wzx_believer  阅读(114)  评论(0编辑  收藏  举报