更新ing
菜鸡 \(wljss\) 来讲组合数学啦。
组合数学博大精深,主要是爱数数的人上大学了 ,从模拟赛到NOI都出现过。
一些技巧可以看这里
其实看完上边那个也就没啥好说的了
上例题吧
这里 有讲解,直接放代码233
点我展开代码 #include <iostream>
using namespace std;
long long ans[21 ];
int main ()
{
int n;
cin >> n;
ans[2 ] = 1 ; ans[3 ] = 2 ;
for (int i = 4 ; i <= 20 ; ++i)
ans[i] = (i - 1 ) * (ans[i - 1 ] + ans[i - 2 ]);
cout << ans[n];
return 0 ;
}
首先我们可以发现总的方案很好求,就是 \(m^n\) .
所以我们采用求 补集 的思想
有多少可能不会发生越狱呢?
第一个人有 \(m\) 种可能的信仰。
从第二个人开始,每个人的信仰都要和上一个人不同,每个人有 \(m-1\) 种。
所以答案就是 \(m^n - m \times (m-1)^{n-1}\)
点我展开代码 #include <iostream>
#include <cstdio>
#define LL long long
using namespace std;
LL n, m;
const LL mod = 100003 ;
LL ksm (LL a, LL b, LL mod)
{
LL ans = 1 ;
for (; b; b >>= 1 , a = a * a % mod) {if (b & 1 )ans = ans * a % mod;}
return ans;
}
int main ()
{
cin >> m >> n;
cout << (ksm (m, n, mod) + mod - m * ksm (m - 1 , n - 1 , mod) % mod) % mod;
return 0 ;
}
设 \(dp[i][j]\) 为第 \(i\) 个人在第 \(j\) 轮拿到球的方案数。
每次考虑一个人的求可能从哪个方向上传过来.
\(f[i][j]=f[i-1][j-1]+f[i+1][j-1]\)
当然 \(1\) 号和 \(n\) 号要特殊处理一下。
点我展开代码 #include <iostream>
using namespace std;
int f[31 ][31 ];
int main ()
{
int n, m;
cin >> n >> m;
f[1 ][0 ] = 1 ;
for (int i = 1 ; i <= m; ++i)
{
f[1 ][i] = f[2 ][i - 1 ] + f[n][i - 1 ];
for (int j = 2 ; j <= n - 1 ; ++j)
f[j][i] = f[j - 1 ][i - 1 ] + f[j + 1 ][i - 1 ];
f[n][i] = f[n - 1 ][i - 1 ] + f[1 ][i - 1 ];
}
cout << f[1 ][m];
return 0 ;
}
总的三元环个数很好求,任意选出 \(3\) 个点就能组成,所以总的三元环个数是 \(C_n^3\)
我们采用求 补集 的思想,不同色的三元环有多少个?
考虑每个点,如果有 \(d\) 条白边,那么就有 \(n-d-1\) 条黑边,两两组合就会产生 \(d \times (n-d-1)\) 个三元环
考虑这样我们求出来的是啥?考虑对于每个不同色的三元环,会被每对不同色的边各枚举一次,一共会被枚举 \(2\) 次.
所以\(/2\) 后才是真正的不同色三元环个数。
点我展开代码 #include <iostream>
#include <cstdio>
using namespace std;
int n, m;
long long tmp;
const int N = 100010 ;
int du[N];
int main ()
{
cin >> n >> m;
for (int i = 1 , x, y; i <= m; ++i)
scanf ("%d%d" , &x, &y), ++du[x], ++du[y];
for (int i = 1 ; i <= n; ++i)tmp += (long long )du[i] * (n - du[i] - 1 );
cout << (long long )n*(n - 1 )*(n - 2 ) / 6 - tmp / 2 ;
return 0 ;
}
简单 \(DP\) ,设 \(dp[i][j][k]\) 为走了 \(k\) 步,走到坐标 \((i,j)\) 的方案数。
转移的话直接从周围 \(4\) 个方向转移就行了。
点我展开代码 #include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m, t, x1, y1, x2, y2;
char mp[105 ][105 ];
int l[105 ][105 ];
long long dp[105 ][105 ][20 ];
int fx[10 ], fy[10 ];
int main ()
{
cin >> n >> m >> t;
for (int i = 1 ; i <= n; ++i)scanf ("%s" , mp[i] + 1 );
for (int i = 1 ; i <= n; ++i)
for (int j = 1 ; j <= m; ++j)
if (mp[i][j] == '*' )l[i][j] = 1 ;
cin >> x1 >> y1 >> x2 >> y2;
fx[1 ] = 0 ; fx[2 ] = 0 ; fx[3 ] = 1 ; fx[4 ] = -1 ;
fy[1 ] = 1 ; fy[2 ] = -1 ; fy[3 ] = 0 ; fy[4 ] = 0 ;
dp[x1][y1][0 ] = 1 ;
for (int k = 1 ; k <= t; ++k)
for (int i = 1 ; i <= n; ++i)
for (int j = 1 ; j <= m; ++j)
for (int d = 1 ; d <= 4 ; ++d)
if (!l[i + fx[d]][j + fy[d]])
dp[i][j][k] += dp[i + fx[d]][j + fy[d]][k - 1 ];
printf ("%lld" , dp[x2][y2][t]);
fclose (stdin); fclose (stdout);
return 0 ;
}
普通求最短路的话一定要用DIJ,SPFA早就死了。
设 \(f[i]\) 为当前 情况下 \(i\) 的最短路计数
首先初始化 \(f[1]=1\)
然后考虑求最短路的过程.
如果 \(dis[to[i]] > dis[x] + 1\) ,由于最短路更新了, 当前情况 最短路只能由 \(x\) 走过来,\(f[to[i]] = f[x]\)
如果 \(dis[to[i]] = dis[x] + 1\) ,最短路没有更新, 当前情况 最短路也能由 \(x\) 走过来,\(f[to[i]] += f[x]\)
跑最短路的时候更新就行了.
点我展开代码 #include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#define pr pair<int,int>
using namespace std;
int n, m, tot;
const int N = 1000010 , M = 2000010 , mod = 100003 ;
int head[N], to[M << 1 ], nt[M << 1 ], dis[N], f[N], vis[N];
priority_queue<pr>q;
void add (int f, int t)
{
to[++tot] = t; nt[tot] = head[f]; head[f] = tot;
}
int main ()
{
cin >> n >> m;
for (int i = 1 , x, y; i <= m; ++i)
scanf ("%d%d" , &x, &y), add (x, y), add (y, x);
memset (dis, 0x3f , sizeof (dis));
dis[1 ] = 0 ; f[1 ] = 1 ; q.push (pr (0 , 1 ));
while (!q.empty ())
{
int x = q.top ().second; q.pop ();
if (vis[x])continue ; vis[x] = 1 ;
for (int i = head[x]; i; i = nt[i])
if (dis[to[i]] == dis[x] + 1 ) {(f[to[i]] += f[x]) %= mod;}
else if (dis[to[i]] > dis[x] + 1 )
{
dis[to[i]] = dis[x] + 1 ; f[to[i]] = f[x];
q.push (pr (-dis[to[i]], to[i]));
}
}
for (int i = 1 ; i <= n; ++i)printf ("%d\n" , f[i]);
return 0 ;
}
首先每次背包一下答案是对的,但是复杂度太高。
对于这种有选取数量限制的计数题,我们通常枚举有哪些突破了数量限制,然后容斥。
对于这道题来说,我们先用四种面值做一个完全背包。
然后枚举哪些面值肯定会突破限制,这个可以通过 \(tmp -= c * (b + 1)\) 来实现,也就是先选出来 \(b+1\) 个,再怎么选都会突破限制。
答案并不是 有0个强制突破限制的情况,因为虽然没有强制突破的情况,但因为随便选还是有突破的情况。
所以我们需要减去强制有1个突破的情况。
然后会发现减的有点多,要加上强制有2个突破的情况.以此类推。
点我展开代码 #include <iostream>
#include <cstdio>
using namespace std;
int n, s, opt;
long long ans, tmp;
int b[5 ], c[5 ];
long long f[100010 ];
int main ()
{
cin >> c[1 ] >> c[2 ] >> c[3 ] >> c[4 ] >> n;
f[0 ] = 1 ;
for (int i = 1 ; i <= 4 ; ++i)
for (int j = c[i]; j <= 100000 ; ++j)f[j] += f[j - c[i]];
while (n--)
{
scanf ("%d%d%d%d%d" , &b[1 ], &b[2 ], &b[3 ], &b[4 ], &s);
ans = 0 ;
for (int i = 0 ; i < (1 << 4 ) - 1 ; ++i)
{
tmp = s; opt = 1 ;
for (int j = 1 ; j <= 4 ; ++j)
if ((i >> (j - 1 )) & 1 )tmp -= 1ll * c[j] * (b[j] + 1 ), opt = -opt;
if (tmp < 0 )continue ;
ans += opt * f[tmp];
}
cout << ans << '\n' ;
}
return 0 ;
}
首先枚举哪些位置满足 \(a_i=i\) ,那么剩下的 \(n-m\) 个数就需要错排。
所以答案就是 \(C_n^m \times f[n-m]\)
点我展开代码 #include <iostream>
#include <cstdio>
#define LL long long
using namespace std;
int T, n, m;
const int N = 1000010 , M = 1000000 , mod = 1e9 + 7 ;
LL jc[N], inv[N], f[N];
LL ksm (LL a, LL b, LL mod)
{
LL res = 1 ;
for (; b; b >>= 1 , a = a * a % mod)
if (b & 1 )res = res * a % mod;
return res;
}
void YYCH ()
{
jc[0 ] = jc[1 ] = inv[0 ] = inv[1 ] = 1 ;
for (int i = 2 ; i <= M; ++i)jc[i] = jc[i - 1 ] * i % mod;
inv[M] = ksm (jc[M], mod - 2 , mod);
for (int i = M - 1 ; i >= 1 ; --i)inv[i] = inv[i + 1 ] * (i + 1 ) % mod;
f[0 ] = 1 ; f[1 ] = 0 ; f[2 ] = 1 ; f[3 ] = 2 ;
for (int i = 4 ; i <= M; ++i)f[i] = (i - 1 ) * (f[i - 1 ] + f[i - 2 ]) % mod;
}
LL C (int n, int m) {return jc[n] * inv[m] % mod * inv[n - m] % mod;}
int main ()
{
YYCH ();
cin >> T;
while (T--)
{
scanf ("%d%d" , &n, &m);
printf ("%lld\n" , C (n, m)*f[n - m] % mod);
}
return 0 ;
}
设 \(f[i][j]\) 为用 \(1\) ~ \(i\) 组成的序列有j个逆序对的方案数。
我们将每个数一个一个插入原序列,考虑第 \(i\) 个数放在原来的序列的哪个位置,由于之前的数都比 \(i\) 小,所以如果插在 \(k\) 个数后面,就会增加 \(k\) 个逆序对。
所以 \(\displaystyle f[i][j]=\sum_{k=0}^{min(i-1,j)}f[i-1][j-k]\)
发现后面那一段是连续的一段,可以用前缀和优化一下。
点我展开代码 #include <iostream>
#include <cstdio>
using namespace std;
int n, k, tot = 0 , p = 10000 ;
int ans[1001 ][1001 ];
int main ()
{
cin >> n >> k;
ans[1 ][0 ] = 1 ;
for (int i = 2 ; i <= n; ++i)
{
tot = 0 ;
for (int j = 0 ; j <= k; ++j)
{
tot += ans[i - 1 ][j];
ans[i][j] = tot % p;
if (j >= i - 1 )
{
tot = tot - ans[i - 1 ][j - i + 1 ];
tot = (tot + p) % p;
}
}
}
cout << ans[n][k];
return 0 ;
}
考虑容斥:合法方案:总方案-不合法方案
总方案很好求,可以 \(DP\) ,也可以用 \(\displaystyle (\prod_{i=1}^{n} (1 + \sum_{j=1}^{m}a[i][j]))-1\) 来求。
如果一个方案不合法,一定是某个食材出现次数超过了 \(\lfloor \frac{k}{2} \rfloor\) 次,并且能造成这个的最多只有 \(1\) 个食材。
枚举哪个食材不合法,\(DP\) 设 \(g[i][j][k]\) 为前 \(i\) 个里面,一共选了 \(j\) 个,其中 \(id\) 选了 \(k\) 个的方案数
时间复杂度 \(O(m n^3)\) ,只有84分
点我展开代码 #include <iostream>
#include <cstdio>
#define LL long long
using namespace std;
int n, m, ans;
const int N = 105 , M = 2005 , mod = 998244353 ;
int a[N][M];
LL f[N][N], g[N][N][N];
void solve1 ()
{
int tmp;
f[0 ][0 ] = 1 ;
for (int i = 1 ; i <= n; ++i)
{
f[i][0 ] = 1 ; tmp = 0 ;
for (int j = 1 ; j <= m; ++j)(tmp += a[i][j]) %= mod;
for (int j = 1 ; j <= i; ++j)f[i][j] = (f[i - 1 ][j] + f[i - 1 ][j - 1 ] * tmp) % mod;
}
}
void solve2 (int id)
{
int tmp;
g[0 ][0 ][0 ] = 1 ;
for (int i = 1 ; i <= n; ++i)
{
g[i][0 ][0 ] = 1 ; tmp = 0 ;
for (int j = 1 ; j <= m; ++j)
if (j != id)(tmp += a[i][j]) %= mod;
for (int j = 1 ; j <= i; ++j)
{
for (int k = 0 ; k <= j; ++k)g[i][j][k] = g[i - 1 ][j][k];
for (int k = 0 ; k <= j; ++k)(g[i][j][k] += g[i - 1 ][j - 1 ][k] * tmp) %= mod;
for (int k = 1 ; k <= j; ++k)(g[i][j][k] += g[i - 1 ][j - 1 ][k - 1 ] * a[i][id]) %= mod;
}
}
}
int main ()
{
cin >> n >> m;
for (int i = 1 ; i <= n; ++i)
for (int j = 1 ; j <= m; ++j)scanf ("%d" , &a[i][j]);
solve1 ();
for (int i = 1 ; i <= m; ++i)
{
solve2 (i);
for (int j = 1 ; j <= n; ++j)
for (int k = j / 2 + 1 ; k <= j; ++k)
(f[n][j] -= g[n][j][k]) %= mod;
}
for (int i = 1 ; i <= n; ++i)(ans += f[n][i]) %= mod;
cout << (ans % mod + mod) % mod;
return 0 ;
}
发现同时记录 \(j\) 和 \(k\) 就有点多余,重新设 \(g[i][j]\) 为前 \(i\) 个里,\(id\) 选的比其他的多 \(j\) 个时的方案数。
时间复杂度就成了 \(O(mn^2)\) ,可以过,但是因为可能 \(j\) 有可能是负数,所以需要整体下标加上 \(n\)
点我展开代码 #include <iostream>
#include <cstring>
#include <cstdio>
#define LL long long
using namespace std;
int n, m, ans;
const int N = 105 , M = 2005 , mod = 998244353 ;
int a[N][M];
LL f[N][N], s[N], g[N][N << 1 ];
void solve1 ()
{
ans = 1 ;
for (int i = 1 ; i <= n; ++i)
{
for (int j = 1 ; j <= m; ++j)(s[i] += a[i][j]) %= mod;
ans = (LL)ans * (s[i] + 1 ) % mod;
}
--ans;
}
void solve2 (int id)
{
int tmp;
g[0 ][n] = 1 ;
for (int i = 1 ; i <= n; ++i)
{
tmp = (s[i] - a[i][id]) % mod;
for (int j = 0 ; j <= 2 * n; ++j)g[i][j] = g[i - 1 ][j];
for (int j = 0 ; j <= 2 * n - 1 ; ++j)(g[i][j] += g[i - 1 ][j + 1 ] * tmp) %= mod;
for (int j = 1 ; j <= 2 * n; ++j)(g[i][j] += g[i - 1 ][j - 1 ] * a[i][id]) %= mod;
}
}
int main ()
{
cin >> n >> m;
for (int i = 1 ; i <= n; ++i)
for (int j = 1 ; j <= m; ++j)scanf ("%d" , &a[i][j]);
solve1 ();
for (int i = 1 ; i <= m; ++i)
{
solve2 (i);
for (int j = 1 ; j <= n; ++j)(ans -= g[n][n + j]) %= mod;
}
cout << (ans % mod + mod) % mod;
return 0 ;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步