[USACO21JAN] Sum of Distances P 题解

你谷 link

这个题也太妙了,考场根本一点头绪没有。

给定一些图,求这些图的张量积中 \((1,1,\cdots,1)\) 到所有点的最短路和。

首先我们发现这个张量积上的最短路有什么特点,我们可以考虑将所有图分开考虑,每一张图分开做,加入从 \((1,1,\cdots,1)\)\((a_1,a_2,\cdots,a_K)\),可以看成分别从 \(1\)\(a_1\)\(1\)\(a_2\)\(\cdots\)\(1\)\(a_K\),同时到达。

我们发现图是无向图,所以可以通过在一条边反复走来达到等待的目的,所以就只和最短路长度的奇偶性相关。

考虑算出每张图上每个点的奇偶最短路,设第 \(i\) 张图中 \(1\)\(j\) 的长度为奇数的最短路长度为 \(\text{odd}_{i,j}\),长度为偶数为 \(\text{even}_{i,j}\),那么 \((1,1,\cdots,1)\)\((a_1,a_2,\cdots,a_K)\) 的最短路长度为 \(\min\{\max_{i=1}^K\{\text{odd}_{i,a_i}\},\max_{i=1}^K\{\text{even}_{i,a_i}\}\}\),这个东西是 \(\min\)\(\max\),并不是很好处理,所以考虑进行一步转化,将 \( \min\) 展开成 \(\max\),最后我们就是要求这样一个式子:

\[\sum_{1\le a_i\le n_i}\max_{i=1}^K\{\text{odd}_{i,a_i}\}+\max_{i=1}^K\{\text{even}_{i,a_i}\}-\max_{i=1}^K\{\text{odd}_{i,a_i},\text{even}_{i,a_i}\} \]

发现可以把三个式子分开来算,这样形式是一样的,最后就是要算这样一个东西:

\[\sum_{1\le a_i\le n_i}\max_{i=1}^K\{V_{i,a_i}\} \]

考虑这个 \(V\) 的范围只有 \(n\),所以可以统计 \(\max_{i=1}^K\{V_{i,a_i}\}=x\)\((a_1,a_2,\cdots,a_n)\) 的数量。

然后再考虑这个新的式子怎么算,其实也不难,因为求得是 \(\max\),又是计数,可以考虑两两合并,这样就只要考虑相邻两项,算以下式子:

\[F_k=\sum_{\max\{i,j\}=k}A_iB_j \]

其中 \(A_x\)\(B_x\) 分别代表待合并的两个数组中满足 \(\max_{i=1}^K\{V_{i,a_i}\}=x\) 的数量,\(F_x\) 同理,不过是合并结果。

这个式子很像卷积,但显然不是正常的多项式卷积或是位运算卷积之类的,所以肯定不是什么 FFT 或者 FWT 之类的。

这个可以形象地称为 \(\max\) 卷积的东西算起来相对 FFT 或者 FWT 更简单地多,但我们可以思考做一种类似 FFT 中 DFT 和 IDFT 的变换来使其成为一种线性变换,发现这个东西可以用一种非常简单的办法,就是直接前缀和,逆变换就是直接再差分回来就好了,读者可以手模一下,具体原理可以看下面这张图:

这里黑色的格子表示两个要合并的数组,红线表示待求值最顶层要算的,红线和蓝线的和正好是前缀和的积,而蓝线也是前缀和的积,做差分就能得到答案。

这样整道题就做完了,但还有一个细节。

两个数组和合并是 \(\mathcal O\left(\text{len}\right)\) 的,如果直接将所有数组合并时间复杂度会退化成 \(\mathcal O\left(Kn\right)\),会被卡掉,怎样优化呢?其实直接将数组按长度从小到大合并就好了,这样时间复杂度就是 \(\mathcal O\left(\sum n\right)\),是可过的。

c++ 代码
#include<bits/stdc++.h>

using namespace std;

#define Reimu inline void // 灵梦赛高
#define Marisa inline int // 魔理沙赛高
#define Sanae inline bool // 早苗赛高

typedef long long LL;
typedef unsigned long long ULL;

typedef pair<int, int> Pii;
typedef tuple<int, int, int> Tiii;
#define fi first
#define se second

const int P = 1e9 + 7;
Marisa add(int x, int y) { return (x += y) < P ? x : x - P; }
Marisa&Add(int&x, int y) { return (x += y) < P ? x : x -= P; }
Marisa sub(int x, int y) { return (x -= y) < 0 ? x + P : x; }
Marisa&Sub(int&x, int y) { return (x -= y) < 0 ? x += P : x; }
Marisa mul(int x, int y) { return 1LL * x * y % P; }
Marisa&Mul(int&x, int y) { return x = 1LL * x * y  % P; }
Marisa qpow(int x, int y) { int res = 1; for (; y; y >>= 1, Mul(x, x)) if (y & 1) Mul(res, x); return res; }

const int N = 100010, M = 50010;

int c, n, m;
int dis[N][2];
vector<int> G[N], d0[M], d1[M], d[M];

Sanae cmp(vector<int> &a, vector<int> &b) { return a.size() < b.size(); }

Reimu bfs(int k) {
	const int B = 1 << 30;
	memset(dis + 1, -1, sizeof(int) * n << 1);
	int mx[] = {0, 0};
	queue<int> Q; Q.emplace(1), dis[1][0] = 0;
	while (!Q.empty()) {
		int x = Q.front(), f = x >> 30; Q.pop();
		for (auto y: G[x & ~B]) if (!~dis[y][!f]) Q.emplace(y | !f << 30), mx[!f] = max(mx[!f], dis[y][!f] = dis[x & ~B][f] + 1);
	}
	d0[k].resize(mx[0] + 1); d1[k].resize(mx[1] + 1); d[k].resize(max(mx[0], mx[1]) + 1);
	for (int i = 1; i <= n; ++i) {
		if (~dis[i][0]) ++d0[k][dis[i][0]];
		if (~dis[i][1]) ++d1[k][dis[i][1]];
		if (~dis[i][0] && ~dis[i][1]) ++d[k][max(dis[i][0], dis[i][1])];
	}
}

Reimu merge(vector<int> &a, vector<int> &b) {
	int len = max(a.size(), b.size());
	a.resize(len); b.resize(len);
	for (int i = 1; i < len; ++i) Add(a[i], a[i - 1]), Add(b[i], b[i - 1]);
	for (int i = 0; i < len; ++i) Mul(a[i], b[i]);
	for (int i = len - 1; i; --i) Sub(a[i], a[i - 1]);
}
Marisa calc(vector<int> *vec) {
	sort(vec + 1, vec + c + 1, cmp);
	for (int i = 2; i <= c; ++i) merge(vec[i], vec[i - 1]);
	int res = 0;
	for (int i = 1; i < vec[c].size(); ++i) Add(res, mul(i, vec[c][i]));
	return res;
}

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> c;
	for (int k_ = 1; k_ <= c; ++k_) {
		cin >> n >> m;
		for (int i = 1; i <= n; ++i) G[i].clear();
		for (int i = 1, x, y; i <= m; ++i) cin >> x >> y, G[x].emplace_back(y), G[y].emplace_back(x);
		bfs(k_);
	}
	cout << sub(add(calc(d0), calc(d1)), calc(d));
	return 0;
}
posted @ 2022-04-25 11:05  老莽莽穿一切  阅读(168)  评论(0)    收藏  举报