[日常训练] 独立集

【问题描述】

有一天,一个名叫顺旺基的程序员从石头里诞生了。又有一天,他学会了冒泡排序和独立集。在一个图里,独立集就是一个点集,满足任意两个点之间没有边。于是他就想把这两个东西结合在一起。众所周知,独立集是需要一个图的。那么顺旺基同学创造了一个算法,从冒泡排序中产生一个无向图。
这个算法不标准的伪代码如下:
Pascal版本

procedure bubblesortgraph(n, a[]) :
//输入:点数n,1到n的全排列a。
//输出:一个点数为n的无向图G。
//创建一个有n个点,0条边的无向图G。
repeat
   swapped = false
   for i 从 1 到 n-1if a[i] > a[i + 1] :
          //在G中连接点a[i]和点a[i + 1]
          //交换a[i]和a[i + 1]
          swapped = true
until not swapped
//输出图G。
//结束。   

C/C++版本

void bubblesortgraph(n,a[])
  //输入:点数n,1到n的全排列a
  //输出:一个点数为n的无向图G
{ //创建一个有n个点,0条边的无向图G。
   do{  swapped=false
        for i 从1 到n-1
          if(a[i]>a[i+1])
           {  //在G中连接点a[i]和点a[i+1]
              //交换a[i]和a[i+1]
              swapped =true
          }
     }while(swapped);
   //输出图G。
}
//结束

那么我们要算出这个无向图G最大独立集的大小。但是事情不止于此。顺旺基同学有时候心情会不爽,这个时候他就会要求你再回答多一个问题:最大独立集可能不是唯一的,但有些点是一定要选的,问哪些点一定会在最大独立集里。今天恰好他不爽,被他问到的同学就求助于你了。

【输入格式】

输入两行。第一行为N,第二行为1到N的一个全排列。

【输出格式】

输出两行。第一行输出最大独立集的大小,第二行从小到大输出一定在最大独立集的点的编号。

【输入输出样例】

bubble.in bubble.out
3
3 1 2 2
2 3

【数据范围】

30%的数据满足 N<=16。
60%的数据满足 N<=1,000。
100%的数据满足 N<=100,000。

【分析】 O(nlogn) 最长不下降(上升)子序列

  • 首先得看懂题目:我们要求独立集上的点互相之间没有边,则首先要满足 a[i]a[i+1] ,又因为这是冒泡排序,实际上独立集中的点就满足 a[j]a[i](j<i) ,那么最大独立集就被我们转化为求最长不下降子序列,直接用 O(nlogn) 算法求出,记为 Ans
  • 然后我们考虑第二问:记 f1[i] 表示区间 [1,i] 中包含点 i 的最长不下降子序列长度,f2[i]表示区间 [i,n] 中包含点 i 的最长不下降子序列长度(也就是从点n开始,倒着求一遍最长不上升子序列),那么对于可能存在于区间 [1,n] 中的最长不下降子序列的点,必然要满足 f1[i]+f2[i]1=Ans ,我们将这些点统计出来
  • 接下来我们记 G[k] 表示长度为 k 的最长不下降子序列最多能以多少个不同的点结尾,令可能存在于最长不下降子序列中的点i G[f1[i]]++ 。那么若对这些点统计完后仍存在 G[f1[i]]=1 ,则这个点 i 一定在最长不下降子序列中,也就是我们第二问的答案
  • 最后顺便附上O(nlogn)求最长不下降子序列算法讲解链接:
    http://blog.163.com/general_happy/blog/static/1693514082011024112934126/

【代码】

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int Maxn = 0x3f3f3f3f;
const int N = 1e5 + 5;
int f1[N], f2[N], G[N], a[N], b[N];
int n, k, Ans;

inline int get()
{
    char ch; int res;
    while ((ch = getchar()) < '0' || ch > '9');
    res = ch - '0';
    while ((ch = getchar()) >= '0' && ch <= '9')
     res = (res << 3) + (res << 1) + ch - '0';
    return res; 
}

inline void put(int x)
{
    if (x > 9) put(x / 10);
    putchar(x % 10 + 48);
}

inline int Find1(const int &x)
{
    int l = 1, r = k, res = 0;
    while (l <= r)
    {
        int mid = l + r >> 1;
        if (b[mid] < x) res = mid, l = mid + 1;
         else r = mid - 1;
    }
    return res + 1;
}

inline int Find2(const int &x)
{
    int l = 1, r = k, res = 0;
    while (l <= r)
    {
        int mid = l + r >> 1;
        if (b[mid] > x) res = mid, l = mid + 1;
         else r = mid - 1;
    }
    return res + 1;
}

int main()
{
    freopen("bubble.in", "r", stdin);
    freopen("bubble.out", "w", stdout);
    n = get(); int x;
    for (int i = 1; i <= n; ++i) a[i] = get();
    for (int i = 1; i <= n; ++i)
     if (a[i] >= b[k]) b[++k] = a[i], f1[i] = k;
      else f1[i] = x = Find1(a[i]), b[x] = a[i];
    Ans = k; b[k = 0] = Maxn;
    for (int i = n; i >= 1; --i)
     if (a[i] <= b[k]) b[++k] = a[i], f2[i] = k;
      else f2[i] = x = Find2(a[i]), b[x] = a[i];
    put(Ans), putchar('\n');
    for (int i = 1; i <= n; ++i)
     if (f1[i] + f2[i] - 1 == Ans) G[f1[i]]++;
    for (int i = 1; i <= n; ++i)
     if (f1[i] + f2[i] - 1 == Ans && G[f1[i]] == 1) put(i), putchar(' ');
    fclose(stdin); fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值