前言
感觉这个虽然挺有用的,但目前几乎没见过,可能还是太菜了......
一、二项式定理
1.二项式定理及组合恒等式
首先,二项式定理 就是 。
当 时,就是著名的杨辉三角,即
。所以,根据杨辉三角的规律,就可以得到 组合恒等式:
。有了这个关系,就可以通过类似 dp 的方法递推组合数了。这样就可以在无法求阶乘的情况下,每次 O(1) 查询组合数了。
这里就不多说证明了,可以直接从组合意义上来理解。
2.杨辉三角
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<vector<ll>>C(n,vector<ll>(n));
for(int i=0;i<n;i++)
{
C[i][0]=1;
}
for(int i=1;i<n;i++)
{
for(int j=1;j<i;j++)
{
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
C[i][i]=1;
}
for(int i=0;i<n;i++)
{
for(int j=0;j<=i;j++)
{
cout<<C[i][j]<<" ";
}
cout<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
就是组合数递推的板子题,没啥好说的。
3.计算系数
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 10007;
using Z = ModInt<M>;
constexpr int N = 1000+5;
Comb<M>comb(N);
template<std::signed_integral U>
Z P(U n, U m) {
return comb.P(n, m);
}
template<std::signed_integral U>
Z C(U n, U m) {
return comb.C(n, m);
}
void solve()
{
ll a,b,k,n,m;
cin>>a>>b>>k>>n>>m;
cout<<power((Z)a,n)*power((Z)b,m)*C(k,n)<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这个其实就是二项式定理一个简单的应用。
回顾高中知识,可以想到该变量的系数既包含二项式系数,又包含自己的系数。那么对于 x 的 n 次方项,其自己的系数就是 a 的 n 次方,对于 b 也同理。所以就只需要用乘法快速幂计算出 a 的 n 次方和 b 的 m 次方,再乘以二项式系数即可。注意模数很小,所以组合数不能开太大,否则取模就成 0 了。
4.组合数问题
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
const int N=2000;
void solve()
{
int q,k;
cin>>q>>k;
vector<vector<int>>C(N+1,vector<int>(N+1));
for(int i=0;i<=N;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)
{
C[i][j]=(C[i-1][j]+C[i-1][j-1])%k;
}
}
for(int i=0;i<=N;i++)
{
for(int j=0;j<=i;j++)
{
C[i][j]=(C[i][j]==0);
}
}
vector<vector<int>>pre(N+1,vector<int>(N+1));
for(int i=1;i<=N;i++)
{
for(int j=1;j<=N;j++)
{
pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+C[i][j];
}
}
int n,m;
while(q--)
{
cin>>n>>m;
cout<<pre[n][m]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为 n 和 m 很大,直接求会溢出。所以对于这种整除问题,还是考虑转化成同余问题。那么就是构建每个组合数模 k 的结果,然后还是根据组合恒等式去转移即可。对于多次查询,可以通过对合法的个数求一个二维前缀和解决。
5.单调数组对的数目 II
逆天打表找规律......
cpp
typedef long long ll;
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 1e9+7;
using Z = ModInt<M>;
constexpr int N = 2e5+5;
Comb<M>comb(N);
template<std::signed_integral U>
Z P(U n, U m) {
return comb.P(n, m);
}
template<std::signed_integral U>
Z C(U n, U m) {
return comb.C(n, m);
}
class Solution {
public:
int countOfPairs(vector<int>&a) {
int n=a.size();
int k=a[0]+1;
for(int i=1;i<n;i++)
{
if(a[i-1]>a[i])
{
k-=a[i-1]-a[i];
}
}
if(k<=0)
{
return 0;
}
return C(k+n-1,n).val();
}
};
将数对 (i,j) 认为分给第一个数组 i,分给第二个数组 j。假如只有一个数 5,那么其必然是可以分成 (1,4),(2,3),(3,2),(4,1) 四种。那么如果再来一个 5,那么对于当前这个 5,其还是可以分成以上四种。之后,在结合的时候,若第一次选择了 (4,1) 这种分法,那么之后就只能也选择 (4,1) 了,否则就会违规,所以有 1 种选法。若选择了 (3,2) 这种分法,那么之后就只能选择 (4,1) 和 (3,2) 了,所以 2 种选法。所以若选择 (2,3) 就有 3 种,选择 (1,4) 就有 4 种,所以一共 10 种。那么此时如果再来一个 5,选择 (4,1) 就还是 1 种,选择 (3,2) 是上一次选 (4,1) 的 1 种加上 (3,2) 的 2 种,一共 3 种。画一下图手玩可以发现,此时一共有 20 种。可以发现,若数组中只有一种数 v,数组长度为 n,那么单个数就有 k=v-1 种方案。那么把杨辉三角画出来,可以发现总的方法数就是 种。
若数字不同,首先,若第一个数是 6,那么其是有 5 种方案的。之后,若第二个数大于 6,那么不管其有几种方案,有效的只有 5 种。举个例子,若第二个数是 8,那么其 7 种方案中,(1,7) 和 (2,6) 是不会产生变化的。因为 (1,7) 不管和前一个数 6 的哪个方案结合都会违规,(2,6) 也是同理。所以,第二个数 8 是完全可以看作 6 的。
而若第二个数是 4 比第一个数 6 小,对于 (1,3),(2,2),(3,1) 这三种方案,第一个数 6 的 (4,2) 和 (5,1) 这两种方案就都不能用了。所以,若后一个数小于前一个数,此时有效方案数就要减去两数的差值。注意,这种减小不是只对上一个数有效,而是会让所有前缀的合法方案都减小。
所以,在对整个数组求出最终的合法方案数 k 后,只需要再用一次组合数公式即可。注意力扣里是可以分配 0 的,那么就只需要把单个数的方案改成 k=v+1 即可,其他的跟着再推一遍可以发现是不变的。
完全不是人......
二、二项式反演
这是给人学的吗......
首先先说一下反演,对于式子 ,其反演就是
。当一个很难求,而另一个很好求时,就可以通过当前反演式推出来。
1.四种形式
第一种形式,。
第二种形式,。
第三种形式,。
第四种形式,。
再说一个组合恒等式 ,这个可以通过组合意义求解。对于左侧,就是先从 j 个人里选出 i 个人,再从 i 个人里选 n 个,这个可以看作两轮比赛进行选拔。对于右侧,就可以看作先从 j 个人里内定出 n 个最终获选的人,再从剩下 j-n 个人里选出 j-i 个人第一轮淘汰掉。
2.信封问题
上来第一个题就不简单......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
//动态规划解
void solve1()
{
int n;
cin>>n;
if(n==1)
{
cout<<0<<endl;
return ;
}
vector<ll>dp(n+1);
dp[1]=0;
dp[2]=1;
for(int i=3;i<=n;i++)
{
dp[i]=1ll*(i-1)*(dp[i-1]+dp[i-2]);
}
cout<<dp[n]<<endl;
}
//二项式反演解
void solve2()
{
int n;
cin>>n;
ll fac=1;
for(int i=1;i<=n;i++)
{
fac*=i;
}
ll ans=fac;
ll cur=1;
for(int i=1;i<=n;i++)
{
cur*=i;
ans+=(i%2?-1:1)*fac/cur;
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve1();
}
return 0;
}
首先,这个题求的就是 n 个数的错排方案数,就是 1~n 都不在自己位置上的排列方案数。
先说 dp 的方法,考虑定义 dp[i] 为 i 个人的错排。那么对于当前人数 i,首先,肯定可以选定一个人,从这个人开始往其他 i-1 个人送信,那么就是 i-1 种不同的方案。对于每一种方案,若送往的那个人送回去了,那么之后就是剩下 i-2 个人送信,即 dp[i-2]。否则,因为此时对于这两个人,可以发现也是需要一封信送过来,一封信送出去。所以就可以将这两个人看作一个人,所以之后依赖的就是 dp[i-1]。
对于二项式反演解,设 n 个人错排的方案数为 f(n),那么如果发现这个不好求,就可以考虑进行转化。考虑定义 g(n) 为 n 个人的全排列,那么方案数显然就是 n 的阶乘。之后,考虑将全排列的所有方案,按照正好 i 个人不在自己位置划分。此时,f(n) 的定义就是指定 n 个指定的人,让他们不在自己位置的方案数。所以,对于正好 i 个人不在自己位置的方案数,就是先从 n 个人里选 i 个人,再乘以 f(i),即正好 i 个指定的人不在自己位置的方案数。所以就有 ,那么就可以通过第二种反演式求了。所以,将 g(n) 代入化简,有
,换元就可以得到
。
3.集合计数
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 1e9+7;
using Z = ModInt<M>;
constexpr int N = 1e6+5;
Comb<M>comb(N);
template<std::signed_integral U>
Z P(U n, U m) {
return comb.P(n, m);
}
template<std::signed_integral U>
Z C(U n, U m) {
return comb.C(n, m);
}
void solve()
{
int n,k;
cin>>n>>k;
vector<Z>g(n+1);
g[n]=2;
for(int i=n-1;i>=0;i--)
{
g[i]=g[i+1]*g[i+1];
}
for(int i=0;i<=n;i++)
{
g[i]=(g[i]-1)*C(n,i);
}
Z ans=0;
for(int i=k;i<=n;i++)
{
Z sign=(i-k)%2?-1:1;
ans+=sign*C(i,k)*g[i];
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
考虑定义 g(i) 为对于任选 i 个元素的所有方案,交集至少包含每次选择的这 i 个元素的总方案数。注意,这里认为一开始选择的元素不同方案就不同。那么首先,从 n 个元素里选 i 个,方案数就是 。对于每种方案,考虑让这 i 个元素往其他元素构成的集合里插。所以对于剩下的 n-i 个数,可以形成
个集合。又因为每个集合都可以选择要和不要,所以一共就有
种方案。最后,注意需要把空集的情况减去,因为没法往里插入。
对于式子 ,由于指数同余不能直接取模(其实可以费马小定理直接求的,不懂为啥左神这里不讲),所以考虑别的方法。观察式子,可以发现在从后往前求的过程中,前面这部分每次就是上一项的平方,那么就可以递推解决了。
那么之后考虑定义 f(i) 为交集正好 i 个元素的方案数。举个例子,对于 g(3),f(5) 表示交集正好五个元素的方案数,那么其从这五个元素里任选三个,都可以给 g(3) 产生一次贡献。所以就有 ,所以就有
,那么就可以使用反演形式四了。
所以就有一个二项式反演的 Trick:指定 k 个且至少的问题 和 恰好 k 个的问题可以通过二项式反演的第四种形式相互转化。所以在遇到恰好 k 个的计数问题时,直接求比较困难,那么就可以考虑先求出指定 k 个且至少的问题,然后二项式反演。
4.分特产
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 1e9+7;
using Z = ModInt<M>;
constexpr int N = 2e5+5;
Comb<M>comb(N);
template<std::signed_integral U>
Z P(U n, U m) {
return comb.P(n, m);
}
template<std::signed_integral U>
Z C(U n, U m) {
return comb.C(n, m);
}
void solve()
{
int n,m;
cin>>n>>m;
vector<int>a(m+1);
for(int i=1;i<=m;i++)
{
cin>>a[i];
}
vector<Z>g(n+1);
for(int i=0;i<n;i++)
{
g[i]=C(n,i);
for(int j=1;j<=m;j++)
{
g[i]*=C(a[j]+n-i-1,n-i-1);
}
}
g[n]=0;
Z ans=0;
for(int i=0;i<=n;i++)
{
int sign=i%2?-1:1;
ans+=sign*g[i];
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
还有由上个题得到启发,还是定义 g(i) 为一定要分完所有特产,任选 i 个人不获得特产的所有方案中,最终没获得特产的人中至少包含每次选择的这 i 个人的总方案数。那么再定义 f(i) 为一定分完所有特产,正好 i 个人没有特产的方案数。那么可以发现两者还是存在反演形式四的关系,所以答案 f(0) 就可以直接从 g(i) 得到。
对于 g(i),首先从 n 个人里任选 i 个人,方案数就是 。那么之后就是只在剩下的 n-i 个人里考虑,那么问题就变成了对于第 i 号特产,将 a[i] 个相同的物品分给 n-i 个人的方案数。那么这个就可以用隔板法解决,方案数
,所以连乘即可。
5.已经没有什么好害怕的了
已经没有什么好害怕的了......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 1e9+9;
using Z = ModInt<M>;
constexpr int N = 2e5+5;
Comb<M>comb(N);
template<std::signed_integral U>
Z P(U n, U m) {
return comb.P(n, m);
}
template<std::signed_integral U>
Z C(U n, U m) {
return comb.C(n, m);
}
void solve()
{
int n,k;
cin>>n>>k;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<ll>b(n+1);
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
if((n+k)%2)
{
cout<<0<<endl;
return ;
}
k=(n+k)/2;
sort(a.begin()+1,a.end());
sort(b.begin()+1,b.end());
vector<Z>cnts(n+1);
for(int i=1,j=0;i<=n;i++)
{
while(j+1<=n&&b[j+1]<a[i])
{
j++;
}
cnts[i]=j;
}
vector<vector<Z>>dp(n+1,vector<Z>(n+1));
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
dp[i][0]=dp[i-1][0];
for(int j=1;j<=i;j++)
{
dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*(cnts[i]-(j-1));
}
}
vector<Z>g(n+1);
for(int i=0;i<=n;i++)
{
g[i]=dp[n][i]*comb.fac[n-i];
}
Z ans=0;
for(int i=k;i<=n;i++)
{
int sign=(i-k)%2?-1:1;
ans+=sign*C(i,k)*g[i];
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
首先,设糖果大的有 a 对,药片大的有 b 对,那么就有关系 和
,所以相加就可以得到
。所以,如果 n 和 k 加起来是奇数,那必然是没有方案的。因为如果 n 是偶数 k 是奇数,那么糖果和药片必然全偶或者全奇,那么就必然不可能差值是个奇数。奇偶性相反时类似。那么问题就转化为了,要求糖果大的对数一定为
的配对数。
那么对于这种恰好的问题,还是考虑转化成指定且至少的问题,然后二项式反演。所以还是定义 g(i) 为任意指定 i 个糖果,最终至少保证选的这 i 个糖果形成糖果大的配对的总方案数。那么再定义 f(i) 为恰好 i 个糖果形成糖果大的配对的总方案数。
对于 g(i),考虑定义 dp[i][j] 为前 i 个糖果中,指定 j 个糖果,必须形成糖果大的对的方案数。那么首先考虑对两个数组排序,之后就可以用双指针找出比每个位置的糖果小的药片数量 cnts[i]。那么对于当前第 i 个糖果,若不指定,那么依赖的就是 dp[i-1][j]。若指定,那么依赖的就是 dp[i-1][j-1]。此时由于之前的糖果占用了比当前糖果小的 j-1 个药片,所以要乘上 cnts[i]-(j-1)。所以就能发现,dp[n][i] 是不关心没指定的情况的,而 g(i) 是关心没指定的情况的。也就是说,对于剩下的糖果,g(i) 认为其他糖果不同的配对属于不同的方案。所以剩下 n-i 个糖果和药片就可以看作一个全排列问题,那么就需要在 dp[n][i] 的基础上,再乘上 n-i 的阶乘。
6.游戏
这个反演形式四这么常用。
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 998244353;
using Z = ModInt<M>;
constexpr int N = 2e5+5;
Comb<M>comb(N);
template<std::signed_integral U>
Z P(U n, U m) {
return comb.P(n, m);
}
template<std::signed_integral U>
Z C(U n, U m) {
return comb.C(n, m);
}
void solve()
{
int n;
cin>>n;
int m=n/2;
string s;
cin>>s;
s=" "+s;
vector<vector<int>>adj(n+1);
for(int i=1,u,v;i<=n-1;i++)
{
cin>>u>>v;
adj[u].push_back(v);
adj[v].push_back(u);
}
vector<vector<Z>>dp(n+1,vector<Z>(m+1));
vector<int>sz(n+1);
vector<array<int,2>>belong(n+1);
auto dfs=[&](auto &&self,int u,int fa)->void
{
int cur=s[u]-'0';
sz[u]=1;
belong[u][cur]=1;
dp[u][0]=1;
//not contain u
for(auto v:adj[u])
{
if(v!=fa)
{
self(self,v,u);
vector<Z>ndp(m+1);
for(int a=0;a<=min(sz[u]/2,m);a++)
{
for(int b=0;b<=min(sz[v]/2,m-a);b++)
{
ndp[a+b]+=dp[u][a]*dp[v][b];
}
}
sz[u]+=sz[v];
belong[u][0]+=belong[v][0];
belong[u][1]+=belong[v][1];
dp[u]=ndp;
}
}
int cnt=belong[u][cur^1];
vector<Z>ndp(m+1);
ndp[0]=dp[u][0];
for(int i=1;i<=min(cnt,m);i++)
{
ndp[i]=dp[u][i]+dp[u][i-1]*(cnt-(i-1));
}
dp[u]=ndp;
};
dfs(dfs,1,0);
vector<Z>g(m+1);
for(int i=0;i<=m;i++)
{
g[i]=dp[1][i]*comb.fac[m-i];
}
vector<Z>f(m+1);
for(int k=0;k<=m;k++)
{
for(int i=k;i<=m;i++)
{
int sign=(i-k)%2?-1:1;
f[k]+=sign*C(i,k)*g[i];
}
}
for(int k=0;k<=m;k++)
{
cout<<f[k]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
对于恰好 k 个的问题,还是转化成指定 k 个且至少。所以定义 g(i) 为指定 i 个回合,保证至少指定的这 i 个回合非平局的方案数,再定义 f(i) 为正好 i 个回合非平局的方案数。
对于树上问题,还是考虑分析当前节点 u 和孩子节点 v 的关系,然后看能否 dp。所以考虑定义 dp[u][i] 为在 u 的子树内考虑,选出 i 对节点非平局,要求选出的对里节点都不同的方案数。那么不难发现 g(i) 可以从 dp[1][i] 中得到,又因为 dp[1][i] 只关注选出的方法数,不关注剩下的,所以要把剩下 m-i 个能形成的方案数乘进去。因为这个可以看作固定一个选择的顺序,然后另一个自由排列,所以方案数就是全排列,即 m-i 的阶乘。
对于这个树型 dp,是一个类似背包的过程。若当前节点 u 不参与,那么对于当前孩子 v,若之前考虑过的孩子中选了 a 对,当前孩子中选了 b 对,那么就能在原来 dp[u][a+b] 的基础上,加上两者的乘积。
重点是复杂度的证明。乍一看这样好像是 O(n^3) 的,但实际上是 O(n^2) 的。因为每次循环的上限是 sz[u] 的,那么就可以看作,每来到一个孩子 v,枚举所有之前的遍历过的子树内的所有节点,再枚举当前孩子子树内的所有节点。在这个过程中,可以看作每次产生一个点对 (x,y),而且是不会重复出现的,因为之后就都合并成一个子树了。那么由于一共有 O(n^2) 个点对,所以这个过程的复杂度其实是 O(n^2) 的。
若当前节点 u 必须参与,那么依赖的肯定就是不包含 u 的情况下,产生 i-1 对的方案数。在这个基础上,若当前 u 内有 a 个属于甲的节点,有 b 个属于乙的节点,其中 u 属于甲。那么因为之前有 i-1 个乙的节点被占用了,所以就只能选剩下 b-(i-1) 个节点了,所以再乘上 b-(i-1) 即可。
总结
太难了......