更新ing
菜鸡 w l j s s w l j s s 来讲组合数学啦。
组合数学博大精深,主要是爱数数的人上大学了 ,从模拟赛到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 n .
所以我们采用求 补集 的思想
有多少可能不会发生越狱呢?
第一个人有 m m 种可能的信仰。
从第二个人开始,每个人的信仰都要和上一个人不同,每个人有 m − 1 m − 1 种。
所以答案就是 m n − m × ( m − 1 ) n − 1 m n − m × ( 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 ;
}
设 d p [ i ] [ j ] d p [ i ] [ j ] 为第 i i 个人在第 j j 轮拿到球的方案数。
每次考虑一个人的求可能从哪个方向上传过来.
f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + f [ i + 1 ] [ j − 1 ] f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + f [ i + 1 ] [ j − 1 ]
当然 1 1 号和 n 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 3 个点就能组成,所以总的三元环个数是 C 3 n C n 3
我们采用求 补集 的思想,不同色的三元环有多少个?
考虑每个点,如果有 d d 条白边,那么就有 n − d − 1 n − d − 1 条黑边,两两组合就会产生 d × ( n − d − 1 ) d × ( n − d − 1 ) 个三元环
考虑这样我们求出来的是啥?考虑对于每个不同色的三元环,会被每对不同色的边各枚举一次,一共会被枚举 2 2 次.
所以/ 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 ;
}
简单 D P D P ,设 d p [ i ] [ j ] [ k ] d p [ i ] [ j ] [ k ] 为走了 k k 步,走到坐标 ( i , j ) ( i , j ) 的方案数。
转移的话直接从周围 4 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 ] f [ i ] 为当前 情况下 i i 的最短路计数
首先初始化 f [ 1 ] = 1 f [ 1 ] = 1
然后考虑求最短路的过程.
如果 d i s [ t o [ i ] ] > d i s [ x ] + 1 d i s [ t o [ i ] ] > d i s [ x ] + 1 ,由于最短路更新了, 当前情况 最短路只能由 x x 走过来,f [ t o [ i ] ] = f [ x ] f [ t o [ i ] ] = f [ x ]
如果 d i s [ t o [ i ] ] = d i s [ x ] + 1 d i s [ t o [ i ] ] = d i s [ x ] + 1 ,最短路没有更新, 当前情况 最短路也能由 x x 走过来,f [ t o [ i ] ] + = f [ x ] f [ t o [ 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 ;
}
首先每次背包一下答案是对的,但是复杂度太高。
对于这种有选取数量限制的计数题,我们通常枚举有哪些突破了数量限制,然后容斥。
对于这道题来说,我们先用四种面值做一个完全背包。
然后枚举哪些面值肯定会突破限制,这个可以通过 t m p − = c ∗ ( b + 1 ) t m p − = c ∗ ( b + 1 ) 来实现,也就是先选出来 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 a i = i ,那么剩下的 n − m n − m 个数就需要错排。
所以答案就是 C m n × f [ n − m ] C n m × 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 ] f [ i ] [ j ] 为用 1 1 ~ i i 组成的序列有j个逆序对的方案数。
我们将每个数一个一个插入原序列,考虑第 i i 个数放在原来的序列的哪个位置,由于之前的数都比 i i 小,所以如果插在 k k 个数后面,就会增加 k k 个逆序对。
所以 f [ i ] [ j ] = m i n ( i − 1 , j ) ∑ k = 0 f [ i − 1 ] [ j − k ] f [ i ] [ j ] = ∑ k = 0 m i n ( 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 ;
}
考虑容斥:合法方案:总方案-不合法方案
总方案很好求,可以 D P D P ,也可以用 ( n ∏ i = 1 ( 1 + m ∑ j = 1 a [ i ] [ j ] ) ) − 1 ( ∏ i = 1 n ( 1 + ∑ j = 1 m a [ i ] [ j ] ) ) − 1 来求。
如果一个方案不合法,一定是某个食材出现次数超过了 ⌊ k 2 ⌋ ⌊ k 2 ⌋ 次,并且能造成这个的最多只有 1 1 个食材。
枚举哪个食材不合法,D P D P 设 g [ i ] [ j ] [ k ] g [ i ] [ j ] [ k ] 为前 i i 个里面,一共选了 j j 个,其中 i d i d 选了 k k 个的方案数
时间复杂度 O ( m n 3 ) 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 j 和 k k 就有点多余,重新设 g [ i ] [ j ] g [ i ] [ j ] 为前 i i 个里,i d i d 选的比其他的多 j j 个时的方案数。
时间复杂度就成了 O ( m n 2 ) O ( m n 2 ) ,可以过,但是因为可能 j j 有可能是负数,所以需要整体下标加上 n 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 IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具