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\),使得:

  1. 区间 \([L,R]\) 是好区间;
  2. \(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;
}
posted @ 2022-03-26 10:18  Aphrosia  阅读(19)  评论(0编辑  收藏  举报