uestc summer training #3 线段树优化建边
线段树建边
struct E { int value, modvalue; } a[MAXN << 3]; pair<int, int> b[MAXN]; int root, l[MAXN << 3], r[MAXN << 3], tot; int build(int a, int b) { int x; if (a == b) { x =::b[a].second; } else { x = ++tot; } if (a == b) { return x; } int mid = (a + b) >> 1; l[x] = build(a, mid); r[x] = build(mid + 1, b); addedge(x, l[x]); addedge(x, r[x]); return x; } void ins(int x, int a, int b, int c, int d, int p) { if (d < c) { return ; } if (c <= a && b <= d) { addedge(p, x); return; } int mid = (a + b) >> 1; if (c <= mid) { ins(l[x], a, mid, c, d, p); } if (d > mid) { ins(r[x], mid + 1, b, c, d, p); } }
A
如果把边数缩小到n^2可以接受的话 就是一个最小点基的裸题
但是这里可能有n^2条边所以我们需要线段树优化建边 然后再求出SCC
扣掉不包含原始n个节点的SCC或者把除叶子节点外线段树上的点权设为inf 然后跑最小点基
claris姐姐版SCC缩点:
#include<cstdio> #include<algorithm> #include<set> using namespace std; typedef pair<int, int> P; const int N = 1000010, M = 8000000; int n, m, i, j, x, y; long long ans; struct E { int p, r, c; } a[N]; P b[N]; int root, l[N], r[N], tot; set<P>T[N]; int g[2][N], nxt[2][M], v[2][M], ed, f[N], q[N], t, vis[N], ban[N]; inline void add(int x, int y) { v[0][++ed] = y; nxt[0][ed] = g[0][x]; g[0][x] = ed; v[1][ed] = x; nxt[1][ed] = g[1][y]; g[1][y] = ed; } inline void ADD(int x, int y) { v[1][++ed] = y; nxt[1][ed] = g[1][x]; g[1][x] = ed; } int build(int a, int b) { int x; if (a == b) //如果该点是叶子节点的话 值就为下标 { x =::b[a].second; } else //否则的话 就给该节点一个标号 { x = ++tot; } if (a == b) { return x; } int mid = (a + b) >> 1; l[x] = build(a, mid); r[x] = build(mid + 1, b); add(x, l[x]); add(x, r[x]); return x; } void ins(int x, int a, int b, int c, int d, int p) { if (c <= a && b <= d) { add(p, x); //p是不会变的 如果满足条件的话就把p和x节点连上一条边 return; } int mid = (a + b) >> 1; if (c <= mid) { ins(l[x], a, mid, c, d, p); } if (d > mid) { ins(r[x], mid + 1, b, c, d, p); } } inline int askl(int x) //min >=x { int l = 1, r = n, mid, t; while (l <= r) { mid = (l + r) >> 1; if (b[mid].first >= x) { r = (t = mid) - 1; } else { l = mid + 1; } } return t; } inline int askr(int x) //max <=x { int l = 1, r = n, mid, t; while (l <= r) { mid = (l + r) >> 1; if (b[mid].first <= x) { l = (t = mid) + 1; } else { r = mid - 1; } } return t; } void dfs1(int x) { vis[x] = 1; for (int i = g[0][x]; i; i = nxt[0][i]) if (!vis[v[0][i]]) { dfs1(v[0][i]); } q[++t] = x; } void dfs2(int x, int y) { vis[x] = 0; f[x] = y; for (int i = g[1][x]; i; i = nxt[1][i]) if (vis[v[1][i]]) { dfs2(v[1][i], y); } } void dfs3(int x) { if (ban[x]) { return; } ban[x] = 1; for (int i = g[1][x]; i; i = nxt[1][i]) { dfs3(v[1][i]); } } inline void solve(int x) { if (vis[x]) { return; } vis[x] = 1; for (int i = g[1][x]; i; i = nxt[1][i]) { dfs3(v[1][i]); } } int main() { scanf("%d%d", &n, &m); for (i = 1; i <= n; i++) { scanf("%d%d%d", &a[i].p, &a[i].r, &a[i].c); b[i] = P(a[i].p, i); } sort(b + 1, b + n + 1); //根据每个点的位置进行排序 tot = n; //初始会有n个节点 root = build(1, n); //建立线段树并对线段树上的节点进行赋值 for (i = 1; i <= n; i++) { int l = askl(a[i].p - a[i].r); //二分得到最左边炸到的节点 int r = askr(a[i].p + a[i].r); //二分得到最右边炸到的节点 ins(root, 1, n, l, r, i); //把该节点和线段树上范围为子区间的节点连一条边 } for (t = 0, i = 1; i <= tot; i++) if (!vis[i]) { dfs1(i); } for (i = tot; i; i--) if (vis[q[i]]) { dfs2(q[i], q[i]); } ed = 0; //ed为SCC的边总数 for (i = 1; i <= tot; i++) //SCC前向星初始化head数组 { g[1][i] = 0; } for (i = 1; i <= tot; i++) for (j = g[0][i]; j; j = nxt[0][j]) if (f[i] != f[v[0][j]]) //不同SCC之间建边 { ADD(f[i], f[v[0][j]]); } for (i = 1; i <= n; i++) { solve(f[i]); } for (i = 1; i <= n; i++) if (!ban[f[i]]) //如果f[i]这个SCC是合法的话 就插入该点的一个值 { T[f[i]].insert(P(a[i].c, i)); } for (i = 1; i <= tot; i++) if (!ban[i] && f[i] == i) //如果这个SCC合法且这个SCC的入度是0的话 就把这个SCC内最小的点权值加上 { ans += T[i].begin()->first; } while (m--) { scanf("%d%d", &x, &y); if (!ban[f[x]]) // { ans -= T[f[x]].begin()->first; T[f[x]].erase(P(a[x].c, x)); T[f[x]].insert(P(a[x].c = y, x)); ans += T[f[x]].begin()->first; } printf("%lld\n", ans); } }
自己写的:
/*Huyyt*/ #include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a)) #define pb push_back using namespace std; typedef long long ll; typedef unsigned long long ull; const int mod = 1e9 + 7; const int gakki = 5 + 2 + 1 + 19880611 + 1e9; const int MAXN = 5e5 + 5, MAXM = 3e6 + 5; int to[MAXM << 1], nxt[MAXM << 1], Head[MAXN], ed = 1; typedef pair<int, int> P; const int N = 500010, M = 6000000; int n, m, i, j, x, y; long long ans; struct E { int p, r, c; } a[N]; P b[N]; int to2[MAXM << 1], nxt2[MAXM << 1], Head2[MAXN]; int ed2 = 1; int root, l[N], r[N], tot; //set<P> T[N]; int t; int value[N]; set<P> valuemin[N]; inline void addedge(int u, int v) { to[++ed] = v; nxt[ed] = Head[u]; Head[u] = ed; } inline void addedge2(int u, int v) { to2[++ed2] = v; nxt2[ed2] = Head2[u]; Head2[u] = ed2; } //inline void add(int x, int y) //{ // v[0][++ed] = y; // nxt[0][ed] = g[0][x]; // g[0][x] = ed; // v[1][ed] = x; // nxt[1][ed] = g[1][y]; // g[1][y] = ed; //} //inline void ADD(int x, int y) //{ // v[1][++ed] = y; // nxt[1][ed] = g[1][x]; // g[1][x] = ed; //} int build(int a, int b) { int x; if (a == b) { x =::b[a].second; } else { x = ++tot; } if (a == b) { return x; } int mid = (a + b) >> 1; l[x] = build(a, mid); r[x] = build(mid + 1, b); addedge(x, l[x]); addedge(x, r[x]); return x; } void ins(int x, int a, int b, int c, int d, int p) { if (c <= a && b <= d) { addedge(p, x); return; } int mid = (a + b) >> 1; if (c <= mid) { ins(l[x], a, mid, c, d, p); } if (d > mid) { ins(r[x], mid + 1, b, c, d, p); } } inline int askl(int x) //min >=x { int l = 1, r = n, mid, t; while (l <= r) { mid = (l + r) >> 1; if (b[mid].first >= x) { r = (t = mid) - 1; } else { l = mid + 1; } } return t; } inline int askr(int x) //max <=x { int l = 1, r = n, mid, t; while (l <= r) { mid = (l + r) >> 1; if (b[mid].first <= x) { l = (t = mid) + 1; } else { r = mid - 1; } } return t; } int dfn[MAXN];//表示这个点在dfs的时候是第几个搜到的; int low[MAXN];//表示这个点及其子节点连的所有点里dfn的最小值 int sta[MAXN];//存着当前所有可能能构成强连通分量的点 int visit[MAXN];//表示一个点目前是否在sta中 int color[MAXN];//表示一个点属于哪个强连通分量 int deep;/*表示从前有多少个点被搜到*/ int top;/*sta目前的大小*/ int colorsum = 0;/*目前强连通分量的数目*/ int rudu[MAXN]; int ok[N]; queue<int> q, anser; void tarjan(int x) { dfn[x] = ++deep; low[x] = deep; visit[x] = 1; sta[++top] = x; for (int i = Head[x]; i; i = nxt[i]) { int v = to[i]; if (!dfn[v]) { tarjan(v); low[x] = min(low[x], low[v]); } else { if (visit[v]) { low[x] = min(low[x], low[v]); } } } if (dfn[x] == low[x]) { color[x] = ++colorsum; visit[x] = 0; while (sta[top] != x) { color[sta[top]] = colorsum; visit[sta[top--]] = 0; } top--; } } inline void read(int &v) { v = 0; char c = 0; int p = 1; while (c < '0' || c > '9') { if (c == '-') { p = -1; } c = getchar(); } while (c >= '0' && c <= '9') { v = (v << 3) + (v << 1) + c - '0'; c = getchar(); } v *= p; } int main() { read(n), read(m); for (i = 1; i <= n; i++) { read(a[i].p), read(a[i].r), read(a[i].c); b[i] = P(a[i].p, i); } sort(b + 1, b + n + 1); tot = n; root = build(1, n); for (i = 1; i <= n; i++) { int l = askl(a[i].p - a[i].r); int r = askr(a[i].p + a[i].r); ins(root, 1, n, l, r, i); } for (int i = 1; i <= tot; i++) { if (i <= n) { value[i] = a[i].c; } else { value[i] = INT_MAX; } } for (int i = 1; i <= tot; i++) { if (!dfn[i]) { tarjan(i); } } for (int i = 1; i <= tot; i++) { valuemin[color[i]].insert(make_pair(value[i], i)); for (int j = Head[i]; j; j = nxt[j]) { int v = to[j]; if (color[v] != color[i]) { rudu[color[v]]++; addedge2(color[i], color[v]); } } } for (int i = 1; i <= colorsum; i++) { if (rudu[i] == 0) { q.push(i); } } while (!q.empty()) { int now = q.front(); q.pop(); if (valuemin[now].begin()->first != INT_MAX) { anser.push(now); } else { for (int i = Head2[now]; i; i = nxt2[i]) { int v = to2[i]; rudu[v]--; if (rudu[v] == 0) { q.push(v); } } } } while (!anser.empty()) { int now = anser.front(); anser.pop(); ans += valuemin[now].begin()->first; ok[now] = 1; //cout << now << "!!!" << endl; } int mi, ci; while (m--) { read(mi), read(ci); if (ok[color[mi]]) { ans -= 1LL * valuemin[color[mi]].begin()->first; valuemin[color[mi]].erase(make_pair(a[mi].c, mi)); a[mi].c = ci; valuemin[color[mi]].insert(make_pair(a[mi].c, mi)); ans += 1LL * valuemin[color[mi]].begin()->first; } cout << ans << endl; } }
牛客第四场 J
先线段树优化建边 然后把整个图扣下来拓扑排序 要判两种-1的情况
第一种是insert的时候区间里有-1 用前缀和来判
第二种是拓扑排序的时候有环 用最终答案的size来判
/*Huyyt*/ #include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a)) #define pb push_back using namespace std; typedef long long ll; typedef unsigned long long ull; const int mod = 1e9 + 7; const int gakki = 5 + 2 + 1 + 19880611 + 1e9; const int MAXN = 8e6 + 5, MAXM = 1e7 + 5; int to[MAXM << 1], nxt[MAXM << 1], Head[MAXN], ed = 1; typedef pair<int, int> P; const int N = 1000010, M = 10000000; int n, m, i, j, x, y; long long ans; struct E { int value, modvalue; } a[N]; P b[N]; int pre[N], flag = 1; int number = 0; int wait[N]; int root, l[N], r[N], tot; queue<int> finalans; bool visit[N]; int du[N]; int sum = 0; int nowmod; int nowvalue[N]; int t; priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q; inline void addedge(int u, int v) { du[v]++; to[++ed] = v; nxt[ed] = Head[u]; Head[u] = ed; } int build(int a, int b) { int x; if (a == b) { x =::b[a].second; } else { x = ++tot; } if (a == b) { return x; } int mid = (a + b) >> 1; l[x] = build(a, mid); r[x] = build(mid + 1, b); addedge(l[x], x); addedge(r[x], x); return x; } void ins(int x, int a, int b, int c, int d, int p) { if (d < c) { return ; } if (c <= a && b <= d) { //cout << "add " << x << " " << p << " l r " << l[x] << " " << r[x] << endl; addedge(x, p); return; } int mid = (a + b) >> 1; if (c <= mid) { ins(l[x], a, mid, c, d, p); } if (d > mid) { ins(r[x], mid + 1, b, c, d, p); } } inline void read(int &v) { v = 0; char c = 0; int p = 1; while (c < '0' || c > '9') { if (c == '-') { p = -1; } c = getchar(); } while (c >= '0' && c <= '9') { v = (v << 3) + (v << 1) + c - '0'; c = getchar(); } v *= p; } void get_du(int x) { //wait[++number] = x; visit[x] = true; for (int v, i = Head[x]; i; i = nxt[i]) { v = to[i]; du[v]++; if (!visit[v]) { get_du(v); } } } void init(int x) { pre[0] = number = 0; sum = 0; flag = ed = 1; for (int i = 0; i <= x + 1; i++) { visit[i] = Head[i] = du[i] = 0; } } int main() { int T; read(T); while (T--) { read(n); for (i = 1; i <= n; i++) { pre[i] = pre[i - 1]; read(a[i].value); if (a[i].value != -1) { sum++; } else { pre[i]++; } a[i].modvalue = i - 1; b[i] = P(a[i].modvalue, i); } tot = n; if (sum == 0) { cout << endl; init(tot); continue; } root = build(1, n); for (i = 1; i <= n; i++) { if (a[i].value != -1) { nowmod = a[i].value % n; if (a[i].modvalue > nowmod) { //printf("ins %d %d %d\n", i, nowmod + 1, a[i].modvalue); if (a[i].modvalue >= nowmod + 1 && pre[a[i].modvalue] - pre[nowmod] != 0) { flag = 0; break; } ins(root, 1, n, nowmod + 1, a[i].modvalue, i); } else if (a[i].modvalue < nowmod) { //printf("ins %d %d %d\n", i, 1, a[i].modvalue); //printf("ins %d %d %d\n", i, nowmod + 1, n); if (a[i].modvalue >= 1 && pre[a[i].modvalue] - pre[0] != 0) { flag = 0; break; } if (n >= nowmod + 1 && pre[n] - pre[nowmod] != 0) { flag = 0; break; } ins(root, 1, n, 1, a[i].modvalue, i); ins(root, 1, n, nowmod + 1, n, i); } } } if (!flag) { cout << -1 << endl; init(tot); continue; } //cout << "!!!" << endl; for (int i = 1; i <= tot; i++) { if (i <= n) { nowvalue[i] = a[i].value; } else { nowvalue[i] = -1; } } // for (int i = 1; i <= tot; i++) // { // if (!visit[i]) // { // get_du(i); // } // } // for (int i = 1; i <= n; i++) // { // cout << "i du " << i << " " << du[i] << endl; // } for (int i = 1; i <= tot; i++) { if (du[i] == 0) { //cout << "push " << nowvalue[i] << " " << i << endl; q.push(make_pair(nowvalue[i], i)); } } //cout << "!!!" << endl; pair<int, int> cnt; while (!q.empty()) { cnt = q.top(); q.pop(); if (cnt.first >= 0) { //cout << cnt.first << "zzzz" << endl; finalans.push(cnt.first); } for (int v, i = Head[cnt.second]; i; i = nxt[i]) { v = to[i]; //cout << i << " too " << v << " " << nowvalue[cnt.second] << " nowvalue " << nowvalue[v] << endl; du[v]--; if (du[v] == 0) { q.push(make_pair(nowvalue[v], v)); } } } if (finalans.size() != sum) { printf("-1\n"); } else { while (!finalans.empty()) { printf("%d", finalans.front()); finalans.pop(); if (finalans.size() > 0) { putchar(' '); } else { putchar('\n'); } } } init(tot); } }