1115晚上考试总结
T1
题目大意:
小豪有一个花园,里面有 n 个花棚,编号 1~n,每个花棚里有一定数量的花 a i 。小豪花园的路十分神奇,可以使得任意两个花棚之间仅有一条最短路,即形树结构,其中根节点是 1 号花棚。
现在小豪打算修缮一下他的花园,重新分配每个花棚里花的数量。为了能方便快捷地知道花园的情况,小豪现在需要你的帮助。具体地说,小豪共有 m 个操作。操作有三种:
1 u k 表示如果一个花棚在以 u 号花棚为根的子树中,那么小豪会把这个花棚花的数量模 k;
2 u x 表示小豪将 u 号花棚花的数量变成 x;
3 u v 表示小豪询问从 u 号花棚走到 v 号花棚总共能看到的花的数量。
(n, m <= 1e5).
裸的树剖板子, 不硕了.
#include <bits/stdc++.h>
#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
#define mid ((l + r) >> 1)
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5;
int n, m, cnt, tot;
int a[N], A[N], fa[N], dep[N], top[N], dfn[N], siz[N], hav[N], head[N];
struct edge { int to, nxt; } e[N << 1];
struct tree { int Max; long long sum; } t[N << 2];
void add(int x, int y) {
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}
void get_tree(int x, int Fa) {
fa[x] = Fa; siz[x] = 1; dep[x] = dep[Fa] + 1;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to; if(y == Fa) continue ;
get_tree(y, x); siz[x] += siz[y];
if(siz[y] > siz[hav[x]]) hav[x] = y;
}
}
void get_top(int x, int topic) {
top[x] = topic; A[dfn[x] = ++ tot] = a[x];
if(hav[x]) get_top(hav[x], topic);
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to; if(y == fa[x] || y == hav[x]) continue ;
get_top(y, y);
}
}
int up(int o) {
t[o].sum = t[ls(o)].sum + t[rs(o)].sum;
t[o].Max = max(t[ls(o)].Max, t[rs(o)].Max);
}
void build(int o, int l, int r) {
if(l == r) { t[o].Max = t[o].sum = A[l]; return ; }
build(ls(o), l, mid); build(rs(o), mid + 1, r);
up(o);
}
void modify1(int o, int l, int k) {
A[l] = A[l] % k; t[o].Max = t[o].sum = A[l];
}
void change_son_k(int o, int l, int r, int x, int y, int k) {
if(t[o].Max < k) return ;
if(l == r) { modify1(o, l, k); return ; }
if(x <= mid) change_son_k(ls(o), l, mid, x, y, k);
if(y > mid) change_son_k(rs(o), mid + 1, r, x, y, k);
up(o);
}
void modify2(int o, int l, int k) {
A[l] = k; t[o].Max = t[o].sum = k;
}
void change_point(int o, int l, int r, int x, int k) {
if(l == r) { modify2(o, l, k); return ; }
if(x <= mid) change_point(ls(o), l, mid, x, k);
if(x > mid) change_point(rs(o), mid + 1, r, x, k);
up(o);
}
long long query(int o, int l, int r, int x, int y) {
if(x <= l && y >= r) { return t[o].sum; }
long long res = 0;
if(x <= mid) res += query(ls(o), l, mid, x, y);
if(y > mid) res += query(rs(o), mid + 1, r, x, y);
return res;
}
long long query_path(int x, int y) {
long long res = 0;
while(top[x] != top[y]) {
// cout << x << " " << y << "
";
if(dep[top[x]] < dep[top[y]]) swap(x, y);
res += query(1, 1, n, dfn[top[x]], dfn[x]);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
res += query(1, 1, n, dfn[x], dfn[y]);
return res;
}
int main() {
n = read(); m = read();
for(int i = 1, x, y;i < n; i++) x = read(), y = read(), add(x, y), add(y, x);
for(int i = 1;i <= n; i++) a[i] = read();
get_tree(1, 0); get_top(1, 1); build(1, 1, n);
for(int i = 1, opt, u, k;i <= m; i++) {
opt = read(); u = read(); k = read();
if(opt == 1) { change_son_k(1, 1, n, dfn[u], dfn[u] + siz[u] - 1, k); }
if(opt == 2) { change_point(1, 1, n, dfn[u], k); }
if(opt == 3) { printf("%lld
", query_path(u, k)); }
}
return 0;
}
T2
题目大意:
有(n)个数字, 每个数字可能入队可能出队.入队出队操作次数有(m)次, 已给出. 询问(m)次, 输出每次操作后, 当前队内(gcd(a[i], a[j]) == 1)的对数.
(n, m <= 1e5, a[i] <= 5e5).
容斥原理.
每次入队或出队一个数, 我们只需考虑新来的或出去的数对答案造成的影响, 暴力(for)复杂度(O(n ^ 2)).
正难则反, 我们可以求一下(gcd)不为1的对数有多少.
我们知道唯一分解定理, 也就是我们可以预处理出每个数字的质因子, 然后容斥一下, 也就是加上(p_1, p_2, ..., p_k)的倍数, 减去(p_1 * p_2, p_1*p_3, ...)的倍数, 然后一直容斥就好了.
复杂度是(O(m * 2^7)).为什么是乘(2 ^ 7)呢? 因为最小的7个质数:(2, 3, 5, 7, 11, 13, 17)的乘积已经大于(5e5),所以一个数字的质因子最多有7个, 那么单次容斥的复杂度就是(2 ^ 7)的.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5, M = 5e5 + 5;
int n, m, cnt;
long long ans;
int in[N], val[N], sum[M];
vector <int> a[N];
int c1(int x) {
int res = 0; while(x) { if(x & 1) res ++; x >>= 1; } return (res & 1) ? 1 : -1;
}
int calc(int now, int x) {
int res = 1;
for(int i = 0;i < (int)a[now].size(); i++)
if((x >> i) & 1) res *= a[now][i];
return res;
}
void work(int x) {
int t = a[x].size(), res = 0;
if(in[x] == 1) {
for(int i = 1;i < (1 << t); i++) res += sum[calc(x, i)] * c1(i);
ans += (cnt - res); cnt ++;
for(int i = 1;i < (1 << t); i++) sum[calc(x, i)] ++;
}
if(in[x] == -1) {
for(int i = 1;i < (1 << t); i++) sum[calc(x, i)] --;
for(int i = 1;i < (1 << t); i++) res += sum[calc(x, i)] * c1(i);
cnt --; ans -= (cnt - res);
}
}
int main() {
n = read(); m = read();
for(int k = 1, x;k <= n; k++) {
x = read(); in[k] = -1; val[k] = x;
for(int i = 2;i * i <= x; i++)
if(!(x % i)) {
a[k].push_back(i);
while(!(x % i)) x /= i;
}
if(x > 1) a[k].push_back(x);
}
for(int i = 1, x;i <= m; i++) {
x = read(); in[x] = -in[x];
if(val[x] == 1) {
if(in[x] == 1) ans += cnt, cnt ++;
if(in[x] == -1) cnt --, ans -= cnt;
}
else work(x);
printf("%lld
", ans);
}
return 0;
}
T3
题目大意:
我们看见了一个由 m 行 n 列的 1*1 的格子组成的矩阵,每个格子(i,j)有对应的高度 (h[i][j])和初始的一个非负权值 (v[i][j])。我们可以随便选择一个格子作为起点,然后在接下来的每一步当中,我们能且只能到达与当前格子有边相邻的四个格子中的高度不超过当前格子高度的格子,每当我们到达一个新格子(包括一开始选择的初始格子),我们就能得到该格子的权值分,然后该格子的权值就会等概率变成不比当前的权值大的一个非负权值。每一个格子在满足前面条件情况下,可以走任意多次。
我们希望得到一个最大的期望权值和路径,并给出这个最大的期望权值和。(n, m <= 1000)
Tarjan缩点, 拓扑, 一点点期望知识.
首先我们可以知道, 当走到一个格子((i, j))时, 它的变成的权值的期望是(frac{1}{2} v[i][j]), 因为这个权值(v)可以等概率的变成(0)到(v)的任何一个权值,那么期望变成的权值就是(frac{0 + 1 + 2 + ... + v}{v + 1} = frac{1}{2}v).
为什么要算这个呢? 我们可以注意到, 如果两个相邻的格子高度一样, 那么这两个格子可以无数次的来回走.那么一个格子每被到达一次, 答案就会加上(frac{1}{2} ^ {n} v), 走无限次就变为了(v + frac{1}{2}v + frac{1}{4} v + frac{1}{8}v + .... = 2v).所以如果有一堆高度一样的相邻的格子, 那么我们就可以把这些格子缩成一个, 这一整个的权值就是(2sum v).
最后根据高度关系建出图, 跑一个最长路就好了.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1005;
int m, n, tot, top, cnt, css;
int in[N * N], sta[N * N], col[N * N], dfn[N * N], low[N * N], head[N * N], head_n[N * N], in_edge[N * N];
int h[N][N], v[N * N];
long long ans_, ans[N * N], val[N * N];
struct edge { int to, nxt; } e[N * N * 8], en[N * N * 8];
int get_id(int x, int y) {
return (x - 1) * m + y;
}
void add(int x, int y) {
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}
void add_new(int x, int y) {
en[++ cnt].nxt = head_n[x]; head_n[x] = cnt; en[cnt].to = y; in_edge[y] ++;
}
void ADD(int x, int y) {
int now = get_id(x, y);
if(x != 1 && h[x][y] >= h[x - 1][y]) add(now, get_id(x - 1, y));
if(x != n && h[x][y] >= h[x + 1][y]) add(now, get_id(x + 1, y));
if(y != 1 && h[x][y] >= h[x][y - 1]) add(now, get_id(x, y - 1));
if(y != m && h[x][y] >= h[x][y + 1]) add(now, get_id(x, y + 1));
}
void Tarjan(int x) {
dfn[x] = low[x] = ++ tot; in[sta[++ top] = x] = 1;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
if(!dfn[y]) Tarjan(y), low[x] = min(low[x], low[y]);
else if(in[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x]) {
int p, siz = 0; css ++;
do {
in[p = sta[top --]] = 0; col[p] = css; val[css] += v[p]; siz ++;
} while(p != x);
if(siz > 1) val[css] *= 2;
}
}
void topo_sort() {
queue <int> q;
for(int i = 1;i <= n * m; i++) if(!in_edge[i]) q.push(i), ans[i] = val[i];
while(!q.empty()) {
int x = q.front(); q.pop();
ans_ = max(ans_, ans[x]);
for(int i = head_n[x]; i ; i = en[i].nxt) {
int y = en[i].to;
ans[y] = max(ans[y], ans[x] + val[y]);
if(!(-- in_edge[y])) q.push(y);
}
}
}
int main() {
n = read(); m = read();
for(int i = 1;i <= n; i++) for(int j = 1;j <= m; j++) h[i][j] = read();
for(int i = 1;i <= n; i++) for(int j = 1;j <= m; j++) v[get_id(i, j)] = read();
for(int i = 1;i <= n; i++) for(int j = 1;j <= m; j++) ADD(i, j);
for(int i = 1;i <= n * m; i++) if(!dfn[i]) Tarjan(i); cnt = 0;
for(int x = 1;x <= n * m; x++)
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
if(col[x] != col[y]) add_new(col[x], col[y]);
}
topo_sort();
printf("%lld", ans_);
return 0;
}