NKOJ 3500 独立集
输入格式
输入包含两行,第一行为 N,
第二行为 1 到 N 的一个全排列
输出格式
输出包含两行,第一行输出最大独立集的大小,第二行从小到大输出一定在最大独立集 的点的编号。
样例输入
3
3 1 2
样例输出
2
2 3
数据规模
30%的数据满足 N<=16
60%的数据满足 N<=1,000
100%的数据满足 N<=100,000
首先不要被迷惑了,虽然“最大独立集”容易让我们想到二分图匹配,但是这道题的正解与二分图匹配一点关系也没有!
首先一定要知道对于给定的一个排列,按照题目中的方式建出的图是怎么样的。根据冒泡排序的实现过程,可以知道只有逆序对才会有边相连。
所以是树状数组or归并排序?虽然时间复杂度O(nlogn)看起来很契合数据规模,但是发现知道逆序对的数量并没有什么用,尤其是第二问,根本没有关系。
考虑独立集的特征。由于只要有逆序对,那么就有边相连;只要有边相连,就不是独立集,所以独立集里的点一定是在给出的排列里的上升子序列。由于是最大独立集,那么最大独立集的大小就是最长上升子序列的长度。这可以通过O(nlogn)的算法实现。
考虑第二问。实在想不出就可以暴力枚举每个点,判断去掉它会不会使得最长上升子序列的长度减小,如果减小就说明它是必须的,反之则不是。然而这样的时间复杂度为O(n2logn),会超时。
再考虑必须在最大独立集的点的特征。标程给的方法比较机智,这样的点必然满足三个特征:
1.它处在一个最长上升子序列中(废话)
2.在所有能处在一个最长上升子序列的点当中,以它为结尾的最长上升子序列的长度唯一
3.在所有能处在一个最长上升子序列的点当中,以它为开头的最长上升子序列的长度唯一
对于1,正反两次最长上升子序列算法就搞定了,只需要在倒着跑的时候把原排列中的每个元素改为负数即可。
对于2,3,注意到2和3是等价的,因为最长上升子序列的长度唯一。这是比较难理解的一点,下面给出两种解释:
第一种说得比较简单,来自PWJ大佬:
“以它为结尾的最长上升子序列的长度唯一”,长度其实表示了位置,也就是说在最长上升子序列中,这个位置只有一种选择。如果这个长度不是唯一的,说明在这个位置上有多种选择,那么任何一种选择可以互相替代,就都不是必须的。
第二种是我的解释,稍微啰嗦一点:
首先容易理解,如果这个长度不是唯一的,那么它一定不是必须的。因为这说明了有多种不同的方案。
然后解释为什么长度唯一就一定是必须的。采用反证法,假设以某个点为结尾的长度唯一,但是它却不是必须的。什么样的点不是必须的呢?就是存在一个最长上升子序列,满足这个点不在它当中。那么对于这一个最长上升子序列,在对应位置上存在一个与它不同的点,那么就与“以它为结尾的长度唯一”的条件矛盾了。
然后代码就比较好写了:
#include<stdio.h>
#include<algorithm>
using namespace std;
#define MAXN 100005
int N,p[MAXN],Ans=1,f[MAXN],g[MAXN],Cnt[MAXN],ff[MAXN],gg[MAXN];
int main()
{
int i,k,j,tot=0;
scanf("%d",&N);
for(i=1;i<=N;i++)scanf("%d",&p[i]);
for(i=1;i<=N;i++)g[i]=1e9;
f[1]=1;g[1]=p[1];
for(i=2;i<=N;i++)
{
j=lower_bound(g+1,g+Ans+1,p[i])-g;
f[i]=j;
g[j]=min(g[j],p[i]);
Ans=max(f[i],Ans);
}
printf("%d\n",Ans);
int Tmp=Ans;
Ans=1;
for(i=1;i<=N;i++)gg[i]=1e9;
for(i=N;i>=1;i--)
{
j=lower_bound(gg+1,gg+Ans+1,-p[i])-gg;
ff[i]=j;
gg[j]=min(gg[j],-p[i]);
Ans=max(ff[i],Ans);
}
for(i=1;i<=N;i++)if(f[i]+ff[i]-1==Tmp)Cnt[f[i]]++;
for(i=1;i<=N;i++)if(f[i]+ff[i]-1==Tmp&&Cnt[f[i]]==1)printf("%d ",i);
}