CERC2017 Intrinsic Interval
CERC2017 Intrinsic Interval
去年做过的题,今天一看居然忘了怎么做。对着自己的代码想了半天才会,然后发现这题很有意思。
简要题意
给定长度为 \(n\) 的排列 \(P\)。
定义 \(P\) 的区间 \([L,R]\) 是好区间,当且仅当将 \(P_L,P_{L+1},P_{L+2}\dots P_{R}\) 排序后,相邻两个元素之差都为 \(1\)。
现有 \(q\) 次询问,每次询问,包含区间 \(l,r\) 的最小的好区间 的左右端点;也即求出 \(L,R\),使得:
- 区间 \([L,R]\) 是好区间;
- \(1 \le L \le l\le r\le R \le n\)。
在此基础上使得 \(R-L\) 最小。
\(n,q \le 10^5\)。
题解
答案区间显然是存在且唯一的。
首先证明一个性质:
- 若 \([l_1, r_1]\) 和 \([l_2,r_2]\) 都是好区间,且 \(l_1 \le l_2 \le r_1 \le r_2\)(即两区间有交),则 \([l_1, r_2]\) 也是好区间。
证明:考虑区间 \([l_2,r_1]\),它既包含在 \([l_1,r_1]\) 中元素组成的连续段中,也包含在 \([l_2,r_2]\) 中元素组成的连续段中。于是需要容斥一下:区间 \([l_1, r_2]\) 内,满足 \(|P_{i}-P_{j}|=1\) 的无序对 \((i,j)\) 的数量为 \((r_1-l_1) + (r_2-l_2) - (r_1-l_2) = r_2 - l_1\),也即区间 \([l_1, r_2]\) 为好区间。
对于 \(P\) 中相邻元素 \(P_{i}, P_{i+1}\),考虑求出将 \(\min(P_{i},P_{i+1})\) 到 \(\max(P_{i},P_{i+1})\) 之间所有元素都包含的最小区间,记作 \(ml_{i}\) 和 \(mr_i\)。通过预处理排名数组的区间最小/最大值即可查询。那么如果询问区间包含 \([i,i+1]\),则 \([ml_i, mr_{i}]\) 都必须包含其中。这可以图论建模,有向边 \(x\to y\) 的意义为若 \(x\) 在区间中,则 \(y\) 也必须在区间中。
那么把 \(i\) 向区间 \([ml_i, mr_i]\) 中的所有点连边。则若 \(i\) 在区间中,则 \(i\) 所在的强连通分量中的结点都必须在区间中。Tarjan + 缩点后,就可以在 DAG 上 拓扑排序或 dfs 求出缩点后每个结点所牵连的区间大小(即选了该结点后,有多大的区间必须被选),把这个区间记为结点的答案区间。
现在考虑一次询问 \([l,r]\)。那么根据上面的结论,\([l,l+1], [l+1,l+2]\dots[r-1,r]\) 的对应结点的答案区间的并。维护区间可达的最左端点和最右端点即可查询。
代码实现
实现中,建图可以使用线段树优化;区间最小值、最大值、最左端点和最右端点都可以使用 ST 表处理。
#include <cstdio>
#include <iostream>
#include <cstring>
#define minn 0
#define maxx 1
using namespace std;
const int MAXN = 100010;
namespace STtable
{
int lg[MAXN];
inline void getLg(int n)
{
lg[0] = -1;
for(int i=1;i<=n;i++)
lg[i] = lg[i>>1] + 1;
}
int f[2][MAXN][24];
inline void build(int n)
{
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
{
f[minn][i][j] = min(f[minn][i][j-1], f[minn][i+(1<<j-1)][j-1]);
f[maxx][i][j] = max(f[maxx][i][j-1], f[maxx][i+(1<<j-1)][j-1]);
}
}
inline int qrymax(int l, int r)
{
if(l > r) return 0;
int k = lg[r-l+1];
return max(f[maxx][l][k], f[maxx][r-(1<<k)+1][k]);
}
inline int qrymin(int l, int r)
{
if(l > r) return 0;
int k = lg[r-l+1];
return min(f[minn][l][k], f[minn][r-(1<<k)+1][k]);
}
}
using STtable::getLg;
using STtable::build;
using STtable::qrymax;
using STtable::qrymin;
struct edge{
int ne, to;
}g[MAXN<<3];
int head[MAXN<<2], num = 0;
inline void join(int a, int b)
{
g[++num].ne = head[a];
head[a] = num;
g[num].to = b;
}
int ind[MAXN];
struct node{
int l, r;
}t[MAXN<<2];
int maxn = 0;
void buildtree(int l, int r, int k)
{
t[k].l = l; t[k].r = r;
if(l == r)
{
ind[l] = k;
return ;
}
int mid = l+r>>1;
join(k, k<<1); join(k, k<<1|1);
buildtree(l, mid, k<<1);
buildtree(mid+1, r, k<<1|1);
}
void addEdge(int l, int r, int pos, int k)
{
if(t[k].l >= l && t[k].r <= r)
{
if(pos != k) join(pos, k);
return ;
}
int mid = t[k].l+t[k].r>>1;
if(l <= mid) addEdge(l, r, pos, k<<1);
if(r >= mid+1) addEdge(l, r, pos, k<<1|1);
}
int dfn[MAXN<<2], low[MAXN<<2], cnt = 0, stk[MAXN<<2], top = 0;
int pos[MAXN<<2], tot = 0;
bool vis[MAXN<<2];
void tarjan(int u)
{
dfn[u] = low[u] = ++cnt;
vis[u] = 1;
stk[++top] = u;
for(int i=head[u];i;i=g[i].ne)
{
int v = g[i].to;
if(!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
else if(vis[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
pos[u] = ++tot;
vis[u] = 0;
while(stk[top] != u)
{
pos[stk[top]] = tot;
vis[stk[top]] = 0;
--top;
}
--top;
}
}
struct newedge{
int ne, to;
}e[MAXN<<3];
int fir[MAXN<<2];
inline void joinnew(int a, int b)
{
e[++num].ne = fir[a];
fir[a] = num;
e[num].to = b;
}
inline void rebuild()
{
num = 0;
for(int u=1;u<=maxn;u++)
{
for(int i=head[u];i;i=g[i].ne)
{
int v = g[i].to;
if(pos[u] != pos[v]) joinnew(pos[u], pos[v]);
}
}
}
int vl[2][MAXN<<2], nw[2][MAXN<<2];
void dfs(int u)
{
if(vis[u]) return ;
vis[u] = 1;
for(int i=fir[u];i;i=e[i].ne)
{
int v = e[i].to;
dfs(v);
nw[minn][u] = min(nw[minn][u], nw[minn][v]);
nw[maxx][u] = max(nw[maxx][u], nw[maxx][v]);
}
}
int n, m, p[MAXN], tmp[MAXN];
int main()
{
freopen("perm.in","r",stdin);
freopen("perm.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&p[i]);
buildtree(1, n, 1);
getLg(n);
for(int i=1;i<=n;i++)
STtable::f[minn][p[i]][0] = STtable::f[maxx][p[i]][0] = i;
build(n);
maxn = (n<<2)-1;
for(int i=1;i<n;i++)
{
int x = min(p[i], p[i+1]), y = max(p[i], p[i+1]);
vl[minn][i] = qrymin(x, y);
vl[maxx][i] = qrymax(x, y);
addEdge(vl[minn][i], vl[maxx][i]-1, ind[i], 1);
}
for(int i=1;i<=maxn;i++)
if(!dfn[i]) tarjan(i);
rebuild();
for(int i=1;i<=tot;i++)
nw[minn][i] = n+1, nw[maxx][i] = 0;
for(int i=1;i<n;i++)
{
nw[minn][pos[ind[i]]] = min(nw[minn][pos[ind[i]]], vl[minn][i]);
nw[maxx][pos[ind[i]]] = max(nw[maxx][pos[ind[i]]], vl[maxx][i]);
}
memset(vis, 0, sizeof(vis));
for(int i=1;i<=tot;i++)
dfs(i);
for(int i=1;i<n;i++)
for(int j=0;j<2;j++)
STtable::f[j][i][0] = nw[j][pos[ind[i]]];
build(n-1);
scanf("%d",&m);
while(m--)
{
int l, r;
scanf("%d%d",&l,&r);
if(l == r) printf("%d %d\n",l,r);
else printf("%d %d\n",qrymin(l, r-1),qrymax(l, r-1));
}
return 0;
}