20231112组合数学+DP——题目练习

例题一:P7322 「PMOI-4」排列变换

「PMOI-4」排列变换 - 洛谷icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P7322

解法:

问滑动窗口中的 max⁡ 变化了多少次。

因为向右滑动 1长度的情况下,只有窗口最左的数会出窗口,而右边紧邻的那个数会进入窗口,所以我们实际上只需要对于每个窗口看最左边的数是否是窗口滑动之前的最大值,以及新加入的最右边的数是否是窗口滑动之后新的最大值即可。

(也可以用最小值来理解:)

其中从左往右,求和符合是枚举这个最小值的大小,组合数是令窗口中剩余的 k−1 个数大于最小值的值的个数,两个阶乘分别是窗口外面的数任意排列和窗口内部除去最小值外任意排列,最后一个 n−k 是计算排列中窗口可以在的位置有 n−k 个。(其实总窗口有n−k+1 个,就是最左边那个无法移动了)。注意:左边和右边对应的都是这个式子,*2即可

实际上我们还会算重一部分,既满足最左边是窗口最小值,又满足新加入的是窗口的新最小值,这部分答案需要减掉:考虑左边小右边大、左边大右边小两种情况:

所以请看代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const ll N = 5e5+5,mod = 998244353;
ll n,k,inv[N],fac[N],ifac[N];


inline ll Mod(ll x) {return x>=mod?x-mod:x;}

inline void init()
{
	inv[1]=fac[0]=ifac[0]=1;
	for(int i=1;i<=n;++i)
	{
		if(i^1) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
		fac[i]=fac[i-1]*i%mod;
		ifac[i]=ifac[i-1]*inv[i]%mod;
	}
}

inline ll C(ll x,ll y)
{
	if(x<y) return 0;
	return fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}

int main()
{
	scanf("%lld %lld",&n,&k);
	init();
	ll ans=0;
	for(ll i=0;i<=n-1;++i)
	{
		ll tmp=2*(n-k)*fac[n-k]%mod*fac[k-1]%mod*C(i,k-1)%mod;
		ans=(ans+tmp)%mod;
	}
	for(ll i=0;i<=n-1;++i)
	{	
		ll tmp2=2*(n-k)*fac[n-k-1]%mod*fac[k-1]%mod*C(i,k)%mod;
		ans=((ans-tmp2)%mod+mod)%mod;
	}
	ans=(ans+fac[n])%mod; 
	ans=(ans%mod+mod)%mod;
	printf("%lld",ans);
	return 0;
}

例题二:P7044 「MCOI-03」括号

「MCOI-03」括号 - 洛谷icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P7044

  • K=0

        就是经典括号匹配问题,开个栈模拟一下即可。

  • K=1

        显然可以 O(n2) 枚举子串,但这样复杂度太高。

        考虑拆开找贡献,对于下标为 i 的左括号,设它匹配的右括号下标为 j (特别的,没有匹配时 j=n+1 )。

        那么易证,这个左括号对所有i≤r<j 且 1≤l≤i 的区间都有 1 的贡献,总贡献即为 i×(j−i) 。

右括号同理,因此可以O(n) 计算。

  • K=2(层数再多的时候同理)

请看代码:

#include<bits/stdc++.h>
#define MOD 998244353
#define N 2000010

using namespace std;
typedef long long ll;

ll n,k,ans;
ll f[N],inv[N],finv[N],p[N];
char ch[N];
stack<ll> s;

ll C(ll n,ll m)
{
	return f[n]*finv[m]%MOD*finv[n-m]%MOD;
}

int main()
{
    f[0]=f[1]=inv[0]=inv[1]=finv[0]=finv[1]=1;
    for(ll i=2;i<=2000000;++i) 
		f[i]=f[i-1]*i%MOD;
	for(ll i=2;i<=2000000;++i)
	{
		inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
		finv[i]=finv[i-1]*inv[i]%MOD;
	}
    scanf("%lld %lld",&n,&k);
    scanf("%s",ch+1);
    for(ll i=1;i<=n;++i) 
		if(ch[i]=='(') 
			p[i]=n+1;
    for(ll i=1;i<=n;++i) 
        if(ch[i]=='(')
			s.push(i);
        else 
		{
            if(s.empty()) 
				continue;
            p[s.top()]=i;
			p[i]=s.top(),s.pop();
        }
    for(ll i=1;i<=n;++i)
        if(ch[i]=='(') 
			ans=(ans+C(k+i-1,k)*(C(k+n-i,k)-C(n-p[i]+k,k)+MOD)%MOD)%MOD;
        else 
			ans=(ans+C(k+n-i,k)*(C(k+i-1,k)-C(k+p[i]-1,k)+MOD)%MOD)%MOD;
    printf("%lld\n",ans);
    return 0;
}

例题三:P5505 [JSOI2011] 分特产 

[JSOI2011] 分特产 - 洛谷icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P5505

考虑两个情况:1. 恰好x个人没特产 2. 至少x个人没特产,看哪个好求,这题中很明显应该是第二个,且第一个可以通过第二个容斥加减得到。

现在求出了至少i个人没特产的情况就可以着手去求恰好的时候了:

代码如下:

#include<bits/stdc++.h>
#define MOD 1000000007
#define N 2010

using namespace std;
typedef long long ll;

ll ans;
ll f[N],inv[N],finv[N],F[N],a[N];
char ch[N];
stack<ll> s;

ll qpow(ll x,ll k){
    ll res=1;
    while(k){
        if(k&1) res=res*x%MOD;
        x=x*x%MOD;k>>=1;
    }
    return res;
}

ll C(ll n,ll m)
{
	return f[n]*finv[m]%MOD*finv[n-m]%MOD;
}

int main()
{
    ll n,m;
    scanf("%lld %lld",&n,&m);
    for(ll i=1;i<=m;++i)
    	scanf("%lld",&a[i]);
	f[0]=f[1]=inv[0]=inv[1]=finv[0]=finv[1]=1;
    for(ll i=2;i<=2010;++i) 
		f[i]=f[i-1]*i%MOD;
	for(ll i=2;i<=2010;++i)
	{
		inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
		finv[i]=finv[i-1]*inv[i]%MOD;
	}
    for(ll i=0;i<=n;++i)
    {
    	F[i]=C(n,i);
    	for(ll j=1;j<=m;++j)
		{
			F[i]=F[i]*C(a[j]+n-i-1,a[j])%MOD;	
		} 
	}
	for(ll i=0;i<=n-1;++i)
	{
		if(i&1)
		{
			ans-=F[i];
			ans=(ans%MOD+MOD)%MOD;
		}
		else
		{
			ans+=F[i];
			ans%=MOD;
		}
	}
	printf("%lld",ans); 
    return 0;
}

例题四:P6076 [JSOI2015] 染色问题

[JSOI2015] 染色问题 - 洛谷icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P6076转载:洛谷题解

我的代码:

#include<bits/stdc++.h>
#define MOD 1000000007
#define N 1010

using namespace std;
typedef long long ll;

ll ans;
ll f[N],inv[N],finv[N],F[N];
char ch[N];
stack<ll> s;

ll qpow(ll x,ll k){
    ll res=1;
    while(k){
        if(k&1) res=res*x%MOD;
        x=x*x%MOD;k>>=1;
    }
    return res;
}

ll C(ll n,ll m)
{
	return f[n]*finv[m]%MOD*finv[n-m]%MOD;
}

int main()
{
    ll n,m,c;
    scanf("%lld %lld %lld",&n,&m,&c);
	f[0]=f[1]=inv[0]=inv[1]=finv[0]=finv[1]=1;
    for(ll i=2;i<=1010;++i) 
		f[i]=f[i-1]*i%MOD;
	for(ll i=2;i<=1010;++i)
	{
		inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
		finv[i]=finv[i-1]*inv[i]%MOD;
	}
    for(ll i=1;i<=c;++i)
    {
    	ll tmp=0;
		for(ll j=1;j<=m;++j)
    	{
    		if((m-j)&1)
    		{
    			tmp=((tmp-C(m,j)*qpow(qpow(i+1,j)-1,n))%MOD+MOD)%MOD;
			}
			else
			{
				tmp=((tmp+C(m,j)*qpow(qpow(i+1,j)-1,n))%MOD+MOD)%MOD;
			}
		}
		F[i]=tmp;
	}
	ll tmp=0;
	for(ll i=0;i<=c;++i)
	{
		if((c-i)&1)
		{
			ans-=(F[i]*C(c,i));
			ans=(ans%MOD+MOD)%MOD;
		}
		else
		{
			ans+=(F[i]*C(c,i))%MOD;
			ans%=MOD;
		}
	}
	printf("%lld",ans); 
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值