0721 模拟赛
A
给定一个数 \(n\),有集合 \(\{1, 2, 3, \dots, n\}\)。每次可以选择两个数 \(x, y\),从集合中剔除这两个数并加入 \(\frac{x + y}{2}\)。操作 \(n - 1\) 次后集合中剩下的数的最大值 \(a\)。求 \(a \cdot 2^{n - 1} \bmod 10^9 + 7\)。
\(n \le 10^{10}\)
看到这特殊的数据范围,很容易想到一些奇妙的 \(O(\sqrt{n})\) 做法,比如分块打表。但是这道题的正解显然并非如此。
这题理应一眼看出来是个推柿子题。
考虑贪心地进行操作,每次考虑将最小的两个数进行该操作,这样能使更大的数的分母尽可能小,从而达到答案尽可能大的目的。
则有
(我也不知道这个式子的省略该怎么写)那么最终输出的答案就是:
如果你一不小心写成了递推式(\(f_n = f_{n - 1} + n \times 2^{n - 2}\)),那么你就可以用分块打表做了。但这并不是最优解。实际上还可以再进行优化。
考虑到式子中的 \(1\) 太碍事了,先把他踢一边去不管。然后就会发现,后面是一堆 \(2\) 的次幂,并且系数还是单调递增的。那么很容易想到(虽然我考试的时候想到了)乱搞把它们拆开成:
啊,前两个括号里的东西眼熟啊,可以化简成 \(2^{n - 1} - 1\),所以后面的括号里的东西也可以化简成这个玩意减去一些东西。所以就变成了
化简得
你发现后面的也可以化简。
啊呀,你发现还能化简,真是烦死了
哇!然后化简成了
嗯,别忘了加上一开始被踢掉的 \(1\)。所以最后输出
注意到其中的 \(2^{n - 1}\) 用左移运算符会爆炸,所以使用快速幂。时间复杂度 \(O(\log n)\)。建议缩短时限使 \(O(\sqrt n)\) 不可过,或减小空限使分块打表不可过。
这个推柿子挺简单的,考场推了十分钟左右就退出来了。快速幂模板是橙,这题评橙不过分吧。
B
定义一条路径的长短为:这条路径经过的边权总和减去这些边权的最大值,再加上这些边权的最小值。
给定一张带权无向图,求从 \(1\) 出发的单源最短路。\(n \le 2 \times 10^5\)。
注意到题目可以转换为把路径上其中一条边的边权转为 \(0\),另一条边权翻倍,然后求最短路。能这么转的原因是最终的最短路肯定会贪心地将路径中最大的边变成 \(0\),最小的边变成二倍。这样无论如何也比相同路径的其他方法短。
所以可以使用分层图解决。把输入的图拆成四层,分别是零的和双倍的都没过,只过了零的,只过了双倍的,和都过了。具体建边很好想,但是需要注意的一点是都没过到都过了还需要连边,边权是自己。
贴
// not add long long and to see foot brown
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
#define fi first
#define se second
typedef long long ll;
const int N = 8000010;
int n, m, x, y, z;
ll dis[N];
bool vis[N];
priority_queue <pair<ll, ll>, vector <pair <ll, ll>>, greater<pair <ll, ll>>> q;
struct edge {
int to, v, nxt;
} e[N];
int head[N], now;
void add (int x, int y, int z) {
e[++ now].to = y;
e[now].v = z;
e[now].nxt = head[x];
head[x] = now;
}
void dijkstra (int start) {
q.push ({0, start});
dis[start] = 0;
while (! q.empty ()) {
int k = q.top ().se; q.pop ();
if (vis[k]) continue;
vis[k] = true;
for (int i = head[k]; i; i = e[i].nxt) {
int to = e[i].to;
if (dis[to] > dis[k] + e[i].v) {
dis[to] = dis[k] + e[i].v;
q.push ({dis[to], to});
}
}
}
}
int main () {
freopen ("path.in", "r", stdin);
freopen ("path.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++) {
scanf("%d%d%d", &x, &y, &z);
// 00 都没有 01 过了边权为0的 10 过了边权为2v的 11 都过了
add (x, y, z);
add (y, x, z);
add (n + x, n + y, z);
add (n + y, n + x, z);
add (n + n + x, n + n + y, z);
add (n + n + y, n + n + x, z);
add (n + n + n + x, n + n + n + y, z);
add (n + n + n + y, n + n + n + x, z);
add (x, n + y, 0);
add (y, n + x, 0);
add (n + n + x, n + n + n + y, 0);
add (n + n + y, n + n + n + x, 0);
add (x, n + n + n + y, z);
add (y, n + n + n + x, z);
add (x, n + n + y, 2 * z);
add (y, n + n + x, 2 * z);
add (n + x, n + n + n + y, 2 * z);
add (n + y, n + n + n + x, 2 * z);
}
for (int i = 1; i <= 4 * n; i ++) {
dis[i] = 1145141919810810810;
} dijkstra (1);
for (int i = 2; i <= n; i ++) {
if (dis[i] == 1145141919810810810) printf("-1 ");
else printf ("%lld ", dis[n + n + n + i]);
}
return 0;
}
浙公网安备 33010602011771号