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]);
}