题目:Intrinsic Interval
链接:https://codeforces.com/gym/101620
题意:
Intrinsic Interval : 排序后是连续区间
给定区间(a,b),求包含该区间的的最大的Intrinsic Interval
分析:
1)参考:https://www.cnblogs.com/yqgAKIOI/p/10087038.html
平凡区间(即区间长度为1的区间)都是Intrinsic Interval的,这种做法的本质是联系,构造依赖。
如果我们要把两个相邻区间合并到一起,左边区间最右边的数和右边区间最左边的数取到决定作用。
我们分析最特殊的区间,两个相邻的平凡区间合并[i,i+1],这表示[ a[i],a[i+1] ] 中所有的权值都要出现。
这些权值出现的最左和最右位置卡出来的区间 [l,r]是[i,i+1]的依赖,也就是为了合并[i, i+1],至少要合并[l,r]区间。
合并一个区间就是把区间中两两相邻区间合并。
向区间连边问题可以用线段树辅助建图。
这样就得到了一张依赖网络,有向图。
可以用tarjan求SCC,然后缩点重构图,可以求得每一个包含[i,i+1]的最小Intrinsic Interval 。
从析合树来看,就是在求析点和相邻的合点。
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int INF = 1e9; 4 const int MAXN = 5e5 + 7; 5 int a[MAXN], b[MAXN], dl[MAXN], dr[MAXN]; 6 int dfs_clock, scc_cnt, dfn[MAXN], low[MAXN], sccno[MAXN]; 7 8 struct RMQ{ 9 static const int S = 20; 10 int lg[MAXN], mx[MAXN][S], mn[MAXN][S]; 11 void init(int *a, int n) { 12 for(int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1; 13 for(int i = 1; i <= n; i++) mn[i][0] = mx[i][0] = a[i]; 14 for(int k = 1; (1 << k) <= n; k++) 15 for(int i = 1; i + (1 << k) - 1 <= n; i++) { 16 mn[i][k] = min(mn[i][k - 1], mn[i + (1 << (k - 1))][k - 1]); 17 mx[i][k] = max(mx[i][k - 1], mx[i + (1 << (k - 1))][k - 1]); 18 } 19 } 20 inline int MIN(int l, int r) { 21 int len = lg[r - l + 1]; 22 return min(mn[l][len], mn[r - (1 << len) + 1][len]); 23 } 24 inline int MAX(int l, int r) { 25 int len = lg[r - l + 1]; 26 return max(mx[l][len], mx[r - (1 << len) + 1][len]); 27 } 28 } D, DL, DR; 29 namespace SCC{ 30 vector<int> G[MAXN]; 31 int vis[MAXN]; 32 vector<int> G2[MAXN]; 33 void addEdge(int u, int v) { 34 G[u].push_back(v); 35 } 36 int id[MAXN]; 37 struct SEG{ 38 int setL, setR, W; 39 int L[MAXN], R[MAXN]; 40 void init(int v, int l, int r) { 41 L[v] = l; R[v] = r + 1; 42 if(l == r) {id[l] = v; return;} 43 int mid = (l + r) >> 1; 44 addEdge(v, v << 1); 45 addEdge(v, v<<1|1); 46 init(v << 1, l, mid); 47 init(v<<1|1, mid+1, r); 48 } 49 void init2(int v, int l, int r) { 50 for(auto u : G[v]) { 51 if(sccno[v] != sccno[u]) { 52 G2[sccno[v]].push_back(sccno[u]); 53 } 54 } 55 if(l == r) return; 56 int mid =(l + r) >> 1; 57 init2(v << 1, l, mid); 58 init2(v<<1|1, mid+1, r); 59 } 60 void add(int v, int l, int r) { 61 if(setL <= l && r <= setR) { 62 addEdge(W, v); 63 return; 64 } 65 int mid = (l + r) >> 1; 66 if(setL <= mid) add(v << 1, l, mid); 67 if(mid < setR) add(v<<1|1, mid + 1, r); 68 } 69 }T; 70 stack<int> S; 71 int sl[MAXN], sr[MAXN]; 72 void tarjan_init(int n) { 73 dfs_clock = scc_cnt = 0; 74 for(int i = 0; i <= n; i++) { 75 dfn[i] = low[i] = sccno[i] = 0; 76 G[i].clear(); 77 } 78 T.init(1, 1, n); 79 for(int i = 1; i < n; i++) { 80 int ai = a[i], aj = a[i+1]; 81 if(ai > aj) swap(ai, aj); 82 T.setL = D.MIN(ai, aj); 83 T.setR = D.MAX(ai, aj); 84 if(T.setL > T.setR) swap(T.setL, T.setR); 85 --T.setR; 86 T.W = id[i]; 87 T.add(1, 1, n); 88 } 89 } 90 void tarjan(int u) { 91 dfn[u] = low[u] = ++dfs_clock; 92 S.push(u); 93 for(auto v : G[u]) { 94 if(!dfn[v]) { 95 tarjan(v); 96 low[u] = min(low[u], low[v]); 97 }else if(!sccno[v]) { 98 low[u] = min(low[u], dfn[v]); 99 } 100 } 101 if(dfn[u] == low[u]) { 102 scc_cnt++; 103 sl[scc_cnt] = INF; 104 sr[scc_cnt] = -INF; 105 for(;;) { 106 int v = S.top(); S.pop(); 107 sccno[v] = scc_cnt; 108 sl[scc_cnt] = min(sl[scc_cnt], T.L[v]); 109 sr[scc_cnt] = max(sr[scc_cnt], T.R[v]); 110 if(v == u) break; 111 } 112 } 113 } 114 void dfs(int u) { 115 vis[u] = 1; 116 for(auto v : G2[u]) { 117 if(!vis[v]) dfs(v); 118 sl[u] = min(sl[u], sl[v]); 119 sr[u] = max(sr[u], sr[v]); 120 } 121 } 122 void sol(int n) { 123 for(int i = 1; i < n; i++) 124 if(!dfn[id[i]]) tarjan(id[i]); 125 T.init2(1, 1, n); 126 for(int i = 1; i <= scc_cnt; i++) 127 if(!vis[i]) dfs(i); 128 for(int i = 1; i < n; i++) { 129 dl[i] = sl[sccno[id[i]]]; 130 dr[i] = sr[sccno[id[i]]]; 131 } 132 } 133 } 134 int main() { 135 int n; 136 scanf("%d", &n); 137 for(int i = 1; i <= n; i++) { 138 scanf("%d", a + i); 139 b[a[i]] = i; 140 } 141 D.init(b, n); 142 SCC::tarjan_init(n); 143 SCC::sol(n); 144 145 DL.init(dl, n); 146 DR.init(dr, n); 147 int m; 148 scanf("%d", &m); 149 for(int i = 1, xi, yi; i <= m; i ++) { 150 scanf("%d%d", &xi, &yi); 151 if(xi == yi) { 152 printf("%d %d ", xi, yi); 153 }else{ 154 int ai = DL.MIN(xi, yi - 1); 155 int bi = DR.MAX(xi, yi - 1); 156 printf("%d %d ", ai, bi); 157 } 158 } 159 return 0; 160 }
2)
参考:https://www.luogu.com.cn/blog/ywycasm/solution-p4747
Intrinsic Interval 还有一种表述:
定义(i,j)为一个好的二元组,当且仅当a[i]-a[j]=1
这样的两项的二元组在[l,r]中恰好有r-l个
所以,一个区间是好的区间,当且仅当好的二元组有r-l个
也就是 val + l = r, 其中val是区间[l,r]中好二元组的个数
枚举r,在[1,l]中如果存在 val + i = r, 最靠右的i就是答案
上面算式可以用线段树维护
如果 $$ a[r] > 1 && pos[a[r] - 1] < r $$ 这样[1,pos[a[r] -1] ] 就会多一个整数对。
如果 $$ a[r] < n && pos[a[r] + 1] < r $$ 这样[1,pos[a[r] + 1] ] 就会多一个整数对。
这个本质是将难以维护的问题转化为可维护的计数问题。
这种做法需要离线
#include <bits/stdc++.h> using namespace std; typedef pair<int, int> P; const int MAXN = 100001; int a[MAXN], b[MAXN]; P ans[MAXN]; struct que{ int l, r, id; bool operator < (const que &x) const { if(l == x.l && r == x.r) return id < x.id; if(l == x.l) return r < x.r; return l > x.l; } } q[MAXN]; struct SGT{ int setL, setR, setW; P val[MAXN << 2]; int tag[MAXN << 2]; void push_down(int v) { if(tag[v]) { val[v << 1].first += tag[v]; tag[v << 1] += tag[v]; val[v<<1|1].first += tag[v]; tag[v<<1|1] += tag[v]; tag[v] = 0; } } void push_up(int v) { val[v] = max(val[v << 1] , val[v<<1|1]); } void init(int v, int l, int r) { val[v] = {l, l}; tag[v] = 0; if(l == r) return; int mid = (l + r) >> 1; init(v<<1, l, mid); init(v<<1|1, mid + 1, r); push_up(v); } void upt(int v, int l, int r) { if(setL <= l && r <= setR) { val[v].first += setW; tag[v] += setW; return; } push_down(v); int mid = (l + r) >> 1; if(setL <= mid) upt(v<<1, l, mid); if(mid < setR ) upt(v<<1|1, mid+1,r); push_up(v); } P que(int v, int l, int r) { if(setL <= l && r <= setR) { return val[v]; } push_down(v); int mid = (l + r) >> 1; P res = {0, 0}; if(setL <= mid) res = max(res, que(v << 1, l, mid)); if(mid < setR ) res = max(res, que(v<<1|1, mid+1, r)); return res; } }T; int main() { int n; scanf("%d", &n); T.init(1, 1, n); for(int i = 1; i <= n; i++) { scanf("%d", a + i); b[a[i]] = i; } int m; scanf("%d", &m); for(int i = 1; i <= m; i++) { scanf("%d%d", &q[i].l, &q[i].r); q[i].id = i; } sort(q + 1, q + m + 1, [=] (const que &x, const que &y) { if (x.l == y.l && x.r == y.r) return x.id < y.id; if (x.r == y.r) return x.l < y.l; return x.r < y.r; }); set <que> st; for(int i = 1, j = 1; i <= n; i++) { while(j <= m && q[j].r == i) { st.insert(q[j]); ++j; } if(a[i] > 1 && b[a[i] - 1] < i) { T.setL = 1; T.setR = b[a[i] - 1]; T.setW = 1; T.upt(1, 1, n); } if(a[i] < n && b[a[i] + 1] < i) { T.setL = 1; T.setR = b[a[i] + 1]; T.setW = 1; T.upt(1, 1, n); } while(st.size()) { auto it = st.begin(); T.setL = 1; T.setR = it->l; P mx = T.que(1, 1, n); if(mx.first != i) break; else { ans[it->id].first = mx.second; ans[it->id].second = i; st.erase(it); } } } for(int i = 1; i <= m; i++) printf("%d %d ", ans[i].first, ans[i].second); return 0; }
3)
参考:https://oi-wiki.org/ds/divide-combine/
区间最大值-区间最小值=区间长度
也就是: mx-mn = r - l, 令 fx = (mx-mn) - (r - l)
析合树利用了单调栈维护了最大值最小值的断点位置,用线段树维护上式
在析合树上求lca就可以了
#include <bits/stdc++.h> using namespace std; const int MAXN = 200010; const int S = 22; typedef long long LL; namespace CST{ struct RMQ { int lg[MAXN], mn[MAXN][S+1], mx[MAXN][S+1]; inline void init(int *a, int n) { for (int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1; for (int i = 1; i <= n; i++) mn[i][0] = mx[i][0] = a[i]; for (int k = 1; (1 << k) <= n; k++) for (int i = 1; i + (1 << k) - 1 <= n; i++) { mn[i][k] = min(mn[i][k - 1], mn[i + (1 << (k - 1))][k - 1]); mx[i][k] = max(mx[i][k - 1], mx[i + (1 << (k - 1))][k - 1]); } } inline int Min(int l, int r) { int len = lg[r - l + 1]; return min(mn[l][len], mn[r - (1 << len) + 1][len]); } inline int Max(int l, int r) { int len = lg[r - l + 1]; return max(mx[l][len], mx[r - (1 << len) + 1][len]); } } D; struct SEG { int setL, setR, setW; int mn[MAXN << 2], tag[MAXN << 2]; inline void pushup(int x) { mn[x] = min(mn[x << 1], mn[x << 1 | 1]); } inline void pushdown(int x) { if(!tag[x]) return; mn[x << 1] += tag[x]; mn[x << 1 | 1] += tag[x]; tag[x << 1] += tag[x]; tag[x << 1 | 1] += tag[x]; tag[x] = 0; } void init(int x, int l, int r) { mn[x] = tag[x] = 0; if (l == r) return; int mid = (l + r) >> 1; init(x << 1, l, mid); init(x << 1 | 1, mid + 1, r); } void upt(int x, int l, int r) { if (setL <= l && r <= setR) { tag[x] += setW; mn[x] += setW; return; } pushdown(x); int mid = (l + r) >> 1; if (setL <= mid) upt(x << 1, l, mid); if (mid < setR ) upt(x << 1 | 1, mid+1, r); pushup(x); } int que(int x, int l, int r) { if (l == r) return l; pushdown(x); int mid = (l+r)>>1; if (!mn[x << 1]) return que(x << 1, l, mid); return que(x << 1 | 1, mid+1, r); } } T; int tpmn, stmn[MAXN], tpmx, stmx[MAXN], tpk, stk[MAXN]; int ncnt, type[MAXN<<1], L[MAXN<<1], R[MAXN<<1], M[MAXN<<1]; int dep[MAXN<<1], fa[MAXN<<1][S+1], C[MAXN<<1]; int id[MAXN << 1]; int newnode(int _type, int _L, int _R, int _M = 0) { ++ncnt; type[ncnt] = _type; L[ncnt] = _L; R[ncnt] = _R; M[ncnt] = _M; C[ncnt] = 0; return ncnt; } inline bool judge(int l, int r) { return D.Max(l, r) - D.Min(l, r) == r - l; } int ecnt, head[MAXN << 1]; struct Edge{int to, nxt;} e[MAXN<<1]; inline void addEdge(int x, int y) { e[++ecnt] = (Edge) {y, head[x]}; head[x] = ecnt; fa[y][0] = x; C[x]++; } void dfs(int u) { for(int j = 0; j < S; j++) fa[u][j+1] = fa[fa[u][j]][j]; for(int i = head[u]; i; i = e[i].nxt) { dep[e[i].to] = dep[u] + 1; dfs(e[i].to); } } inline void init(int n) { ecnt = 0; for(int i = 0; i <= n; i++) head[i] = 0; } void buildT(int *a, int n) { init(n); D.init(a, n); T.init(1, 1, n); tpmn = tpmx = tpk = 0; stmn[0] = stmx[0] = stk[0] = 0; for (int i = 1; i <= n; i++) { for (;tpmn && a[i] <= a[stmn[tpmn]]; --tpmn) { T.setL = stmn[tpmn - 1] + 1; T.setR = stmn[tpmn]; T.setW = a[stmn[tpmn]]; T.upt(1, 1, n); } T.setL = stmn[tpmn] + 1; T.setR = i; T.setW = -a[i]; T.upt(1, 1, n); stmn[++tpmn] = i; for (;tpmx && a[i] >= a[stmx[tpmx]]; --tpmx) { T.setL = stmx[tpmx - 1] + 1; T.setR = stmx[tpmx]; T.setW = -a[stmx[tpmx]]; T.upt(1, 1, n); } T.setL = stmx[tpmx] + 1; T.setR = i; T.setW = a[i]; T.upt(1, 1, n); stmx[++tpmx] = i; int Li = T.que(1, 1, n), np = id[i] = newnode(0, i, i), nq, nw; while (tpk && L[nq = stk[tpk]] >= Li) { if (type[nq] && judge(M[nq], i)) { R[nq] = i; addEdge(nq, np); np = nq; tpk--; } else if (judge(L[nq], i)) { nw = newnode(1, L[nq], i, L[np]); addEdge(nw, nq); addEdge(nw, np); np = nw; tpk--; } else { nw = newnode(0, -1, i); addEdge(nw, np); do { addEdge(nw, nq); nq = stk[--tpk]; } while (tpk && !judge(L[nq], i)); addEdge(nw, nq); L[nw] = L[nq]; R[nw] = i; np = nw; --tpk; } } stk[++tpk] = np; T.setL = 1; T.setR = i; T.setW = -1; T.upt(1, 1, n); } assert(tpk == 1); dfs(stk[tpk]); } void lca(int u, int v, int &aL, int &bR) { if(u == v) { aL = L[u]; bR = R[v]; return; } if(dep[u] > dep[v]) swap(u, v); for(int i = S; i >= 0; i--) if(dep[fa[v][i]] >= dep[u]) v = fa[v][i]; assert(u != v); for(int i = S; i >= 0; i--) if(fa[u][i] != fa[v][i]) { u = fa[u][i]; v = fa[v][i]; } if(type[fa[u][0]]) { aL = min(L[v], L[u]); bR = max(R[v], R[u]); }else{ aL = L[fa[u][0]]; bR = R[fa[u][0]]; } } }; int a[MAXN]; int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); CST::buildT(a, n); int q; scanf("%d", &q); while(q--) { int ai, bi, aL, bR; scanf("%d%d", &ai, &bi); CST::lca(CST::id[ai], CST::id[bi], aL, bR); printf("%d %d ", aL, bR); } return 0; }
第一种做法求scc和析合树本质都是在求本原连续段,但是析合树包含关系比较清楚,相对的,代码比较长
第二种做法和析合树本质相同,是析合树的简化版,在扫描过程中就统计完毕,但是这样需要离线。