A. 区间

这题似乎用 \(ST\) 表,单调栈等各种方法都可以过。

我用的是无脑线段树(智商不够,数据结构来凑)。

简单来说,维护一下区间最小值及其位置即可,然后递归输出。

直接贴代码吧。

这个 \(pushup\) 好像不能写三目运算符(不知道为什么),大样例一直过不去,然后调了半天 \(QwQ\)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define ls rt << 1
#define rs rt << 1 | 1

using namespace std;

const int N = 2e5 + 10;
int n;
int a[N], mins[N << 2], pos[N << 2];

inline void pushup(int rt){
if(mins[ls] <= mins[rs]) pos[rt] = pos[ls], mins[rt] = mins[ls];
else pos[rt] = pos[rs], mins[rt] = mins[rs];
}

inline void build(int l, int r, int rt){
if(l == r){
mins[rt] = a[l];
pos[rt] = l;
return;
}
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
pushup(rt);
}

inline int query(int L, int R, int l, int r, int rt){
if(l > R || r < L) return 0;
if(L <= l && r <= R)
return pos[rt];
int mid = (l + r) >> 1;
int res = 0;
if(L <= mid) res = query(L, R, l, mid, ls);
if(R > mid){
int id = query(L, R, mid + 1, r, rs);
if(a[id] < a[res]) res = id;
}
return res;
}

struct node{
int l, r;
}ans[N];
int cnt;

inline void dfs(int l, int r, int tmp){
if(l > r) return;
int id = query(l, r, 1, n, 1);
int res = a[id] - tmp, d = 0;
while(res > 0){
ans[++cnt].l = l, ans[cnt].r = r;
res--, d++;
}
dfs(l, id - 1, tmp + d), dfs(id + 1, r, tmp + d);
}

int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
a[0] = 1e9;
memset(mins, 0x3f, sizeof(mins));
build(1, n, 1);
dfs(1, n, 0);
printf("%d\n", cnt);
for(int i = 1; i <= cnt; i++)
printf("%d %d\n", ans[i].l, ans[i].r);
return 0;
}


B. 树

事实上我并不是很会算期望,所以就算给我 \(n \leq 10\) 数据的我也做不出来 \(QwQ\)。

考场上当然就直接弃了。

后来听学长讲,发现是一波大力推式子。

由于输入的是一棵树,所以从 \(u\) 到 \(v\) 的路径是先网上走到 \(lca(u, v)\),再往下走到 \(v\)。

所以先推出来从 \(x\) 节点走到 \(fa_x\) 的期望,然后推从 \(fa_x\) 走到 \(x\) 的期望(懒得打了,可以去看别人的题解)。就可以求解了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define ll long long

using namespace std;

inline int read(){
int x = 0;
char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}

const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int n, m;
struct node{
int v, nxt;
}edge[N << 1];
int head[N], tot;
int ans;
int fa[N][20], dep[N];
ll f[N],g[N];

inline void add(int x, int y){
edge[++tot] = (node){y, head[x]};
head[x] = tot;
}

inline void dfs1(int x, int p){
fa[x][0] = p, dep[x] = dep[p] + 1;
for(int i = 1; i <= 20; i++)
fa[x][i] = fa[fa[x][i - 1]][i - 1];
f[x] = 1;
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(y == p) continue;
dfs1(y, x);
f[x] = (f[x] + f[y] + 1) % mod;
}
}

inline void dfs2(int x, int p){
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(y == p) continue;
g[y] = (g[x] + f[x] - f[y]) % mod;
dfs2(y, x);
}
}

inline void dfs3(int x, int p){
f[x] += f[p], g[x] += g[p];
for(int i = head[x]; i; i = edge[i].nxt)
if(edge[i].v != p)
dfs3(edge[i].v, x);
}

inline int lca(int a, int b){
if(dep[a] < dep[b]) swap(a, b);
for(int i = 20; i >= 0; i--)
if(dep[fa[a][i]] >= dep[b]) a = fa[a][i];
if(a == b) return a;
for(int i = 20; i >= 0; i--)
if(fa[a][i] != fa[b][i])
a = fa[a][i], b = fa[b][i];
return fa[a][0];
}

signed main(){
n = read(), m = read();
for(int i = 1; i < n; i++){
int u = read(), v = read();
add(u, v), add(v, u);
}
dfs1(1, 0);
f[1]--;
dfs2(1, 0), dfs3(1, 0);
while(m--){
int u = read(), v = read();
int k = lca(u, v);
// cout <<" dd " <<k << endl;
printf("%lld\n", ((f[u] - f[k] + g[v] - g[k]) % mod + mod) % mod);
}
return 0;
}


C. 做运动

巨水的一道题,但是高一学生们全都写的二分 + \(dij\)(默契十足),复杂度 \(O(nlog_n^2)\),然后全都被卡到 10 ~ 30 分不等……

然鹅正解是并查集 + \(dij\),复杂度 \(O(nlogn)\)。

先按温度排序,然后不停加边,当起点和终点在同一集合中时,最大温度就是那条边的温度,然后把所有合法的边全都加进去,跑个最短路即可。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#define INF 1e18
#define ll long long

using namespace std;

inline ll read(){
ll x = 0;
char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}

const ll N = 5e5 + 10;
const ll M = 1e6 + 10;
ll n, m;
struct node{
ll u, v, t, c, nxt;
bool operator < (const node &b) const{
return t < b.t;
}
}edge[M << 1], e[M];
ll head[N], tot;
ll temp[M];
ll st, en;
ll dis[N], f[N];
struct Que{
ll x, dis;
bool operator < (const Que &b) const{
return dis > b.dis;
}
};

inline void add(ll x, ll y, ll c){
edge[++tot].v = y, edge[tot].c = c, edge[tot].nxt = head[x];
head[x] = tot;
}

inline void dijkstra(){
priority_queue <Que> q;
q.push((Que){st, 0});
for(int i = 1; i <= n; i++) dis[i] = INF;
dis[st] = 0;
while(!q.empty()){
Que now = q.top();
q.pop();
ll x = now.x;
if(dis[x] < now.dis) continue;
for(ll i = head[x]; i; i = edge[i].nxt){
ll y = edge[i].v;
if(dis[y] > dis[x] + edge[i].c){
dis[y] = dis[x] + edge[i].c;
q.push((Que){y, dis[y]});
}
}
}
}

inline int find(int x){
return f[x] == x ? x : f[x] = find(f[x]);
}

signed main(){
n = read(), m = read();
for(ll i = 1; i <= m; i++){
e[i].u = read(), e[i].v = read(), e[i].t = read(), e[i].c = read();
}
st = read(), en = read();
for(int i = 1; i <= n; i++)
f[i] = i;
sort(e + 1, e + 1 + m);
ll mint, pos;
for(int i = 1; i <= m; i++){
int fu = find(e[i].u), fv = find(e[i].v);
if(fu != fv) f[fu] = fv;
add(e[i].u, e[i].v, e[i].c * e[i].t), add(e[i].v, e[i].u, e[i].c * e[i].t);
if(find(st) == find(en)){
mint = e[i].t, pos = i;
break;
}
}
for(int i = pos + 1; i <= m; i++){
if(e[i].t != e[pos].t) break;
add(e[i].u, e[i].v, e[i].c * e[i].t), add(e[i].v, e[i].u, e[i].c * e[i].t);
}
dijkstra();
printf("%lld %lld\n", mint, dis[en]);
return 0;
}


D. 手机信号

考场上打 \(O(n^2)\) 暴力,结果没注意写了个 \(c * c\) 直接 \(1e36\) 爆 \(long\ long\) 了,然后 30pts 都没有拿到 \(QwQ\)。

正解似乎是 \(set\) + 乱搞。

然鹅还不太会,先咕了,以后会了在补吧。