训练日记2019.10.29 洛谷P1091合唱队形的(nlognlogn)算法

2019.10.29 星期二
今天还是硬核的一天,早上被困电梯,被成功救出来之后,火急火燎出门上课却发现课被取消了就我一个人不知道,然后跑到图书馆上自习了,我发现大学还是比高中辛苦一点,不过是,身边的人都在自习,高中能到这样强度的自习估计得有年级前50了吧。言归正传,今天坐进图书馆就开始继续学习动态规划,我发现不是我太笨,是之前打开方式不太对,一上去就做很难的题目导致全程懵逼。既然来了那肯定就得学上升子序列啊,之前我其实会写n^2的LIS算法,但是我觉得只会n方算法的程序员并不是一位好的acm选手,那也太简单了怎么能叫动态规划呢,然后我发现有nlogn的算法,原来是利用lower_bound函数进行查找,暑假看了几次lis,都是因为看不懂这个而学不会nlogn的算法,今天我下决心一定得学会。

仔细研究了半个小时发现这个东西并不难,开一个dp数组记录当前上升子序列的所有元素,dp的有效长度就是上升子序列的长度,这个应该都不难理解,不过里面有一步确实让人琢磨不透,就是lower_bound函数(查降序长度应该用upper_bound函数),而我们找到一个新的元素需要去判断这个东西能不能敲掉上升子序列的一个元素,使得最后上升子序列尽量平缓,比如有一组数字
1 2 10 11
这样找到上升子序列的可能性远没有
1 2 4 5高对吧?lower_bound函数可以解决这个问题,可以找到比他小的然后敲掉。这样就有可能连成更大的升序序列了,因为dp存的是lis,所以dp在lis长度之前自然是有序的,stl自带的lower_bound函数就是二分查找,可以把普通dp的n方算法降到nlogn, 嘿嘿嘿恰好今天作业里也有二分,我就理解了,lis算法的实质就是维护一个尽可能长的上升子序列的元素数组并且通过变量记住长度,记得这一点就行了,怎么推?我也不会推,这谁想得到啊?

然后写完炮兵阵地就接着下一道题,是合唱队形,就是求一个点作为分界线,然后围绕割点取升序长度和降序长度,noip的普及组数据并不大,然而acm的数据量让我养成了个习惯,就是一定要用最少的复杂度去解题,这道题需要一定的胆识,我大胆猜想如果在n找不到合适的位置,在n - 1也找不到,因为你是把割点算进升序序列长度的,满足有序!那就上二分!结果成功ac,这道题如果数据再大400,n^2 logn甚至是n立方算法就过不掉了,所以还是需要用更好的办法,题解如下,我的一天终于结束了,还有三周就回去了,加油!

洛谷P1091 合唱队形 (传送门
AC代码:

#include <bits/stdc++.h>
using namespace std;
#define limit 1000 + 5//防止溢出
#define INF 0x3f3f3f3f
#define inf 1<<20
#define lowbit(i) i&(-i)//一步两步
#define EPS 1e-6
#define ff(a) printf("%d\n",a );
#define MOD 1e9 + 7
typedef long long ll;   
void read(int &x){
    char ch = getchar();x = 0;
    for (; ch < '0' || ch > '9'; ch = getchar());
    for (; ch >='0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
}//快读
int n;//总共个数
int a[limit];
int dp[limit];//最长上升子序列
int dp2[limit];
int judge(int mid){
    memset(dp, 1, sizeof(dp));
    memset(dp2 , 1 , sizeof(dp2));//初始化
    int lis = 1 ,lds = 1;//两边合在一起再用整人数减掉就是解
    dp[1] = dp2[1] = a[1];
    for(int i = 1; i <= mid ; ++i){
        if(dp[lis] < a[i]){
            dp[++lis] = a[i];
        }else{
            int pos = lower_bound(dp + 1, dp + lis + 1, a[i]) - dp;
            dp[pos] = a[i];
        }
    }
    for(int i = mid ; i <= n ; ++i){
        if(dp2[lds] > a[i]){
            dp2[++lds] = a[i];
        }else{
            int pos = upper_bound(dp2 + 1, dp2 + 1 + lds, a[i] , greater<int>()) - dp2;
            dp2[pos] = a[i];
        }
    }
    return n - lds - lis + 1;
}
int main(){
    //freopen("C:\\Users\\administrator01\\CLionProjects\\untitled14\\data.txt", "rt" , stdin);
    scanf("%d" , &n);
    for(int i = 1 ; i <= n ; ++i){
        scanf("%d" , &a[i]);
    }
    //随机挑做最长上升子序列到mid,然后二分答案
    int L , R;
    L = 1, R = n;
    int mid;
    int ans = INF;

    while(L <= R){
        mid = L + (R - L) / 2;
        int res = judge(mid);
        if(ans > res){
            ans = res;
            L = mid + 1;
        }else{
            R = mid - 1;
        }
    }
    ff(ans)
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值