一道CF送命题引发的博文

来源:互联网 发布:java项目流程图 编辑:程序博客网 时间:2024/05/17 06:53

今天想把这道藏了1年的题写完,就顺便把相关算法看了一下。

包括3部分代码:    LIS O(n^2)的写法     LIS O(nlogn)的写法    送命题:codeforces714E 代码

叫送命题是因为这是去年打网络赛期间的一道集训题,当时没公布题号,就放下了,后来遇到了好多类似的题,都GG了,终于在前几天找到了这道题,果断决定写一写,发现是一道DP+LIS的题目,就把相关代码一起贴出来吧。

LIS

最长上升子序列。    例如序列:5 1 2 6 4 7     LIS序列可以为:1 2 4 7    长度为4。

LIS O(n^2)的写法

其实很简单,就是一个简单的DP,思路自行百度。

代码:

#include<bits/stdc++.h>using namespace std;int LIS(int a[],int len){    if(len==0)        return 0;    int *dp=new int[len];    for(int i=0;i<len;i++) dp[i]=1;    for(int i=1;i<len;i++)        for(int j=0;j<i;j++)            if( a[j] < a[i] )                dp[i]=max(dp[i],dp[j]+1);    int ans=1;    for(int i=0;i<len;i++)        ans=max(ans,dp[i]);    return ans;}int main(){    int n;    scanf("%d",&n);    int *a = new int[n];    for(int i=0;i<n;i++)        scanf("%d",&a[i]);    printf("%d\n",LIS(a,n));    return 0;}

LIS O(nlogn)的写法

主要是用一个数组s记录dp[i]的对应元素中的最小值,然后从左倒右依次扫描元素数组,对于每个元素在s中找到适合的位置,满足:    if(x>=s[slen]) s[slen++]=x;    else 找到s[i]>x的第一个位置,s[i]=x;这种查找位置用2分查找,则时间复杂度就降为O(nlogn),可以这样做的原因自行百度。需要注意一点:s数组记录的值并不一定就是LIS序列(准确说只有极特殊的情况才是)。

代码:

#Include<bits/stdc++.h>using namespace std;int find(int s[],int len,int x)//2分查找{    int a=0,b=len-1,mid;    if(x>s[len-1]) return len;    while(a<b)    {        mid=(a+b)/2;        if(s[mid]<x)            a=mid+1;        else            b=mid;    }    if(a==0)        return 0;    return a;}int LIS(int a[],int len){    if(len==0)        return 0;    int s[len],slen=1,point;    s[0]=a[0];    for(int i=1;i<len;i++)    {        point=find(s,slen,a[i]);        if(point<slen)            s[point]=a[i];        else        {            s[slen]=a[i];            slen++;        }    }    return slen;}int main(){    int A[10001];    int len;    cin >> len;    for(int i=0;i<len;i++)        cin >>A[i];    cout<<LIS(A, len)<<endl;    return 0;}

codeforces 714E

题目描述:

    Sonya was unable to think of a story for this problem, so here comes the formal description.    You are given the array containing n positive integers. At one turn you can pick any element and increase or decrease it by 1. The goal is the make the array strictly increasing by making the minimum possible number of operations. You are allowed to change elements in any way, they can become negative or equal to 0.Input    The first line of the input contains a single integer n (1 ≤ n ≤ 3000) — the length of the array.    Next line contains n integer ai (1 ≤ ai ≤ 109).Output    Print the minimum number of operation required to make the array strictly increasing.

题目分析:

简单点说就是给一组数,求最少经过多少次操作使数组满足严格的单调递增,其中每次操作为将一个数+1或-1。ok,我们来想一下,对于一个单调递增数组,应满足以下条件:    对于任意i,满足a[i+1]-a[i]>=1;    更普通的,对于任意i<j,满足a[j]-a[i]>=j-i;    转换一下格式,得到这样的关系:对于任意i<j,满足a[j]-j>=a[i]-i;于是我们只需将a[i]-i这样一个数组变为单调非递减序列。之后将a[i]-i记为数组a,将其排序后的数组记为数组b。建立一个二维dp数组,dp[n+1][n+1]。它的一维分量表示得到对第i个元素进行操作的次数。它的二维分量表示以b[1] ~ b[j]为基准得到ans[1] ~ ans[i]所要进行的操作次数。则dp的每个数据可以由两种方式得到,以dp[i][j]为例:    1.dp[i-1][j]+abs(a[i]-b[j]),即利用b[1] ~ b[j]得到a[1] ~ a[i-1]所需要的最小操作数,加利用(且此时只能利用)b[j]为基准得到ans[i]所需要的操作。    2.dp[i][j-1],即利用b[1] ~ b[j-1] 得到ans[1] ~ ans[i]所需要的最小操作数。    3.特殊的,当j=1时,只能由第一种情况得到。可能这样说比较抽象,我们举个例子。    假设最终的a数组为:                          2 1    则b数组应为:                               1 2则可以根据本题思想得到4种结果:    以b[1]为基准得到ans数组:        ans  1 1    以b[2]位基准得到ans数组:        ans  2 2    以b[1]为基准得到ans[1],以b[2]为基准得到ans[2]:        ans  1 2    以b[2]为基准得到ans[1],以b[1]为基准得到ans[2]:        ans  2 1  //这时是不满足题意的,这也解释了上面括号里的内容。这样,就得到了状态转移方程:dp[i][j] = min{dp[i][j-1] , dp[i-1][j]+abs(a[i]-b[j])};如果你还不明白,没关系,我们用这个方程进行一次解题过程,你就会明白了。仍然用上面的例子:    假设最终的a数组为:                          2 1    则b数组应为:                               1 21.首先,用b[1]得到ans[1],即将a[1]变为1,则需要进行1次操作;dp[1][1]=1;2.用b[2]得到ans[1],即将a[1]变为2,需要进行0次操作;3.用b[1]和b[2]得到ans[1]的最优解,即比较0与1,得到dp[1][2]=min(1,0)=0;4.用b[1]得到ans[2],即将a[2]变为1,则需要进行0次操作;5.用b[1]得到ans[1],ans[2]的总操作数为1+0=1,dp[2][1]=1;6.用b[2]得到ans[2],即将a[2]变为2,则需要进行1次操作;7.dp[2][2]有两种得到方式:    用b[1]得到ans[1],ans[2]的操作数,即dp[2][1];    用b[1],b[2]得到ans[1]的最优解的操作数(即dp[1][2])与只能用b[2]得到ans[2]的操作数之和 , 即dp[1][2]+abs(a[1]-b[2]);(因为得到dp[1][2]的结果可能用到了b[2],即将a[1]变成了b[2],则a[2]只能选择变为b[2]或更大的数字)    比较这两种方式得到最优解dp[2][2] = min{dp[2][1],dp[1][2]+abs(a[1]-b[2])};这样dp[2][2]就是我们要求的结果了。

代码:

#include<bits/stdc++.h>using namespace std;const int maxn = 3000+5;long long dp[maxn][maxn];int a[maxn],b[maxn];int main(){    int n;    scanf("%d",&n);    memset(dp,0,sizeof(dp));    for(int i=1;i<=n;i++)    {        scanf("%d",&a[i]);        a[i]-=i;        b[i]=a[i];    }    sort(b+1,b+n+1);    for(int i=1;i<=n;i++)    {        dp[i][1]=dp[i-1][1]+abs(a[i]-b[1]);        for(int j=2;j<=n;j++)            dp[i][j]=min( dp[i][j-1] , dp[i-1][j] + abs(a[i]-b[j]) );    }    printf("%lld\n",dp[n][n]);    return 0;}
到这里,终于将这道题ac了。
1 0
原创粉丝点击