Gravatar
终焉折枝
积分:1649
提交:219 / 388
真的更好的阅读体验:https://www.cnblogs.com/To-Carpe-Diem/p/19648133

题目4323  十二重计数法(第一关) AAAAA      1      评论
2026-02-27 20:33:10    
Gravatar
终焉折枝
积分:1649
提交:219 / 388
真的更好的阅读体验:https://www.cnblogs.com/To-Carpe-Diem/p/19648133

题目4324  十二重计数法(第二关) AAAAA      1      评论
2026-02-27 20:32:11    
Gravatar
HXF
积分:7110
提交:1302 / 2752

f[i][j]统一定义为i个小球放到j个盒子里的方案数

1.m^n

2.m!/(m-n)!

3.f[i][j]=j*f[i-1][j]+(m-j+1)*f[i-1][j-1]

  考虑容斥可以O(n)

4.第二类斯特林数S[n][1]+...+S[n][m]

  O(n^2)直接递推S[i][j]=S[i-1][j-1]+j*S[i-1][j]

  O(nlogn)NTT,斯特林数反演

5.[n<=m]

6.S[n][m]

7.C(n+m-1,m-1)

8.C(m,n)

9.C(n-1,m-1)

10.f[i][j]=f[i][j-1]+f[i-j][j]

    考虑球的数量>=根号n的盒子的个数<=根号n个可以分块O(n^1.5)

    五边形数定理可优化到O(nlogn)

11.[n<=m]

12.f[i][j]=f[i-1][j-1]+f[i-j][j]  自然数拆分

    同10



题目4323  十二重计数法(第一关)      评论
2026-02-27 15:23:31    
Gravatar
终焉折枝
积分:1649
提交:219 / 388

更好的阅读体验:https://www.cnblogs.com/To-Carpe-Diem/p/19645638

更好的阅读体验(加强版):https://www.cnblogs.com/To-Carpe-Diem/p/19645952

$\newcommand{\binom}[2]{{#1 \choose #2}}$

大意

$n$ 对情侣,恰好 $k$ 对在 $2 \times n$ 的电影院中坐一起的方案数。


思路

很好的题目,使得我的大脑旋转。

首先我们先定义函数 $f(n, k)$ 表示 $n$ 对情侣有 $k$ 对恰好坐到一块的方案数。首先我们要从 $n$ 对情侣中选择 $k$ 对,这个是 $\binom{n}{k}$ 的,再从 $n$ 排选择 $k$ 排,这个也是 $\binom{n}{k}$ 的,考虑到这 $k$ 对情侣间也有先后顺序,贡献就是 $k!$,两人可以换位,贡献是 $2 ^ k$,剩下有 $n - k$ 对情侣,我们只需要将其错排即可。

定义 $g(m)$ 表示 $m$ 对情侣错排的方案数,我们考虑这个怎么求。这个我们可以利用容斥原理,我们考虑直接计算至少 $j$ 对和睦的方案数,那么这个的贡献与上面的类似,应当是 $\binom{m}{j} ^ 2 j! 2 ^ j (2(m - j))!$,那么由容斥可知:$g(m) = \sum_{j = 0} ^ {m}(-1) ^ j \binom{m}{j} ^ 2 j! 2 ^ j (2(m - j))!$

那么我们合并一下整个式子,就是:$f(n, k) = \binom{n}{k} ^ 2 k ! 2 ^ k g(n - k) = \binom{n}{k} ^ 2 k ! 2 ^ k \sum_{j = 0} ^ {n - k}(-1) ^ j \binom{n - k}{j} ^ 2 j! 2 ^ j (2(n - k - j))!$

这个式子我们是可以直接求的,于是就完成了。

接下来进入正题,我们注意到刚刚的弱化版内的 $f(n, k) = \binom{n}{k} ^ 2 k ! 2 ^ k D(n - k) $,且 $D(n) = \sum_{i = 0} ^ {n} (-1) ^ i\binom{n}{i} ^ 2 i! 2 ^ i (2(n - i))!$,我们考虑对于 $D(n)$ 进行化简.

$\sum_{i = 0} ^ {n}(-1) \binom{n}{i} ^ 2 i! 2 ^ i (2(n - i)) != (n!) ^ 2 \sum_{i = 0} ^ n \frac{(2(n - i))!}{(n - i)! ^ 2} \frac{(-2) ^ i}{i!}$

这里我们就可以直接做卷积了,但是问题在于这个题目要求的复杂度太过苛刻,卷积无法通过此题,我们考虑构造生成函数。

我们设:$F(x) = (n!) ^ 2 \sum_{i = 0} ^ n \frac{(2(n - i))!}{(n - i)! ^ 2} \frac{(-2) ^ i}{i!} x^i,G[n] = \frac{(2n)!}{n!^2},P[n] = \frac{(-2) ^ i}{n !}$。那么,由离散卷积可知:$F(x) = G(x) * P(x)$

接下来,我们只需要分别对于 $G(x)$ 和 $P(x)$ 化简即可。首先,对于 $G(x)$ 来说,我们可以利用**广义二项式定理**,那么什么是广义二项式定理呢?对于任意实数 $\alpha$,都有 $(1 + z) ^ \alpha = \sum_{i = 0} ^ {\infty} \binom{\alpha}{i} z ^ i$,那么说我们就可以化简 $G(x)$ 了:$G(x) = \sum_{i = 0} \frac{(-2x) ^ i}{i!} x ^ i = \sum_{i = 0} \binom{2i}{i}x^i = \frac{1}{\sqrt{1 - 4x}}$对于 $P(x)$ 来说,直接化简:$P(x) = \sum_{i = 0} \frac{(-2x)!}{i!} = e ^ {-2x}$

经过化简,我们可以最终得到:$F(x) = \frac{e ^ {-2x}}{\sqrt{1 - 4x}}$对于这个生成函数,为了得到与自身相关的式子进行递推,我们选择对其求导。$F'(x) = \frac{8x * e ^ {-2x}}{(1 - 4x) ^ \frac{3}{2}} = \frac{8x}{1 - 4x} F(x)$整理一下:$F'(x) = 4xF'(x) + 8xF(x)$。提取 $[x ^ n]$ 的系数:$F'[n] = 4xF'[n - 1] + 8xF[n - 1]$。稍施小计,我们可以直接得到关于 $F[n]$ 的递推式:$(n + 1)F[n + 1] = 4nF[n] + 8F[n - 1]$

你可能以为万事大吉了,但是问题是我们还有个 $(n!) ^ 2$ 的系数在最开始被我们提出去了,我们要把这玩意弄回来,带入 $F[n] = \frac{D(n)}{n!} ^ 2$:$(n + 1)\frac{D[n + 1]}{(n + 1)!} = 4n \frac{D[n]}{n!^2} + 8 \frac{D[n - 1]}{(n - 1)! ^ 2}$我们就可以得到关于 $D[n]$ 的递推式:$D[n + 1] = 4n(n + 1)D[n] + 8n ^ 2(n + 1)D[n - 1]$

直接 $O(n)$ 递推即可。


代码

#include<iostream>
using namespace std;

#define int long long
const int MOD = 998244353;
const int MAXN = 2e3 + 5;

int C[MAXN][MAXN];
int fac[MAXN];
int g[MAXN], pow2[MAXN];

void init(){
	C[0][0] = 1;
	for(int i = 1;i < MAXN;i ++){
		C[i][0] = C[i][i] = 1;
		for(int j = 1;j < i;j ++){
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
		}
	}
	fac[0] = 1;
	for(int i = 1;i < MAXN;i ++){
		fac[i] = (fac[i - 1] * i) % MOD;
	}
	pow2[0] = 1;
	for(int i = 1;i < MAXN;i ++){
		pow2[i] = pow2[i - 1] * 2 % MOD;
	}
	for(int m = 0;m <= 1000;m ++){
		g[m] = 0;
		for(int j = 0;j <= m;j ++){
			int a = (j % 2 == 0) ? 1 : MOD - 1;
			int res = C[m][j] * C[m][j] % MOD;
			res = (res * fac[j]) % MOD;
			res = (res * pow2[j]) % MOD;
			res = (res * fac[2 * (m - j)]) % MOD;
			res = (res * a) % MOD;
			g[m] = (g[m] + res) % MOD;
		}
 	}
	return;
}

int f(int n, int k){
	int res = (C[n][k] * C[n][k]) % MOD;
	res = (res * fac[k]) % MOD;
	res = (res * pow2[k]) % MOD;
	res = (res * g[n - k]) % MOD;
	return res;
}

int T, n, k;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	init();

	cin >> T;

	while(T --){
		cin >> n;
		for(int i = 0;i <= n;i ++){
			cout << f(n, i) << '\n';
		}
	}

	return 0;
}


题目3353  情侣?给我烧了 A      1      评论
2026-02-27 11:52:19    
Gravatar
终焉折枝
积分:1649
提交:219 / 388

更好的阅读体验:https://www.cnblogs.com/To-Carpe-Diem/p/19617614


大意

每一个小朋友在得到自己喜欢的饮料和食物的时候,就实现了其愿望,问最多能实现多少奶牛的愿望。


思路

首先我们考虑这样的三元关系 $(饮料,小朋友,食物)$,这样的三元关系,如果我们直接在中间连边去跑最大流是否有问题呢?

显然是有问题的,如下图:

这样我们发现个奇怪的问题,一个小朋友对应的喜欢的食物和饮料其实是很多的,但是这样会使得不同的饮料与食物都对一个小朋友产生影响,现在我们需要做的是给一个点上约束,而这种技巧叫做拆点。

我们考虑将每个小朋友拆为 $in$ 和 $out$,在 $in$ 和 $out$ 间连容量为 $1$ 的边,这样我们就可以保证每个小朋友只被算一次,如下图:

在拆完点之后直接跑最大流即可。


代码

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAX_N = 1000;
const int MAX_M = 100000;
struct edge {
    int v, c, next;
} e[MAX_M];
int p[MAX_N], eid;
void init() {
    memset(p, -1, sizeof(p));
    eid = 0;
}
void insert(int u, int v, int c) {
    e[eid].v = v;
    e[eid].next = p[u];
    e[eid].c = c;
    p[u] = eid++;
}
void addedge(int u, int v, int c) {
    insert(u, v, c);
    insert(v, u, 0);
}
int S, T;
int d[MAX_N];
bool bfs() {
    memset(d, -1, sizeof(d));
    queue<int> q;
    q.push(S);
    d[S] = 0;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = p[u]; i != -1; i = e[i].next) {
            int v = e[i].v;
            if (e[i].c > 0 && d[v] == -1) {
                q.push(v);
                d[v] = d[u] + 1;
            }
        }
    }
    return (d[T] != -1);
}
int dfs(int u, int flow) {
    if (u == T) {
        return flow;
    }
    int res = 0;
    for (int i = p[u]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if (e[i].c > 0 && d[u] + 1 == d[v]) {
            int tmp = dfs(v, min(flow, e[i].c));
            flow -= tmp;
            e[i].c -= tmp;
            res += tmp;
            e[i ^ 1].c += tmp;
            if (flow == 0) {
                break;
            }
        }
    }
    if (res == 0) {
        d[u] = -1;
    }
    return res;
}
int Dinic() {
    int res = 0;
    while (bfs()) {
        res += dfs(S, INF);
    }
    return res;
}
int main() {
    init();
    int n, m, k;
    cin >> n >> m >> k;
    for(int i = 1;i <= n;i ++){
        addedge(m + i, m + n + i, 1);
        int a, b; cin >> a >> b;
        for(int j = 0;j < a;j ++){
            int x; cin >> x;
            addedge(x, m + i, 1);
        }
        for(int j = 0;j < b;j ++){
            int x; cin >> x;
            addedge(m + n + i, m + 2 * n + x, 1);
        }
    }
    S = 0; T = m + 2 * n + k + 1;
    for(int i = 1;i <= m;i ++){
        addedge(S, i, 1);
    }
    for(int i = 1;i <= k;i ++){
        addedge(m + 2 * n + i, T, 1);
    }
    cout << Dinic() << endl;
    return 0;
}


题目2727  [USACO Open07]牛的进餐      4      2 条 评论
2026-02-26 12:01:19    
Gravatar
终焉折枝
积分:1649
提交:219 / 388

更好的阅读体验:https://www.cnblogs.com/To-Carpe-Diem/p/19617931


大意

$N$ 个顾客,$M$ 个技术人员,不同的技术人员对不同的车的修理时间是不同的,那么求顾客们的最短等待时间。


思路

我们发现,对于这个题来说,很像排队接水(~~不是~~)。

考虑每个技术人员的时候,我们发现如下规律,一个技术人员对答案所作出的贡献是:

$最后一个顾客时间 \times 1 + 倒数第二个顾客时间 \times 2  ... 倒数第 k 个顾客时间 \times k$

我们发现,每个技术人员在不同时刻的状态是不一样的,对答案所做出的贡献也是,不一样的,但是每个状态下的技术人员只能给一个人修车,但是顾客可以自行选择某个状态下的某个技术人员。

这个问题我们可以用网络流来解决,我们对于每个技术人员,将其拆分为 $n$ 个状态,对于正在给倒数第 $k$ 个顾客修车的师傅 $i$ 对顾客 $j$ 连一条容量为 $1$,费用为 $t_{i, j} \times k$,对于源点向每个技术人员的每个状态连容量为 $1$,花费为 $0$ 的边,对于每个顾客都向汇点 $T$ 去连上容量为 $1$,花费为 $0$ 的边。

这样跑出的最小费用最大流就一定会自己最优的选择出最短的排队修车的时间。


代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1005;
const int MAXM = 100005;
struct node{
    int u, v, nxt, c, w;
}e[MAXM * 2];

int S, T;
int tot, h[MAXN], d[MAXN], pre[MAXN];
bool vis[MAXN];

void init(){
    tot = 0;
    memset(h, -1, sizeof(h));
}

void add(int u, int v, int c, int w){
    e[tot] = {u, v, h[u], c, w};
    h[u] = tot ++;
    e[tot] = {v, u, h[v], 0, -w};
    h[v] = tot ++;
}

bool spfa(){
    memset(vis, 0, sizeof(vis));
    memset(d, 0x3f, sizeof(d));
    memset(pre, -1, sizeof(pre));
    queue<int> q;
    q.push(S);
    d[S] = 0;
    vis[S] = true;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = false;
        for(int i = h[u];~i;i = e[i].nxt){
            int v = e[i].v;
            if(e[i].c > 0){
                if(d[v] > d[u] + e[i].w){
                    d[v] = d[u] + e[i].w;
                    pre[v] = i;
                    if(!vis[v]){
                        q.push(v);
                        vis[v] = true;
                    }
                }
            }
        }
    }
    return (d[T] != 0x3f3f3f3f);
}

int EK(){
    int res = 0;
    while(spfa()){
        int Min = 1e9;
        for(int i = T;i != S;i = e[pre[i]].u){
            Min = min(Min, e[pre[i]].c);
        }
        for(int i = T;i != S;i = e[pre[i]].u){
            e[pre[i]].c -= Min;
            e[pre[i] ^ 1].c += Min;
            res += e[pre[i]].w * Min;
        }
    }
    return res;
}

int m, n;

int main(){
    // freopen("repair.in", "r", stdin);
    // freopen("repair.out", "w", stdout);
    cin >> m >> n;
    init();

    S = 0, T = n + m * n + 1;

    for(int i = 1;i <= n;i ++){
        add(S, i, 1, 0);
    }

    for(int i = 1;i <= n;i ++){
        for(int j = 1;j <= m;j ++){
            int t_cost;
            cin >> t_cost;
            for(int k = 1;k <= n;k ++){
                int pos = n + (j - 1) * n + k;
                add(i, pos, 1, k * t_cost);
            }
        }
    }

    for(int j = 1;j <= m;j ++){
        for(int k = 1;k <= n;k ++){
            add(n + (j - 1) * n + k, T, 1, 0);
        }
    }

    int ans = EK();
    printf("%.2f\n", (double)ans / n);

    return 0;
}


题目1383  [SCOI 2007] 修车      4      评论
2026-02-26 11:54:51