Vijous系列(1)LIS 最長不下降子序列的應用

1、P1028魔族密碼 :https://vijos.org/p/1028

分析:最長不下降子序列的變形, 把數字類比成前綴問題


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

using namespace std;

const int maxn=2005;
string word[maxn];
int a[maxn],s[maxn];

bool cmp(int i,int j)   //比較是否爲前綴
{
    int len=word[j].size();
    for (int k=0;k<len;k++)
        if (word[j][k] != word[i][k]) return false;
    return true;
}

int main()
{

    int n;
    scanf("%d",&n);
    for (int i=0;i<n;i++)
    {
        cin>>word[i];
        s[i]=word[i].size();
    }
    a[0]=1;
    for (int i=1;i<n;i++)  //LIS模板
    {
        for (int j=0;j<i;j++)
        {
            if (cmp(i,j))
                a[i]=max(a[i],a[j]);
        }
        a[i]++;
    }
    int ans=0;
    for (int i=0;i<n;i++)
        ans=max(a[i],ans);
    printf("%d",ans);
    return 0;
}


2、合唱隊形  https://vijos.org/p/1098

分析:左右分別進行兩遍LIS便可(入門題)

#include <cstdio>
#include <iostream>
  using namespace std;
 const int maxn=1005;
 int main()
 {
     int f1[maxn],f2[maxn],a[maxn];
     int n;
     scanf("%d",&n);
     for (int i=0;i<n;i++)
         scanf("%d",&a[i]);
     for (int i=0;i<n;i++)
     {
         f1[i]=1;
         f2[i]=1;
     }
     for (int i=1;i<n;i++)
         for (int j=0;j<i;j++)
             if (a[i]>a[j]) f1[i]=max(f1[i],f1[j]+1);
     for (int i=n-2;i>=0;i--)
         for (int j=n-1;j>i;j--)
         if (a[i]>a[j]) f2[i]=max(f2[i],f2[j]+1);
     int sum=0;
     for (int i=0;i<n;i++)
         sum=max(sum,f2[i]+f1[i]-1); //每個數被重複計數一次,應-1
     sum=n-sum;
     printf("%d",sum);
     return 0;
 }

3、P1369難解的問題  https://vijos.org/p/1369

分析:分別求2次LIS(第一次:以第K項爲結尾(一定要包含第K項)的最長LIS+
                    第二次:以第K項爲開始(一定要包含第K項)的最長LIS)
注意的地方:要用長整型,二分查找的修改由temp>=dp[mid].num修改爲temp>dp[mid].num
因爲例如數據 14 3
0 0 0 6 8 4 5 6 7 9 8 9 5 10
注意觀察前三項在DP時的變化。


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

using namespace std;

const int maxn=300005;

struct Node
{
    long long num;
};

Node dp[maxn];

int main()
{
    int n,k,len=0;
    long long tmp;
    scanf("%d%d",&n,&k);
    dp[0].num=-1;
    int top=0;

    for (int i=1;i<=k;i++)
    {
        scanf("%lld",&tmp);
        if (tmp > dp[top].num)
        {
            dp[++top].num=tmp;
            if (i == k)
            {
                len=top;
            }
        }
        else
        {
            int l=1,r=top;
            int mid;
            while (l <= r)
            {
                mid=(l+r)/2;
                if (tmp > dp[mid].num){
                    l=mid+1;
                }
                else{
                    r=mid-1;
                }
            }
            dp[l].num=tmp;
            if (i == k)
                len=l;
        }
    }

    top=1;
    dp[top].num=tmp;

    for (int i=k+1;i<=n;i++)
    {
        scanf("%lld",&tmp);
        if (tmp>dp[top].num){
            dp[++top].num=tmp;
        }
        else
        {
            int l=1,r=top;
            int mid;
            while (l <= r)
            {
                mid=(l+r)/2;
                if (tmp > dp[mid].num){
                    l=mid+1;
                }
                else{
                    r=mid-1;
                }
            }
            if (l!=1)
            {
                dp[l].num=tmp;
            }
        }
    }

    printf("%d\n",top+len-1);
    return 0;
}

4、P1474雷曼兔(csapc) https://vijos.org/p/1474

分析:LIS的變形,題目告訴只能出現1~n的內的數,切每個數都得出現一次。。。倒序LIS一次,計算最大的華麗度總和即可

//v=(|x1-x2|+|y1-y2|)^2

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

using namespace std;

struct node
{
    int x,y,score;
};


int poww(node a,node b)
{
    return (abs(a.x-b.x)+abs(a.y-b.y))*(abs(a.x-b.x)+abs(a.y-b.y));
}

node sum[2501];

int main()
{
    int n,a;
    scanf("%d",&n);

    for (int i=0;i<n;i++)
    for (int j=0;j<n;j++){
        scanf("%d",&a);
        sum[a].x=i;
        sum[a].y=j;
    }
    for (int i=n*n-1;i>=1;i--)   // 逆序LIS
    for (int j=i+1;j<=n*n;j++)
    {
        int p=poww(sum[i],sum[j]);
        sum[i].score=max(sum[j].score+p,sum[i].score);
    }
    printf("%d\n",sum[1].score);
    return 0;
}


5、P1571笨笨的導彈攻擊    https://vijos.org/p/1571

分析:使得這些被抽取出來的導彈奇數位置的編號大於其前一個的編號,偶數位置的編號小於其前一個的編號,這樣子才能夠正常使用這些導彈攻擊目標
用f[ i ] 表示到第i個導彈時,最大可擊中目標的導彈數,由題意知,需考慮奇數和偶數情況,接着LIS即可

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

using namespace std;

const int maxn=10005;
int a[maxn],f[maxn];

int main()
{
    int n;
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for (int i=1;i<=n;i++)
    for (int j=i+1;j<=n;j++)
    {
        if (f[i]%2==1){      //奇數情況
            if (a[j]>a[i]) f[j]=max(f[j],f[i]+1);
        }
        else if(f[i]%2==0)   //偶數情況
            if (a[j]<a[i]) f[j]=max(f[j],f[i]+1);
    }
    int ans=0;
    for (int i=1;i<=n;i++)
        ans=max(ans,f[i]);
    cout<<ans+1<<endl;
    return 0;
}

6、P1264神祕的咒語   https://vijos.org/p/1264

分析:基於LCS的LIS,LICS,最長不下降公共子序列。。。
dp[ i ] [ j ] 表示 第一串前 i 個 和 第二串前 j 個 並且以b[ j ] 爲結尾的 LICS



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

using namespace std;

const int maxn=505;

//// dp[i][j] 表示 第一串前i個, 第二串前j個 並且以b[j]結尾 的最大公共子序列

int a[maxn],b[maxn];
int dp[maxn][maxn];

int main()
{
    int t;
    scanf("%d",&t);
    while (t--)
    {
        int n,m;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%d",&a[i]);
        scanf("%d",&m);
        for (int i=0;i<m;i++)
            scanf("%d",&b[i]);
        memset(dp,0,sizeof(dp));
        for (int i=0;i<n;i++)
        {
            int _max=0;
            for (int j=0;j<m;j++)
            {
                dp[i+1][j+1]=dp[i][j+1];
                if (a[i] > b[j])
                {
                    _max=max(_max,dp[i][j+1]);
                }
                if (a[i] == b[j]) dp[i+1][j+1]=_max+1;
            }
        }
        int ans=0;
        for (int i=1;i<=m;i++)
            ans=max(ans,dp[n][i]);
        printf("%d\n",ans);
    }

    return 0;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章