CF798E Mike and code of a permutation

一、题目

点此看题

二、解法

首先题目的限制显然可以转成若干偏序关系:

  • 如果 \(a_i=-1\),那么找到所有未被标记的 \(j\in[1,n]\),把 \(j\)\(i\) 连一条边,表示 \(p_j<p_i\)
  • 如果 \(a_i\not=-1\),那么找到所有未被标记的 \(j\in[1,a_i)\),把 \(j\)\(i\) 连一条边,表示 \(p_j<p_i\);然后把 \(i\)\(a_i\) 连一条边,表示 \(p_i<p_{a_i}\)

考虑对这个图暴力拓扑,时间复杂度 \(O(n^2)\),考虑优化。一个 \(\tt naive\) 的思路是把所有点的入度维护出来,但是发现并不好维护。

这时候要用一个常见转化:如果不是计数问题,那么可以考虑把维护数量转化成维护最值。

那么原来的拓扑做法肯定不能要了,这里给出一种崭新的拓扑方法:建出原图的反图,然后枚举 \(1\rightarrow n\),如果当前点没有被访问过就以其为起点在反图上 \(\tt dfs\),最后回溯的顺序就是拓扑序。

那么这种方法就只需要知道反图上一个点连出去的点是什么,也就是权值大\(\rightarrow\)权值小的边。设 \(b_i\) 表示 \(i\) 点被标记的时间点,如果没有被标记 \(b_i=n+1\);如果 \(a_i=-1\) 我们令 \(a_i=n+1\)。考虑只会有两种边:\(i\rightarrow a_i(a_i\not=n+1)\)\(i\rightarrow j(j<a_i,i<b_j)\)

我们不用真正维护出边,而是选出最有可能被遍历的那个点去更新它,依据这个思路把它转成最值问题。也就是我们对 \(i\) 为下标建立线段树,维护 \(b_i\) 的最大值,更新时找到 \(b_j\) 最大的 \(j\in[1,a_i)\) 看满不满足 \(b_j>i\),显然如果不满足的话也就没有其它出边了,时间复杂度 \(O(n\log n)\)

三、总结

普通算法的另类形式一定要注意积累,很多奇怪题会用到(最小生成树的另类形式是最热门的)

维护什么东西值得思考,注意维护数量到维护最值的转化。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 500005;
#define pii pair<int,int>
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,cnt,a[M],b[M],c[M];pii mx[4*M];
void ins(int i,int l,int r,int id)
{
	if(l==r)
	{
		mx[i]=make_pair(b[id],id);
		return ;
	}
	int mid=(l+r)>>1;
	if(mid>=id) ins(i<<1,l,mid,id);
	else ins(i<<1|1,mid+1,r,id);
	mx[i]=max(mx[i<<1],mx[i<<1|1]);
}
pii ask(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return make_pair(0,0);
	if(L<=l && r<=R) return mx[i];
	int mid=(l+r)>>1;
	return max(ask(i<<1,l,mid,L,R),
	ask(i<<1|1,mid+1,r,L,R));
}
void dfs(int u)
{
	int k=b[u];b[u]=0;
	ins(1,1,n,u);
	if(k!=n+1 && b[k]) dfs(k);
	while(1)
	{
		pii r=ask(1,1,n,1,a[u]-1);
		if(r.first<=u) break;
		dfs(r.second);
	}
	c[u]=++cnt;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		if(a[i]!=-1) b[a[i]]=i;
	}
	for(int i=1;i<=n;i++)
	{
		if(a[i]==-1) a[i]=n+1;
		if(!b[i]) b[i]=n+1;
		ins(1,1,n,i);
	}
	for(int i=1;i<=n;i++)
		if(!c[i]) dfs(i);
	for(int i=1;i<=n;i++)
		printf("%d ",c[i]);
}
posted @ 2021-08-27 15:39  C202044zxy  阅读(285)  评论(1编辑  收藏  举报