排列最小生成树 (pmst) 50pts
又是诈骗题,然后又不会。。。
暴力很暴力,直接建个完全图跑 Kruskal
即可;
正解考虑如果我们连接编号相邻的点,那么每个边的边权都小于 $ n $ 真能考虑到吗?;
所以我们最终的最小生成树中的边边权都小于 $ n $;
那么对于 $ |p_i - p_j| \times |i - j| < n $ 的限制,我们肯定不会出现 $ |p_i - p_j| > \sqrt n $ 且 $ |i - j| > \sqrt n $ 的情况,所以我们连的边要么是 $ |p_i - p_j| \leq \sqrt n $ ,要么是 $ |i - j| \leq \sqrt n $,(注意这里可以不同时满足);
所以我们在连边的时候只需将原数组按编号和 $ p $ 分别排序,然后对于每个点向右连 $ \sqrt n $ 条边即可;
那么总边数为 $ \Theta(n \sqrt n) $,但是在 Kruskal
过程中需要排序,如果 sort
的话会带 $ \log $ 过不去,但是发现边权很小,所以直接桶排序即可;
时间复杂度:$ \Theta(n \sqrt n) $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int n;
int b[500005], a[500005];
struct sas{
unsigned short f, t, w;
bool operator <(const sas &A) const {
return w < A.w;
}
}e[23000005];
long long len(int x, int y) {
return 1ll * abs(x - y) * abs(b[x] - b[y]);
}
int cnt;
void add(int u, int v, unsigned short ww) {
e[++cnt].f = u;
e[cnt].t = v;
e[cnt].w = ww;
}
int fa[500005];
int find(int x) {
if (x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
long long Kru() {
long long ans = 0;
sort(e + 1, e + 1 + cnt);
int sum = 0;
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= cnt; i++) {
if (sum == n - 1) break;
int x = find(e[i].f);
int y = find(e[i].t);
if (x != y) {
fa[x] = y;
ans += 1ll * e[i].w;
}
}
return ans;
}
int main() {
freopen("pmst.in", "r", stdin);
freopen("pmst.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> b[i];
a[b[i]] = i;
}
int sq = sqrt(n);
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= min(n, i + 1 + sq); j++) {
if (len(i, j) <= n) add(i, j, len(i, j));
if (len(a[i], a[j]) <= n) add(a[i], a[j], len(a[i], a[j]));
}
}
cout << Kru();
return 0;
}
卡牌游戏 (cardgame) 100pts
签到题居然在T2。。。;
看到特殊性质有 $ gcd(n, m) = 1 $,于是很自然的想到从 $ gcd $ 出发;
这里我们默认 $ n < m $;
手模一下,可以发现,对于互质的情况,$ a $ 中每个数都会与 $ b $ 中每个数对战恰好一次;
对于不互质的情况,我们可以发现,$ a_i $ 是从 $ b_{a_i \mod gcd(n, m)} $ 开始对战,并且 $ b $ 的下标每次加 $ gcd(n, m) $,对战次数为 $ \frac{n \times m}{n \times cnt_{now}} $,其中 $ cnt_{now} $ 代表对战的 $ b_i $ 中不同的 $ i $ 的个数;
于是我们维护一下从 1 到 gcd(n, m) 开始,每次下标加 gcd(n, m) 的数组,然后同时维护一下 $ cnt $ 数组即可;
查询直接二分查找;
时间复杂度 $ \Theta(n \log n) $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
long long n, m;
long long a[500005], b[500005];
long long cnt[100005];
vector<long long> v[100005];
long long gc;
long long lose, win, he;
bool vis;
int main() {
freopen("cardgame.in", "r", stdin);
freopen("cardgame.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= m; i++) {
cin >> b[i];
}
if (n > m) {
vis = true;
for (int i = 1; i <= n; i++) swap(a[i], b[i]);
swap(n, m);
}
gc = __gcd(n, m);
for (int i = 1; i <= gc; i++) {
for (int j = i; j <= m; j += gc) {
v[i].push_back(b[j]);
cnt[i]++;
}
sort(v[i].begin(), v[i].end());
}
for (int i = 1; i <= n; i++) {
long long now = i % gc;
if (now == 0) now = gc;
long long val = (n * m) / (n * cnt[now]);
long long lpos = lower_bound(v[now].begin(), v[now].end(), a[i]) - v[now].begin();
win += lpos * val;
long long rpos = upper_bound(v[now].begin(), v[now].end(), a[i]) - v[now].begin();
if (lpos < v[now].size() && v[now][lpos] == a[i]) {
he += (rpos - lpos) * val;
}
if (rpos < v[now].size()) {
lose += (v[now].size() - rpos) * val;
}
}
if (vis) {
cout << lose << '\n' << win << '\n' << he;
} else {
cout << win << '\n' << lose << '\n' << he;
}
return 0;
}
比特跳跃 (jump) 39pts
$ s = 1 $ 特殊性质和暴力39pts;
考虑正解,对于这个题,其实有三个子问题;
- $ s = 1 $;
$ x \ \And \ y $ 这个最好考虑,因为加的边权不是 $ 0 $ 就是 $ k $;
首先我们将 $ 1 $ 与其它点连边保证联通,然后将二的次幂全都连出来一条链(因为边权都是 $ 0 $),然后枚举每个点,找到这个点二进制是 $ 0 $ 的位置并与相应的二的次幂连边,然后我们就将所有需要的的 $ 0 $ 的边权都连上了,然后从 $ 1 $ 出发的 $ 0 $ 或 $ k $ 的边权也连上了,最后跑最短路算法即可;
- $ s = 2 $;
对于 $ x \oplus y $ 的情况,我们发现只更改一位是最优的,所以对于每个点,只连二进制位有一位不同的点即可;
- $ s = 3 $;
对于 $ x | y $ 的情况,可以发现如果不是直接从 $ 1 $ 走过来,那么就由它的子集走过来;
所以我们先将 $ 1 $ 与其它点连边跑一遍最短路,然后顺序更新只改一位的子集,存起来更新即可;
注意这里的更新有 $ DP $ 的感觉,虽然只改了一位,但是存下来的却是很多位,具体看代码;
上面三个子问题都连了 $ \Theta(n \log n) $ 条边,所以时间复杂度:$ \Theta((n \log n + m) \log(n \log n + m)) $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
int n, m, s;
long long k;
long long p[25];
struct sss{
int t, ne;
long long w;
}e[5000005];
int h[5000005], cnt;
void add(int u, int v, long long ww) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
e[cnt].w = ww;
}
long long dis[500005], f[500005];
bool vi[5000005];
bool vis[500005];
void dij(int x, bool is) {
priority_queue<pair<long long, int>, vector<pair<long long, int> >, greater<pair<long long, int> > > q;
if (is) {
memset(dis, 0x3f, sizeof(dis));
} else {
for (int i = 2; i <= n; i++) q.push({dis[i], i});
}
memset(vis, false, sizeof(vis));
q.push({0, x});
dis[x] = 0;
while(!q.empty()) {
int xu = q.top().second;
q.pop();
if (vis[xu]) continue;
vis[xu] = true;
for (int i = h[xu]; i; i = e[i].ne) {
int u = e[i].t;
if (dis[u] > dis[xu] + e[i].w) {
dis[u] = dis[xu] + e[i].w;
q.push({dis[u], u});
}
}
}
}
int main() {
freopen("jump.in", "r", stdin);
freopen("jump.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> s;
cin >> k;
int x, y;
long long w;
for (int i = 1; i <= m; i++) {
cin >> x >> y;
cin >> w;
add(x, y, w);
add(y, x, w);
}
if (n <= 800) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) continue;
if (s == 1) add(i, j, k * 1ll * (i & j));
if (s == 2) add(i, j, k * 1ll * (i ^ j));
if (s == 3) add(i, j, k * 1ll * (i | j));
}
}
dij(1, true);
for (int i = 2; i <= n; i++) {
cout << dis[i] << ' ';
}
return 0;
}
p[0] = 1;
vi[0] = true;
for (int j = 1; j <= 18; j++) {
p[j] = p[j - 1] * 2;
vi[p[j]] = true;
}
if (s == 1) {
for (int j = 18; j >= 0; j--) {
if (p[j] == n) {
for (int i = 2; i <= n; i++) {
cout << 0 << ' ';
}
return 0;
}
}
for (int j = 1; j <= 18; j++) {
if (p[j] > n) break;
add(p[j - 1], p[j], 0);
add(p[j], p[j - 1], 0);
}
for (int i = 2; i <= n; i++) {
if (vi[i]) continue;
add(1, i, k * 1ll * (1 & i));
add(i, 1, k * 1ll * (1 & i));
}
for (int i = 2; i <= n; i++) {
if (vi[i]) continue;
for (int j = 0; j <= 18; j++) {
if (p[j] > n) break;
if (!((1 << j) & i)) {
add(p[j], i, 0);
add(i, p[j], 0);
}
}
}
dij(1, true);
for (int i = 2; i <= n; i++) {
cout << dis[i] << ' ';
}
return 0;
}
if (s == 2) {
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= 18; j++) {
if (p[j] > n) break;
add(i, (i ^ (1 << j)), k * p[j]);
add((i ^ (1 << j)), i, k * p[j]);
}
}
dij(1, true);
for (int i = 2; i <= n; i++) {
cout << dis[i] << ' ';
}
return 0;
}
if (s == 3) {
memset(f, 0x3f, sizeof(f));
f[1] = 0;
for (int i = 2; i <= n; i++) {
add(1, i, k * (1 | i));
add(i, 1, k * (1 | i));
}
dij(1, true);
for (int i = 2; i <= n; i++) {
for (int j = 0; j <= 18; j++) {
if (i & (1 << j)) {
dis[i] = min(dis[i], f[i ^ (1 << j)] + k * i);
f[i] = min(dis[i], f[i ^ (1 << j)]);
}
}
}
dij(1, false);
for (int i = 2; i <= n; i++) cout << dis[i] << ' ';
}
return 0;
}
区间 (interval) 15pts
暴力扫 $ \Theta(n^3) $ 15pts;
对于 $ \Theta(n^2) $ 左右的做法,考虑 $ n $ 很小,所以我们可以预处理出所有区间的答案,然后 $ \Theta(n) $ 询问即可;
怎样预处理?对于每一个数维护它的右边第一个大于等于它的数,那么右端点的范围就确定了,直接预处理 $ b_r > a_l $ 的个数即可;
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, q;
int a[500005], b[500005], c[1000005], cnt;
int pos[500005];
int ans[5005][5005];
int main() {
freopen("interval.in", "r", stdin);
freopen("interval.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
c[++cnt] = a[i];
}
for (int i = 2; i <= n; i++) {
cin >> b[i];
c[++cnt] = b[i];
}
sort(c + 1, c + 1 + cnt);
cnt = unique(c + 1, c + 1 + cnt) - c - 1;
for (int i = 1; i <= n; i++) {
pos[i] = n;
for (int j = i + 1; j <= n; j++) {
if (a[j] >= a[i]) {
pos[i] = j;
break;
}
}
}
cin >> q;
int l = 0, r = 0;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= pos[i]; j++) {
ans[i][j] = ans[i][j - 1];
if (b[j] > a[i]) ans[i][j]++;
}
}
for (int i = 1; i <= q; i++) {
cin >> l >> r;
int sum = 0;
for (int j = l; j <= r - 1; j++) {
sum += ans[j][min(pos[j], r)];
}
cout << sum << '\n';
}
return 0;
}
岛屿 5pts
居然都不给暴搜的部分分。。。;
正解,考虑 $ DP $,然后懒了,直接粘题解把。。。;
就是考虑每次加一对点,然后将其与已经加过的点连边,注意计算概率;
最后的求和其实就是递归一层层往下套得来的;
时间复杂度:$ \Theta(n) $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <iomanip>
using namespace std;
int x, y, n;
double ans;
int main() {
freopen("island.in", "r", stdin);
freopen("island.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> x >> y;
for (int i = 1; i <= y; i++) ans += 1.00 / (2 * x + i);
for (int i = 1; i <= x; i++) ans += 1.00 / (2 * i - 1);
cout << fixed << setprecision(15) << ans;
return 0;
}
最短路 26pts
重测了好多次,然后就整了个26pts;
首先建出最短路树,然后考虑删一条点 $ x $ 和其父亲 $ fa_x $ 连的边能有什么次小的方案;
发现并不怎么好搞,所以我们枚举每一条非树边,看看它能有什么贡献;
那么对于非树边 $ (u, v) $,当它们两个不在同一个子树时可以产生贡献;
简而言之,它们可以对树上 $ u, v $ 两点路径上的所有点,但不包括 $ lca(u, v) $ 的点产生贡献,设这些点为 $ k $,那么贡献为 $ dis_u + w_{u, v} + dis_{v} - dis_k $ (就是 $ 1 \rightarrow u \rightarrow v \rightarrow k $ ,反之同理);
对于一个点 $ k $ ,$ dis_k $ 是定值,那么我们只需要维护 $ \min (dis_u + w_{u, v} + dis_{v}) $ 即可,这是一个区间修改单点查询的问题,用树剖 + 线段树解决;
时间复杂度:$ \Theta(n \log^2 n) $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
int n, m;
struct sss{
int t, ne;
}e[500005];
int h[500005], cnt;
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
vector<pair<int, long long> > v[500005];
long long dis[500005];
bool vis[500005];
int pre[500005];
void dij(int x) {
for (int i = 1; i <= n; i++) {
dis[i] = 0x3f3f3f3f3f3f3f3f;
vis[i] = false;
}
priority_queue<pair<long long, int>, vector<pair<long long, int> >, greater<pair<long long, int> > > q;
while(!q.empty()) q.pop();
dis[x] = 0;
q.push({0, x});
while(!q.empty()) {
int xu = q.top().second;
q.pop();
if (vis[xu]) continue;
vis[xu] = true;
for (int i = 0; i < v[xu].size(); i++) {
int u = v[xu][i].first;
if (dis[u] > dis[xu] + v[xu][i].second) {
dis[u] = dis[xu] + v[xu][i].second;
pre[u] = xu;
q.push({dis[u], u});
}
}
}
}
int fa[500005], siz[500005], dep[500005], hson[500005], htop[500005], dfn[500005], dcnt;
namespace SEG{
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct sss{
int l, r;
long long mi, lz;
}tr[500005];
inline void push_down(int id) {
if (tr[id].lz != 0x3f3f3f3f3f3f3f3f) {
tr[ls(id)].lz = min(tr[ls(id)].lz, tr[id].lz);
tr[rs(id)].lz = min(tr[rs(id)].lz, tr[id].lz);
tr[ls(id)].mi = min(tr[ls(id)].mi, tr[id].lz);
tr[rs(id)].mi = min(tr[rs(id)].mi, tr[id].lz);
tr[id].lz = 0x3f3f3f3f3f3f3f3f;
}
}
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
tr[id].mi = tr[id].lz = 0x3f3f3f3f3f3f3f3f;
if (l == r) return;
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
}
void add(int id, int l, int r, long long d) {
if (tr[id].l >= l && tr[id].r <= r) {
tr[id].lz = min(tr[id].lz, d);
tr[id].mi = min(tr[id].mi, d);
return;
}
push_down(id);
int mid = (tr[id].l + tr[id].r) >> 1;
if (l <= mid) add(ls(id), l, r, d);
if (r > mid) add(rs(id), l, r, d);
}
long long ask(int id, int pos) {
if (tr[id].l == tr[id].r) return tr[id].mi;
push_down(id);
int mid = (tr[id].l + tr[id].r) >> 1;
if (pos <= mid) return ask(ls(id), pos);
else return ask(rs(id), pos);
}
}
namespace TCS{
void dfs1(int x, int f) {
fa[x] = f;
hson[x] = -1;
siz[x] = 1;
dep[x] = dep[f] + 1;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == f) continue;
dfs1(u, x);
siz[x] += siz[u];
if (hson[x] == -1 || siz[hson[x]] < siz[u]) hson[x] = u;
}
}
void dfs2(int x, int t) {
htop[x] = t;
dfn[x] = ++dcnt;
if (hson[x] == -1) return;
dfs2(hson[x], t);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa[x] || u == hson[x]) continue;
dfs2(u, u);
}
}
void add(int x, int y, long long d) {
int u = x, v = y;
while(htop[x] != htop[y]) {
if (dep[htop[x]] < dep[htop[y]]) swap(x, y);
SEG::add(1, dfn[htop[x]], dfn[x], dis[u] + dis[v] + d);
x = fa[htop[x]];
}
if (x == y) return;
if (dfn[x] > dfn[y]) swap(x, y);
SEG::add(1, dfn[x] + 1, dfn[y], dis[u] + dis[v] + d);
}
}
int main() {
freopen("path.in", "r", stdin);
freopen("path.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
int x, y;
long long w;
cnt = 1;
for (int i = 1; i <= m; i++) {
cin >> x >> y;
cin >> w;
v[x].push_back({y, w});
v[y].push_back({x, w});
}
dij(1);
for (int i = 2; i <= n; i++) {
add(i, pre[i]);
add(pre[i], i);
}
TCS::dfs1(1, 0);
TCS::dfs2(1, 1);
SEG::bt(1, 1, dcnt);
for (int i = 1; i <= n; i++) {
for (int j = 0; j < v[i].size(); j++) {
if ((fa[i] == v[i][j].first || fa[v[i][j].first] == i) && v[i][j].second == (dis[max(i, v[i][j].first)] - dis[min(i, v[i][j].first)])) continue;
TCS::add(i, v[i][j].first, v[i][j].second);
}
}
for (int i = 2; i <= n; i++) {
long long val = SEG::ask(1, dfn[i]);
if (val != 0x3f3f3f3f3f3f3f3f) cout << val - dis[i] << '\n';
else cout << -1 << '\n';
}
return 0;
}