Loading

12.17 CW 模拟赛 T4. 记忆碎片

思路

转化题意, 问你在一个带权无向完全图中, 如何填上 \(w_i \in \left[1, \frac{n \cdot (n - 1)}{2} \right]\) , 使得其最小生成树上的边权为给定的 \(n - 1\) 个数

考虑模仿 \(\rm{kruskal}\) 的方式, 令 \(f_S\) 表示当前点集为 \(S\) , 每次转移, 如果当前边权需要在最小生成树中, 我们考虑连不联通的两点, 否则连联通的两点

这样只能通过 \(30 \%\) 的点

考虑优化

注意到我们其实不需要按照边来存储状态, 我们只需要知道连通块的大小即可, 考虑构造一种状态集合 \(\mathbb{S} = \{ (i, SZ_i) \}\) 表示大小为 \(SZ_i\) 的连通块有 \(i\) 个, 要求 \(\displaystyle \sum_{i = 1}^{\lvert \mathbb{S} \rvert} i \cdot SZ_i = n\)

这样我们对于每条树边才需要转移, 因为非树边不会对状态有影响

你发现状态的连通块个数顶多只有 \(\sqrt{n}\) 种, 再 \(\mathcal{O} (\sqrt{n})\) 求出变化后的状态, 需要转移 \(n\) 次, 枚举状态数 \(\mathcal{O} (n \mathop{P} (n))\) , 其中 \(\mathop{P} (n)\) 为分拆数
总时间复杂度 \(\mathcal{O} (n^2\sqrt{n}\mathop{P} (n))\) , 我认为可以通过

总结一下, 我们令 \(f_{i, \mathbb{S}}\) 表示考虑到第 \(i\) 条边, 当前的连通块状态为 \(\mathbb{S}\) 时的方案数, 令树边集合为 \(\mathbb{V}\)

\[f_{i, \mathbb{S}} = \begin{cases} \begin{aligned} & \sum_{a, b} \{ f_{i - 1, \mathbb{S}_{pre} } + SZ_a \cdot SZ_b , i \in \mathbb{V} \} \\ & f_{i - 1, \mathbb{S} } \cdot \sum_{j = 1}^{\lvert \mathbb{S} \rvert} \frac{SZ_j \cdot (SZ_j - 1)}{2} \end{aligned} \end{cases} \]

当然具体实现中使用刷表法

实现

框架

首先你至少要把状态预处理出来, 然后就按照树边和非树边干就完了

代码

#include <bits/stdc++.h>
const int MAXM = 41 * 41;
const int MAXP = 37400;
const int MOD = 1e9 + 7;

int add(int a, int b) { return (a + b >= MOD) ? a + b - MOD : a + b; }
int dec(int a, int b) { return (a - b < 0) ? a - b + MOD : a - b; }
int mul(int a, int b) { return (1LL * a * b) % MOD; }
void pluson(int &a, int b) {a = add(a, b);}


int n;
bool V[MAXM];

std::map<std::vector<int>, int> S;
std::map<int, std::vector<int>> Sback;

std::vector<int> nowS, Snext;
int id = 0;
int C[MAXP];
void init(int x, int v){
	if(v == 0) {
		if(x == 0) {
			S[nowS] = ++id, Sback[id] = nowS;
			for(int i = 0; i < nowS.size(); i++) C[id] += nowS[i] * (nowS[i] - 1) / 2;
		}
		return ;
	}

	init(x, v - 1);
	if(x >= v){
		nowS.push_back(v);
		init(x - v, v);
		nowS.pop_back();
	}
}

int f[MAXM][MAXP];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i < n; i++) {
        int read; scanf("%d", &read);
        V[read] = true;
    }
    int m = n * (n - 1) / 2;
    
    /*初始化状态*/
    init(n, n);

    f[0][1] = 1;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= id; j++)
            if (f[i - 1][j]) {
                if (V[i]) {
                    nowS = Sback[j];
                    for (int a = 0; a < nowS.size(); a++)
                        for (int b = a + 1; b < nowS.size(); b++) {
                            Snext.clear();
                            Snext.push_back(nowS[a] + nowS[b]);
                            for (int k = 0; k < nowS.size(); k++)
                                if (k != a && k != b)
                                    Snext.push_back(nowS[k]);
                            std::sort(Snext.begin(), Snext.end());
                            std::reverse(Snext.begin(), Snext.end());
                            pluson(f[i][S[Snext]], mul(f[i - 1][j], mul(nowS[a], nowS[b])));
                        }
                }
                else
                    pluson(f[i][j], mul(f[i - 1][j], C[j] - i + 1));
            }

    int Ans = 0;
    for (int i = 1; i <= id; i++)
        pluson(Ans, f[m][i]);
    printf("%d", Ans);


    return 0;
}

总结

善于模仿算法的实现方式

优化状态, 首先找到算法的本质, 然后只存储需要用到的性质, 本题中利用组合数学简化了一些状态, 具体的, 连边这一类问题, 通过连通块的组合数即可求出

posted @ 2024-12-17 21:30  Yorg  阅读(14)  评论(0)    收藏  举报