8.11考试差点爆0祭
T1
暴力50分:
跑k次没有写错的dij
看起来似乎是个树
也有可能是森林
也许我们可以把它当树做
据Yousiki说是个入门的树形dp,but我不会
正解:
我们枚举两个距离最近的点的编号
既然两个的int表示不同,就说明它们的二进制表示上至少有一位不同。
假设枚举到第i位,就把这一位为0的点设置为源点,这一位为1的点设置成汇点
然后跑多源多汇最短路
就是设置一个超级源点s,一个超级汇点t
s向每个源点连边权为0的边,所有汇点向t连边权为0的边,然后跑s到t的最短路
stdのcode
#include <queue> #include <cstdio> #include <cstring> template <class cls> inline cls min(const cls & a, const cls & b) { return a < b ? a : b; } const int mxn = 100005; const int mxm = 500005; const int inf = 0x3f3f3f3f; int n, m, k; int points[mxn]; int tot; int hd[mxn]; int nt[mxm]; int to[mxm]; int vl[mxm]; inline void add_edge(int u, int v, int w) { nt[++tot] = hd[u]; to[tot] = v; vl[tot] = w; hd[u] = tot; } int dis[mxn]; struct data { int u, d; data(int _u, int _d) : u(_u), d(_d) {} bool operator < (const data & that) const { return d > that.d; } }; std::priority_queue<data> heap; int main() { int cas; scanf("%d", &cas); for (int c = 0; c < cas; ++c) { scanf("%d%d%d", &n, &m, &k); memset(hd, 0, sizeof(int) * (n + 5)); tot = 0; for (int i = 0, u, v, w; i < m; ++i) { scanf("%d%d%d", &u, &v, &w); add_edge(u, v, w); add_edge(v, u, w); } for (int i = 0; i < k; ++i) scanf("%d", points + i);//神仙指针 int ans = inf; for (int i = 1; i < k; i <<= 1) { memset(dis, inf, sizeof(int) * (n + 5));//只memset dis数组的前n+5位 for (int j = 0, p; j < k; ++j)//这里对每个地下党按照输入顺序进行编号,然后枚举编号 if (p = points[j], (j & i) == 0)//看j的第i位是否是0 heap.push(data(p, dis[p] = 0));//把第i位为0的设置成了源点 while (!heap.empty()) { int u = heap.top().u; int d = heap.top().d; heap.pop(); if (dis[u] != d) continue; for (int e = hd[u], v, w; e; e = nt[e]) if (v = to[e], w = vl[e], dis[v] > d + w) heap.push(data(v, dis[v] = d + w)); } for (int j = 0, p; j < k; ++j) if (p = points[j], (j & i) != 0) ans = min(ans, dis[p]); } printf("%d\n", ans == inf ? -1 : ans); } return 0; }
T2:
处理到达的最大的权值(bfs)(不是dfs)
tarjan缩一波点,来个DAG上dp
stdのcode
#include <cstdio> #include <cstring> template <class cls> inline cls min(const cls & a, const cls & b) { return a < b ? a : b; } template <class cls> inline cls max(const cls & a, const cls & b) { return a > b ? a : b; } const int mxn = 200005; const int mxm = 400005; int n, m, k, w[mxn]; struct edge { int u, v; } edges[mxm]; int tot; int hd[mxn]; int to[mxm << 1]; int nt[mxm << 1]; inline void add_edge(int u, int v) { nt[++tot] = hd[u]; to[tot] = v; hd[u] = tot; } int tim; int cnt; int top; int dfn[mxn]; int low[mxn]; int stk[mxn]; int scc[mxn]; void tarjan(int u) { dfn[u] = low[u] = ++tim; stk[++top] = u; for (int e = hd[u], v; e; e = nt[e]) if (v = to[e], scc[v] == 0) { if (dfn[v] == 0)tarjan(v), low[u] = min(low[u], low[v]); else low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { cnt += 1; do { scc[stk[top]] = cnt; } while (stk[top--] != u); } } int oe[mxn]; int mx[mxn]; int que[mxn]; void bfs() {//O(n)预处理答案数组是真的强 int l = 0, r = 0; for (int i = 1; i <= cnt; ++i) if (oe[i] == 0) que[r++] = i; while (l < r) { int u = que[l++]; for (int e = hd[u], v; e; e = nt[e]) if (v = to[e], mx[v] = max(mx[v], mx[u]), --oe[v] == 0) que[r++] = v; } } int main() { int cas; scanf("%d", &cas); for (int c = 0; c < cas; ++c) { scanf("%d%d%d", &n, &m, &k); for (int i = 1; i <= n; ++i) scanf("%d", w + i); memset(hd, 0, sizeof(int) * (n + 5)); tot = 0; for (int i = 0; i < m; ++i) { scanf("%d%d", &edges[i].u, &edges[i].v); add_edge(edges[i].u, edges[i].v); } tim = cnt = top = 0; memset(scc, 0, sizeof(int) * (n + 5)); memset(dfn, 0, sizeof(int) * (n + 5)); for (int i = 1; i <= n; ++i)//先搞一波tarjan if (scc[i] == 0) tarjan(i); memset(hd, 0, sizeof(int) * (cnt + 5)); tot = 0; memset(oe, 0, sizeof(int) * (cnt + 5));//这样memset省时间 memset(mx, 0, sizeof(int) * (cnt + 5)); for (int i = 0; i < m; ++i) { int u = scc[edges[i].u]; int v = scc[edges[i].v]; if (u != v) add_edge(v, ua), oe[u] += 1; } for (int i = 1; i <= n; ++i) mx[scc[i]] = max(mx[scc[i]], w[i]); bfs(); for (int i = 0, u, x; i < k; ++i) { scanf("%d%d", &u, &x); printf("%lld\n", 1LL * x * mx[scc[u]]);//1LL表示转化成long long类型 } } return 0; }
T3
暴力:
既然有20%的数据是颜色种类不超过5,所以我们可以开5棵线段树
然鹅丧(sang)病(bing)的我开了100棵线段树
真好
100棵线段树的建树优化
不是for(1~100)
而是for c[l]到c[r]
这样复杂度就是O(nlogn)的了
正解:
莫得强制在线
把所有操作都读入进来
修改:从颜色1的集合中删除,从颜色2的集合中加入
所以按照颜色顺序,时间顺序处理每个操作
这样就只需要一棵线段树了
不用树链剖分:
考虑树上的某个节点的权值加x,并且要计算一个点到根的路径上的点权之和
当u+x之后,u的子树里的点到根的路径上的点权之和都+x。
如果不用树链剖分,就跑一遍普通的dfs序。
普通的dfs序保证一个点的子树是这个点出现的第一次和第三次之间的区间
我们依旧用线段树来维护区间和。
当点u的权值+x之后,在线段树上,整个区间的答案都+x
求u到v路径上的点权之和:u到根+v到根的-2*lca到根的+lca的
stdの神仙code
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int getint() { int r = 0, c = getchar(); for (; c < 48; c = getchar()); for (; c > 47; c = getchar()) r = r * 10 + c - 48; return r; } const int mxc = 100005; const int mxn = 100005; const int mxm = 200005; int n, m, c; int tt; int hd[mxn]; int to[mxm]; int nt[mxm]; inline void addedge(int x, int y) { nt[++tt] = hd[x], to[tt] = y, hd[x] = tt; nt[++tt] = hd[y], to[tt] = x, hd[y] = tt; } struct data { int k, x, y;//[x,y]是要搞事情的区间,k是修改标记/查询的时间(k==0:表示当前操作是修改操作) data() {} ; data(int a, int b, int c)//构造函数(把k赋值为a,把x赋值为b,把y赋值为c) : k(a), x(b), y(c) {} ; }; int color[mxn]; #include <vector> vector<data> vec[mxc];//开了100个vector,每个对应一种颜色 int tim; int dfn[mxn]; int top[mxn]; int fat[mxn]; int dep[mxn]; int son[mxn]; int siz[mxn]; void dfs1(int u, int f) { siz[u] = 1; son[u] = 0; fat[u] = f; dep[u] = dep[f] + 1; for (int i = hd[u], v; i; i = nt[i]) if (v = to[i], v != f) { dfs1(v, u); siz[u] += siz[v]; if (siz[v] > siz[son[u]]) son[u] = v; } } void dfs2(int u, int f) { dfn[u] = ++tim; if (son[f] == u) top[u] = top[f]; else top[u] = u; if (son[u]) dfs2(son[u], u); for (int i = hd[u], v; i; i = nt[i]) if (v = to[i], v != f && v != son[u]) dfs2(v, u); } //上面是一波树剖 int bit[mxn]; inline void add(int p, int v)//树状数组维护,常数较小 { for (; p <= n; p += p & -p) bit[p] += v; } inline int ask(int l, int r) { int sum = 0; --l; for (; r; r -= r & -r) sum += bit[r]; for (; l; l -= l & -l) sum -= bit[l]; return sum; } int ans[mxn]; signed main() { int cas = getint(); while (cas--) { n = getint(); m = getint(); for (int i = 1; i <= n; ++i) vec[color[i] = getint()].push_back(data(0, i, +1)); c = 0; for (int i = 1; i <= n; ++i) c = max(c, color[i]); memset(hd, 0, sizeof(int) * (n + 5)); tt = 0; for (int i = 1; i < n; ++i) { int x = getint(); int y = getint(); addedge(x, y); } for (int i = 1; i <= m; ++i) { if (getint() == 1) { int p = getint(); int a = color[p]; int b = color[p] = getint(); vec[a].push_back(data(0, p, -1)); vec[b].push_back(data(0, p, +1)); } else { int x = getint(); int y = getint(); int k = getint(); vec[k].push_back(data(i, x, y)); } } dfs1(1, 0); dfs2(1, 0); memset(ans, -1, sizeof ans); for (int k = 1; k <= c; ++k)//分颜色来搞 { int sz = vec[k].size(); memset(bit, 0, sizeof bit); for (int i = 0; i < sz; ++i) { const data &d = vec[k][i]; ans[d.k] = 0; if (d.k == 0)//进行修改操作 add(dfn[d.x], d.y); else { int a = d.x, ta = top[a]; int b = d.y, tb = top[b]; while (ta != tb) { if (dep[ta] >= dep[tb]) ans[d.k] += ask(dfn[ta], dfn[a]), ta = top[a = fat[ta]]; else ans[d.k] += ask(dfn[tb], dfn[b]), tb = top[b = fat[tb]]; } if (dep[a] <= dep[b]) ans[d.k] += ask(dfn[a], dfn[b]); else ans[d.k] += ask(dfn[b], dfn[a]); } } } for (int i = 1; i <= m; ++i) if (ans[i] >= 0) printf("%d\n", ans[i]); for (int i = 1; i <= c; ++i) vec[i].clear(); tim = 0; } return 0; }