冒泡排序
2020.10.4
题目描述
有一个 \(n\) 个点的图 \(G\) ,最开始没有边。现有一个 \(1~n\) 的排列,若对于一组 \(i,j\in[1,n]\) 有 \(a_i>a_j\) 则在图 \(G\) 中连一条 \(i\leftrightarrow j\) 的无向边。求图 \(G\) 的最大独立集大小。其中 \(1\leq n\leq 10^5\) 。
解法
反图:图 \(G\) 在完全图上的补集
独立集:集合中任意两个点间都没有边相连的集合。
团:集合中任意两个点间都有边相连的集合。
最大独立集/团:集合元素个数最多的独立集/团。
性质:无向图的最大独立集等于其反图的最大团。
证明:显然,在反图中最大团里的点在原图中都没有边相互连接,则这些点即为原图的最大独立集。
回到此题,若其原图中有连边 \(i\leftrightarrow j\) 则在序列中,\(a_i\) 和 \(a_j\) 一定为逆序对。反过来,如果在反图中有连边则在序列中为顺序对。而最大团中的点都相互有连边,则这些点共同组成了一个上升子序列。那么最大团大小即为最长上升子序列的长度。又有此序列为一个排列,那么显然可以直接用树状数组维护。
#include<stdio.h>
#define N 100007
int c[N],n;
inline int max(int x,int y){return x>y? x:y;}
inline int lowbit(int x){return x&(-x);}
inline void add(int x,int v){while(x<=n){c[x]=max(c[x],v);x+=lowbit(x);}}
inline int query(int x){int ret=0;while(x){ret=max(ret,c[x]);x-=lowbit(x);}return ret;}
inline int read(){
int x=0,flag=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
return flag? x:-x;
}
int f[N],a[N];
int main(){
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++) a[i]=read();
int ans=0;
for(int i=1;i<=n;i++){
f[i]=query(a[i])+1;
add(a[i],f[i]);
ans=max(ans,f[i]);
}
printf("%d",ans);
}
/*
3
3 1 2
5
4 1 5 3 2
*/