开始dp之路

https://paste.ubuntu.com/p/svzbg6zTkv/开始系统刷dp啦。先从kuangbin开始;

一个好的启蒙博客https://blog.csdn.net/trochiluses/article/details/37966729
也许,DP算法的难度不在于告诉你这个题目需要用DP求解,然后让你来实现算法。而在于你首先得意识到这道题目需要用递归求解,这里我们通过分析上面的思考步骤来总结DP算法的典型特征:
1>DP算法起源于DC—— 一个问题的解,可以先分解为求解一系列子问题的解,同时包含重叠子问题:于是,我们得到DP算法的第一个黄金准则:某个问题具有独立而重叠的字问题;子问题不独立,没法进行分治;子问题不重叠,没有进行DP的必要,直接用普通的分治法就可以了。
2>DP算法黄金准则2: 最优子问题—— 子问题的最优解可以推出原问题的最优解。
我们还是来看上面的那个决策树,很明显,DP的本质就在于缓存。我们寻找DP结果的时候,往往是需要遍历这个树,从中找出最优解。但是有些情况下,我们需要寻找的不是最优解,而是可行解,这个时候往往使用DFS或者循环更为有效,后面,我们会给出例子。此时,我们仅仅需要记得,动态规划的第二个条件—— 最优子问题。

所以算法的设计思路不在于一下子就想到了某个问题可以使用DP算法,而在于先看能不能用穷举法,如果可以用问题可以分解,分治法+穷举可以解决;如果问题包含重叠字问题,并且是求解最优解,那么此时用动态规划。

kuangbin


太多题目不会全写

A - Max Sum Plus Plus
昨天写的有点忘了。晚点写。
B - Ignatius and the Princess IV
一看这题。离散化一下直接写啊没想到咋dp。我想写法可能错但是反正离散化写一下就当这种算法行不行,结果直接A了。。。去看了下dp代码。这是道傻逼题啊,数据小到连离散化都不用。kuangbin怎么这么水的题啊
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
vector<int> v;
int getid(int k)
{
    return lower_bound(v.begin(),v.end(),k)-v.begin()+1;
}
int main() {
    int n;int s[1000007],z[1000007];
    while(~scanf("%d", &n)) {
        int mj = 0;
        if ((n + 1) % 2)mj = (n + 1) / 2 + 1;
        else mj = (n + 1) / 2;
        v.clear();
        for (int i = 1; i <= n; i++) {s[i]=0;z[i]=0;}
        for (int i = 1; i <= n; i++) {
            scanf("%d", &s[i]);
            v.push_back(s[i]);
        }
        sort(v.begin(), v.end());
        for (int i = 1; i <= n; i++) {
            z[getid(s[i])]++;
            if (z[getid(s[i])] >= mj) {
                cout << v[getid(s[i])] << endl;
                break;
            }
        }
    }
}
C - Monkey and Banana
看了题解。简单来讲就是一个方块给6个数据(定向)地价高度得。(这个题没写出来不熟思路)
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn=200;
struct node{
    int x,y,z;
}square[maxn];
int cmp(node a,node c){
    if(a.x!=c.x)return a.x<c.x;
    return a.y<c.y;
}
int cou=0;
void add(int x,int y,int z){
    square[cou].x=x;
    square[cou].y=y;
    square[cou++].z=z;
}
int main()
{
    int n;int dp[maxn];int zyx=0;
    while(~scanf("%d",&n)&&n){
        zyx++;cou=0;
        int x,y,z;
        for(int i=0;i<n;i++){
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
            add(x,z,y);
            add(y,x,z);
            add(y,z,x);
            add(z,y,x);
            add(z,x,y);
        }
        sort(square,square+cou,cmp);
        int ans=0;
        memset(dp,0, sizeof(dp));
        for(int i=0;i<cou;i++){
            dp[i]=square[i].z;
            for(int k=0;k<i;k++)
            if(square[k].x<square[i].x&&square[k].y<square[i].y)
            dp[i]=max(dp[i],dp[k]+square[i].z);
            ans=max(ans,dp[i]);
        }
        printf("Case %d: maximum height = %d\n",zyx,ans);
    }
}

D原题不想写了(状压建议下次再写一遍)E最长上升子序列不用再写了

F - Piggy-Bank
完全背包裸题。挺简单的,几个知识点注意一下。 1.完全背包之前说过背包就将这里不说 2.处理刚刚好装满两种 ①求最大值: fill(dp,dp+maxn,-INF), dp[0]=0;

若 dp[j] < 0,说明容量为 j 的背包无法被装满;

②求最小值:
fill(dp,dp+maxn,INF), dp[0]=0;

若 dp[j] == INF,说明容量为 j 的背包无法被装满;
3.坑点The minimum amount of money in the piggy-bank is .这里有句号(wa了一发)

#include <stdio.h>
#include <algorithm>
using namespace std;
const int maxn=600;
const int maxx=10006;
const int inf=0x3f3f3f3f;
int main()
{
    int c;scanf("%d",&c);
    while(c--) {
        int n1, n2, W;
        scanf("%d%d", &n1, &n2);
        W = n2 - n1;
        int n;
        scanf("%d", &n);
        int f[maxx],w[maxn],v[maxn];
        for (int i = 1; i <= n; i++){
            scanf("%d%d",&v[i],&w[i]);
        }
        for(int i=0;i<=W;i++)f[i]=inf;
            f[0] = 0;
        for (int i = 1; i <= n; i++)
            for (int j = w[i]; j <= W; j++)
                f[j] = min(f[j], f[j - w[i]] + v[i]);
            if(f[W]>=inf)printf("This is impossible.\n");
            else printf("The minimum amount of money in the piggy-bank is %d.\n",f[W]);
    }

}

G - 免费馅饼 (稍微难)
基本思路还是好想。我原本是想记录dp【时间】【位置】表示一下最后可得。但是转移写错了。我想记录有掉馅饼为x【】位置,然后就转移只看当前位置有掉馅饼那么就从他左侧右侧转移过来。但是我没想到能否从初始位置转移。比如说在第二个时间最左侧0有饼能从1转移过来,但是由于初始位置在5所以不可能再第二个时间转移过来,所以应该是个无穷的值,而不是dp【1】【1】+1.如果光改初始值思路还是错的,不能够从并考虑而要从位置遍历考虑。而且我这种方法将时间和位置分别记录我还要给时间排个序,总之就是想茶了。 正确思路:记录时间位置为a【t】【x】为1;遍历时间,遍历每个位置转移。具体看代码。(看了题解后,我发现之前的问题仍然没解决。但是过了,只能说我想太多或者数据太水。) 比如说数据为
6
5 1
4 1
6 1
1 2
1 2
2 3
0

照理来说第二秒跑不到1所以最后应该只是1但是答案3.emmm而且其实题目没说每个时间点都是相邻的应该要考虑第一个馅饼掉下为1第二个馅饼掉下时间为3那么就有2秒跑步时间,但是还想也不用考虑。反正好水。

#include <iostream>
#include <string.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn][12],dp[maxn][12];
int main()
{
    int n;
    while(~scanf("%d",&n)&&n){
        int x,t;
        int maxx=0;
        memset(a,0,sizeof(a));
        memset(dp,0, sizeof(dp));
        for(int i=0;i<n;i++){
        scanf("%d%d",&x,&t);
        a[t][x]++;
        maxx=max(t,maxx);}
        dp[1][4]=a[1][4];
        dp[1][5]=a[1][5];
        dp[1][6]=a[1][6];
        for(int i=2;i<=maxx;i++)
            for(int j=0;j<11;j++){
                dp[i][j]=dp[i-1][j];
                if(j>0){
                    dp[i][j]=max(dp[i][j],dp[i-1][j-1]);
                }
                if(j<10){
                    dp[i][j]=max(dp[i][j],dp[i-1][j+1]);
                }
                dp[i][j]+=a[i][j];
            }
        int cou=0;
        for(int i=0;i<11;i++)
            cou=max(cou,dp[maxx][i]);
        cout<<cou<<endl;
    }
}
H - Tickets (简单输出麻烦)
这题比较简单,还挺容易想得,前面如果选择相邻那么后面只能选择单个前面选择单个后面可以选择相邻或者单个。
dp[i][1]=dp[i-1][0]-a[i-1]+h[i-1];
dp[i][0]=min(dp[i-1][0],dp[i-1][1])+a[i];
放下代码(这道题输出好麻烦。后来看下别人的题解发现还是太菜了)
#include <iostream>
#include <string.h>
using namespace std;
int a[2006],h[2006],dp[6006][2];
int main()
{
    int n;scanf("%d",&n);
    while(n--){
        int k;scanf("%d",&k);
        for(int i=0;i<k;i++){
            scanf("%d",&a[i]);
        }
        for(int i=0;i<k-1;i++){
            scanf("%d",&h[i]);
        }
        memset(dp,0, sizeof(dp));
        dp[0][0]=dp[0][1]=a[0];
        for(int i=1;i<k;i++){
            dp[i][1]=dp[i-1][0]-a[i-1]+h[i-1];
            dp[i][0]=min(dp[i-1][0],dp[i-1][1])+a[i];
        }
        int ans=min(dp[k-1][0],dp[k-1][1]);
        int hour=8,minn=0,flag=0;
        if(ans>=3600){hour=8+ans/3600;ans%=3600;}
        if(ans>=60){minn=ans/60;ans%=60;}
        if(hour>12){
            hour%=12;flag=1;
        }
        if(hour>9) printf("%d:",hour);
        else printf("0%d:",hour);
        if(minn>9)printf("%d:",minn);
        else printf("0%d:",minn);
        if(ans>9)printf("%d ",ans);
        else printf("0%d ",ans);
        if(flag==0)printf("am\n");
        else printf("pm\n");
    }
}

别人的输出

   //dp[n]就是ans
   int hh = dp[n]/3600;
        int mm = dp[n]%3600/60;
        int ss = dp[n]%60;
    printf("%02d:%02d:%02d %s\n", (8+hh)%24, mm, ss, (hh+8)%24>12? "pm": "am"); 
K - Jury Compromise poj 1015(难)
这个题真的有点难。跟之前的提很不一样看到没啥思路。 首先一个问题,要使差最小。由于是绝对值,所以不能够直接分开|d1+d2-k1-k2|算|d1-k1|+|d2-k2|。这么想就很萌啊都不能拆开怎么d啊。
看了题解后发现居然有转移这种操作太菜了没想到。原本不是初始值为0当有负值需要处理绝对值,但是现在转移一下。初始值假设为最大差值(20*m)这样负值范围就是0-初始值,正值范围就是初始值-两初始值。 解决了这个问题,就可以分开来算哦,候选人都有一个差一个和。
怎样使m个人差最小?那就设二维数组一维为选多少人二维为差值,那么是不是m个人时二维差最小的就是所得。(二维差最小就是离初始值最近,二维就是初始值不断集差值)那么dp就出来了
dp[i+1][k+gap[i]]=dp[i][k];
每个可达的都为1遍历dp[m][20*m+k]所求k差值,当k最小就是差之最小。
但是题目还要求相同差值和最大。这要怎么解决呢只需要dp值记录和就可以了。有点像01背包,再给你容量为m差值为k(假设k最小)求价值最大。这样就简单。
所以最后的dp方程

最后放一下别人的题解:(之前部分内容来自这个题解)
poj 1015 Jury Compromise(一道很老很经典的二维dp
由于还没有写代码,就不放别人的代码了,可以直接去看这个题解这个题结合代码都很明白简洁。只是没有思路梳理所以自己总结了一下。

L - Common Subsequence
最长公共子序列模板题 不会看这里 最长公共子序列 代码:
#include <string.h>
#include <iostream>
using namespace std;
int main()
{
    char s1[1000000],s2[1000000];
    while(~scanf("%s%s",s1,s2)){
        int len1=strlen(s1),len2=strlen(s2);int dp[1000][1000];
        memset(dp,0, sizeof(dp));
        for(int i=1;i<=len1;i++)
            for(int w=1;w<=len2;w++){
                if(s1[i-1]==s2[w-1]){
                    dp[i][w]=dp[i-1][w-1]+1;
                }
                else dp[i][w]=max(dp[i-1][w],dp[i][w-1]);
            }
        cout<<dp[len1][len2]<<endl;
    }
}

背包九讲

背包
0(nV)
for (int i = 1; i <= n; i++)
    for (int j = V; j >= w[i]; j--)
        f[j] = max(f[j], f[j - w[i]] + v[i]);

完全背包
0(nV) 仔细观察会发现跟01背包只是次序不同为什么呢?当二维转一维可以理解为以当前坐标为中,左边是之前(i-1)数组,右边是更新(i)数组,每个都是由左边来更新的。01背包不断又右到左更新。所以更新i的时候只会由左边的更新当前所以不断只会受前面的影响。而完全,时不仅受原来的影响,也受自身这层(i)的影响(无限取)所以可以认为,完全背包左到右更新更新i会由左边的更新,而左边是已经更新过的故同时受更新前更新后数组。
for (int i = 1; i <= n; i++)
    for (int j = w[i]; j <= V; j++)
        f[j] = max(f[j], f[j - w[i]] + v[i]);

多重背包
O(V∗Σlog(p[i])) 一开始觉得和完全很像,所以直接在完全的加限制,然而发现完全的O(NV)是在无限本身的基础上建立的不能记录加了多少次所以不能在次数上面加限制。
用最简单但是复杂度高的写法是可以的,但是在这里感觉没有记录必要,发现一个有趣的地方。(这里表达两种不同点)最简单的那个算法跟母函数很想。其实想想是对的,只不过母函数记录每个节点记录次数,又通过母函数本身形制(x次方。。)所以得到相应容量方案数,而背包则得到相应容量价值数(记录价值)。
但是母函数只能得到方案数,不能求得最大价值,什么意思呢?已经某个容量,可以判断能否达到某个容量以及达到方案相应得数。但是不可求这个容量的最大价值(原理很简单在母函数概念中不包含价值概念)
通过母函数可以变形得到最简单的写法(当然直接写也可以)但这都不是好的方法,这里二进制优化如下(有一个0n算法但是我还没看懂先不放了)
for (int i = 1; i <= n; i++) {
        int num = min(p[i], V / w[i]);
        for (int k = 1; num > 0; k <<= 1) {
            if (k > num) k = num;
            num -= k;
            for (int j = V; j >= w[i] * k; j--)
                f[j] = max(f[j], f[j - w[i] * k] + v[i] * k);
        }
    }

白书

背包问题
最长公共子序列
摘自 动态规划——最长公共子序列LCS及模板
(代码和文字部分,路径表示来自另一个博主在下面标注了三四部分)

由于原文排版更好看而我好懒所以不完全版原文删减了一些重要内容想看建议直接看原文

一,问题描述
给定两个字符串,求解这两个字符串的最长公共子序列(Longest Common Sequence)。比如字符串1:BDCABA;字符串2:ABCBDAB 则这两个字符串的最长公共子序列长度为4,最长公共子序列是:BCBA
二,算法求解
这是一个动态规划的题目。对于可用动态规划求解的问题,一般有两个特征: 最优子结构;②重叠子问题

①最优子结构

设 X=(x1,x2,.....xn) 和 Y={y1,y2,.....ym} 是两个序列,将 X 和 Y 的最长公共子序列记为LCS(X,Y) 找出LCS(X,Y)就是一个最优化问题。因为,我们需要找到X 和 Y中最长的那个公共子序列。而要找X 和 Y的LCS,首先考虑X的最后一个元素和Y的最后一个元素。
1)如果 xn=ym现在只需要找: LCS(Xn-1,Ym-1) LCS(Xn-1,Ym-1)就是原问题的一个子问题最优的子问题要找的是Xn-1 和 Ym-1 的最长公共子序列啊。。。最长的!!!换句话说,就是最优的那个。(这里的最优就是最长的意思)
2)如果xn != ym两个子问题:LCS(Xn-1,Ym) 和 LCS(Xn,Ym-1) LCS(Xn-1,Ym)表示:最长公共序列可以在(x1,x2,....x(n-1)) 和 (y1,y2,...yn)中找。 LCS(Xn,Ym-1)表示:最长公共序列可以在(x1,x2,....xn) 和 (y1,y2,...y(n-1))中找。 求解上面两个子问题,得到的公共子序列谁最长,那谁就是 LCS(X,Y)。用数学表示就是: LCS=max{LCS(Xn-1,Ym),LCS(Xn,Ym-1)} 由于条件 1) 和 2) 考虑到了所有可能的情况。因此,我们成功地把原问题 转化 成了 三个规模更小的子问题。

②重叠子问题(为什么不用递归而要用dp)


1. 重叠子问题是啥?就是说原问题 转化 成子问题后, 子问题中有相同的问题。咦?我怎么没有发现上面的三个子问题中有相同的啊????
2. OK,来看看,原问题是:LCS(X,Y)。子问题有 ❶LCS(Xn-1,Ym-1) ❷LCS(Xn-1,Ym) ❸LCS(Xn,Ym-1) 初一看,这三个子问题是不重叠的。可本质上它们是重叠的,因为它们只重叠了一大部分。举例: 第二个子问题:LCS(Xn-1,Ym) 就包含了:问题❶LCS(Xn-1,Ym-1),为什么? 因为,当Xn-1 和 Ym 的最后一个元素不相同时,我们又需要将LCS(Xn-1,Ym)进行分解:分解成:LCS(Xn-1,Ym-1) 和 LCS(Xn-2,Ym) 也就是说:在子问题的继续分解中,有些问题是重叠的。
3. 由于像LCS这样的问题,它具有重叠子问题的性质,因此:用递归来求解就太不划算了。因为采用递归,它重复地求解了子问题啊。而且注意哦,所有子问题加起来的个数 可是指数级的哦。。。。 这篇文章中就演示了一个递归求解重叠子问题的示例。 用递归求解,有指数级个子问题,故时间复杂度是指数级。 采用动态规划时,并不需要去一 一 计算那些重叠了的子问题。或者说:用了动态规划之后,有些子问题 是通过 “查表“ 直接得到的,而不是重新又计算一遍得到的。
三,转移方程

在这里插入图片描述
c[i,j]表示:(x1,x2…xi) 和 (y1,y2…yj) 的最长公共子序列的长度。(是长度哦,就是一个整数嘛)。公式的具体解释可参考《算法导论》动态规划章节
路径
摘自:以s1={1,3,4,5,6,7,7,8},s2={3,5,7,4,8,6,7,8,2}为例。
c[8][9] = 5,且S1[8] != S2[9],所以倒推回去,c[8][9]的值来源于c[8][8]的值(因为c[8][8] > c[7][9])。
c[8][8] = 5, 且S1[8] = S2[8], 所以倒推回去,c[8][8]的值来源于 c[7][7]。
以此类推,如果遇到S1[i] != S2[j] ,且c[i-1][j] = c[i][j-1] 这种存在分支的情况,这里请都选择一个方向(之后遇到这样的情况,也选择相同的方向)。
第一种结果为:
在这里插入图片描述
这就是倒推回去的路径,棕色方格为相等元素,即LCS = {3,4,6,7,8},这是其中一个结果。

另一个结果
在这里插入图片描述
即LCS ={3,5,7,7,8}。

四,算法模板(转移路径
#include<stdio.h>
#include<string.h>
#include<stack>
#include<algorithm>
using namespace std;
#define N 1010
int dp[N][N];
char c;
int main()
{
    char a[N];
    char b[N];
    scanf("%s%s",a,b);
    int la=strlen(a);
    int lb=strlen(b);
    memset(dp,0,sizeof(dp));
    for(int i=1; i<=la; i++)
    {
        for(int j=1; j<=lb; j++)
        {
            if(a[i-1]==b[j-1])
                dp[i][j]=dp[i-1][j-1]+1;
            else
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        }
    }
    int i=la,j=lb;
    stack<char>s;
    while(dp[i][j])
    {
        if(dp[i][j]==dp[i-1][j])///来自于左方向
        {
            i--;
        }
        else if(dp[i][j]==dp[i][j-1])///来自于上方向
        {
            j--;
        }
        else if(dp[i][j]>dp[i-1][j-1])///来自于左上方向
        {
            i--;
            j--;
            s.push(a[i]);
        }
    }
    while(!s.empty())
    {
        c=s.top();
        printf("%c",c);
        s.pop();
    }
    return 0;
}

题目:L - Common Subsequence https://vjudge.net/contest/318174#problem/L

多重部分和
最长上升子序列
n2写法
//(dp[i]=max(1,dp[j]+1)当j<i&&aj<ai 表示以ai结尾的最长距离)
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=1007;
const int inf=0x3f3f3f3f;
int main()
{
    int dp[maxn],a[maxn];
    int n;scanf("%d",&n);
    for(int i=0;i<n;i++){
        dp[i]=1;
        scanf("%d",&a[i]);
    }
    int ans=0;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<i;j++) {
            if (a[i] > a[j])dp[i] = max(dp[i], dp[j] + 1);
        }
        ans=max(dp[i],ans);
    }
    printf("%d",ans);
}

nlogn写法

//(dp[i]=min(a[j],dp[i])当a[j]>dp[i-1]||i=0表示长度为末尾最小值)
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=1007;
const int inf=0x3f3f3f3f;
int main()
{
    int dp[maxn];
    int n;scanf("%d",&n);
    fill(dp,dp+n,inf);
    int a;
    for(int k=0;k<n;k++){
        scanf("%d",&a);
        *lower_bound(dp,dp+n,a)=a;
    }
    printf("%d",lower_bound(dp,dp+n,inf)-dp);
}

裸题:poj1631

#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=40006;
const int inf=0x3f3f3f3f;
int main()
{
    int dp[maxn];
    int c;scanf("%d",&c);
    while(c--) {
        int n;
        scanf("%d", &n);
        fill(dp, dp + n, inf);
        int a;
        for (int k = 0; k < n; k++) {
            scanf("%d", &a);
            *lower_bound(dp, dp + n, a) = a;
        }
        printf("%d\n", lower_bound(dp, dp + n, inf) - dp);
    }
}

两个排序(也挺简单的):POJ - 1609

#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=10006;
const int inf=0x3f3f3f3f;
struct node{
    int x,y;
};
bool cmp(node a,node c){
    if(a.x==c.x)return a.y<c.y;
    return a.x<c.x;
}
int main()
{
    int dp[maxn];node a[maxn];
    int n;
    while(~scanf("%d",&n)&&n) {
        for (int i = 0; i < n; i++)
            scanf("%d%d", &a[i].x, &a[i].y);
        sort(a, a + n, cmp);
        int ans = 0;
        for (int i = 0; i < n; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (a[j].y <= a[i].y) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            ans = max(ans, dp[i]);
        }
        printf("%d\n", ans);
    }
    printf("*\n");
}

记录路径。参考 https://blog.csdn.net/lxt_Lucia/article/details/81206439

#include <algorithm>
#include <stdio.h>
#include <stack>
using namespace std;
const int maxn=10006;
const int inf=0x3f3f3f3f;
struct node{
    int x,y,zyx;
};
bool cmp(node a,node c){
    if(a.x==c.x)return a.y>c.y;
    return a.x<c.x;
}
int main()
{
    int vec[maxn],dp[maxn];node a[maxn];
    int n=0;
    while(~scanf("%d%d", &a[n].x, &a[n].y)){a[n].zyx=n+1;vec[n]=-1;n++;}
    sort(a, a + n, cmp);
        int ans = 0,f=-1;
        for (int i = 0; i < n; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (a[j].x < a[i].x&&a[j].y > a[i].y&&dp[i]<dp[j]+1) {
                    dp[i] =dp[j] + 1;
                    vec[i]=j;
                }
            }
            if(ans<dp[i]){
                f=i;
                ans=dp[i];
            }
        }
        printf("%d\n", ans);
        stack<int> haofan;
        while(vec[f]!=-1){
            haofan.push(a[f].zyx);
            f=vec[f];
        }
    haofan.push(a[f].zyx);
        while(!haofan.empty()){
            printf("%d\n", haofan.top());
            haofan.pop();
        }
}
//bie ren de
#include<math.h>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#include<queue>
#define INF 0x3f3f3f3f
 
using namespace std;
 
struct A{
    int w;
    int s;
    int xb;
}a[1010];
 
struct U{
    int num;
    int xb;
}dp[1010];
 
bool cmp(struct A a,struct A b){
    if(a.w==b.w)
        return a.s>b.s;
    else return a.w<b.w;
}
 
int main()
{
    int b[1010],i,j,k,n=0,max1=0;
    while(scanf("%d%d",&a[n].w,&a[n].s)!=EOF){
        ++n;
        a[n-1].xb=n;
    }
    sort(a,a+n,cmp);
    for(i=0;i<=n-1;i++){
        dp[i].num=1;
        dp[i].xb=0;
    }
    for(i=1;i<=n-1;i++){
        for(j=0;j<=i-1;j++){
            if(a[i].w>a[j].w&&a[i].s<a[j].s&&dp[i].num<dp[j].num+1){
                dp[i].num=dp[j].num+1;
                dp[i].xb=j;
            }
        }
        if(dp[i].num>max1)
        {
            max1=dp[i].num;
            k=i;
        }
    }
    for(i=1;i<=max1;i++)
    {
        b[i]=k;
        k=dp[k].xb;
    }
    printf("%d\n",max1);
    for(i=max1;i>=1;i--)
        printf("%d\n",a[b[i]].xb);
    return 0;
}

最长不上升子序列(超常见的导弹拦截)POJ - 1887

#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=10006;
const int inf=0x3f3f3f3f;
int main()
{
    int dp[maxn];int a[maxn];
    int k;int c=0;
    while(~scanf("%d", &k)&&k!=-1) {
        int cou = 0;
        a[cou++] = k;
        while (~scanf("%d", &a[cou])) {
            if (a[cou] == -1) { break; }
            cou++;
        }
        int ans = 0;
        for (int i = 0; i < cou; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (a[j] > a[i]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            ans = max(ans, dp[i]);
        }
        printf("Test #%d:\n"
               "  maximum possible interceptions: %d\n\n",++c, ans);
    }
}
树状数组维护LIS
BZOJ3173 题解:https://www.cnblogs.com/Paul-Guderian/p/7237669.html 实在看不下去。。。先欠着
#include<stdio.h>
#include<cstring>
#include<algorithm>
#define go(i,a,b) for(int i=a;i<=b;i++)
#define ro(i,a,b) for(int i=a;i>=b;i--)
using namespace std;const int N=100003;
int a[N],c[N],f[N],n,l,r,M,t;
inline void Add(int x,int d){while(x<=n)c[x]+=d,x+=x&-x;}
inline void Up(int x,int d){while(x<=n)c[x]=max(c[x],d),x+=x&-x;}
inline int Sum(int x){int res=0;while(x)res+=c[x],x-=x&-x;return res;}
inline int Max(int x){int M=0;while(x)M=max(M,c[x]),x-=x&-x;return M;}
inline void Nick(int x){if(x<0)putchar('-'),x=-x;if(x>9)Nick(x/10);putchar(x%10+'0');}
inline int Judy(int &x)
{
    x=0;int w=1;char c=0;
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();x*=w;return 1;
}
int main()
{
    Judy(n);go(i,1,n)Judy(a[i]),Add(i,1);
    ro(i,n,1) {
        l = 1, r = n;
        while (l < r)M = l + r >> 1, Sum(M) < a[i] + 1 ? l = M + 1 : r = M;
        Add(a[i] = r, -1);
    }
    go(i,1,n) {
        Up(a[i], f[i] = Max(a[i]) + 1);
        f[i] = max(f[i], f[i - 1]);
        Nick(f[i]), puts("");
    }
    return 0;
}//Paul_Guderian
有关计数问题的dp
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值