The 2021 CCPC Weihai Onsite M - 810975(容斥)


寫這題前,先把這道hdu6397經典題寫了 。
題意:給定n,m,k,要求個m個格子填數,使每個數在[0,n-1]范圍內,且他們的和為k。
思路:首先,不管[0,n-1]這個限制條件,考慮怎么用組合數算出方案。我是這樣理解的,我們對選出的數取個前綴和,那么這m個數的前綴和必然在[0,k]這個區間內,且滿足不遞減,和最后一個數必然是k,由於最后一個數已經定了,那么問題變成選m-1個數,使他們不遞減,且在[0,k]區間內,可以考慮插板法,對[1,k]的左邊右邊插板,插的每個板表示這個板要選擇當前空隙的左邊那個數,所以組合數就是C(k+m-1,m-1);現在考慮[0,n-1]這個限制,顯然可以容斥,所以問題轉成怎么算至少有i個數超過n-1,且和為k的方案數,這又是一個經典組合數問題,我們可以把這個i個超過n-1的數都減去n,這樣所有數都在[0,n-1]范圍內,且和為k-ni,也可以理解為把和變為k-ni,然后把里面的i個數都加上n就符合條件了,所以方案是\(C(m,i) \times C(k+m-1-n*i,m-1)\);

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <queue>
#include <set>
#include <stack>
#include <time.h>
#include <map>
#include <algorithm>
#include <fstream>
//#include <unordered_map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1000000 + 100;
const int INF = 0x7fffffff;
const int mod = 998244353;
const ll mod1 = 998244353;
const ll base = 137;
const double Pi = acos(-1.0);
const int G = 3;
int q_pow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1)
		{
			res = 1ll * res * a % mod;
		}
		b >>= 1;
		a = 1ll * a * a % mod;
	}
	return res;
}
int F[maxn], Finv[maxn], inv[maxn];
void init()
{
	inv[1] = 1;
	for (int i = 2; i < maxn; i++)
	{
		inv[i] = (mod - mod / i) * 1ll * inv[mod % i] % mod;
	}
	F[0] = Finv[0] = 1;
	for (int i = 1; i < maxn; i++)
	{
		F[i] = F[i - 1] * 1ll * i % mod;
		Finv[i] = Finv[i - 1] * 1ll * inv[i] % mod;
	}
}
int comb(int n, int m)
{
	if (m < 0 || m > n)
		return 0;
	return F[n] * 1ll * Finv[n - m] % mod * Finv[m] % mod;
}
int n,m,k;
int main()
{
	init();
	int t;
	cin >> t;
	while (t--)
	{
		scanf("%d%d%d", &n, &m, &k);
		int f=1;
		int ans=0;
		for(int i=0;i<=m;i++)
		{
			int uu=1;
			if(!f) uu=mod-1;
			f^=1;
			if(1ll*i*n>k+m-1) break;
			ans=(1ll*ans+1ll*uu*comb(m,i)%mod*comb(k+m-1-i*n,m-1)%mod)%mod;
		}
		printf("%d\n",ans);
	}
//	system("pause");
}

然后回到M. 810975這道題。
題意:給定n,m,k,構造長度為n的01串,使1的總數為m,且每個連續的1段長度最大值是k。
思路:首先對於最大值為k的這個條件,可以用最多有k個減去最多有k-1個容斥掉。然后考慮怎么算最多有k的方案。我們可以把兩個連續的0中間看做一個長度為0的連續1段,那么問題轉換成,構造n-m+1個連續1段,長度在[0,k],且和為m。這跟上面那題就一模一樣了,然后這題就解決了。
吐槽下,這周末的兩場區域賽居然都考了容斥,還都是典中典,隊友也沒寫過容斥,我還是得多學點容斥的套路...

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <queue>
#include <set>
#include <stack>
#include <time.h>
#include <map>
#include <algorithm>
#include <fstream>
//#include <unordered_map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1000000 + 100;
const int INF = 0x7fffffff;
const int mod = 998244353;
const ll mod1 = 998244353;
const ll base = 137;
const double Pi = acos(-1.0);
const int G = 3;
int q_pow(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1)
		{
			res=1ll*res*a%mod;
		}
		b>>=1;
		a=1ll*a*a%mod;
	}
	return res;
}
int F[maxn], Finv[maxn], inv[maxn];
void init(){
    inv[1] = 1;
    for(int i = 2; i < maxn; i ++){
        inv[i] = (mod - mod / i) * 1ll * inv[mod% i] % mod;
    }
    F[0] = Finv[0] = 1;
    for(int i = 1; i < maxn; i ++){
        F[i] = F[i-1] * 1ll * i % mod;
        Finv[i] = Finv[i-1] * 1ll * inv[i] % mod;
    }
}
int comb(int n, int m){
    if(m < 0 || m > n) return 0;
    return F[n] * 1ll * Finv[n - m] % mod * Finv[m] % mod;
}
int cal(int num,int lim,int sum)
{
	int f=1;
	int res=0;
	//cout<<num<<' '<<lim<<' '<<sum<<endl;
	for(int i=0;i<=num;i++)
	{
		int uu=1;
		if(!f) uu=mod-1;
		if(1ll*lim*i>sum+num-1) break;
		res=(1ll*res+1ll*uu*comb(num,i)%mod*comb(num+sum-1-i*lim,num-1)%mod)%mod;
		f^=1;
	//	cout<<i<<' '<<res<<endl;
	}
	return res;
}
int n,m,k;
int main()
{
	init();
	scanf("%d%d%d",&n,&m,&k);
	printf("%d\n",(1ll*cal(n-m+1,k+1,m)-cal(n-m+1,k,m)+mod)%mod);
//	system("pause");
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM