dp专场的蒟蒻题解

前言:一直想练练dp,正好衣神弄了个训练赛。。上次看cgold大佬的题解心血来潮所以自己试着写了第一次题解。。可惜本蒟蒻的能力太差有两道题做不太出,只好搬运学习其它大佬的题解了。。

a题

https://vjudge.net/contest/355951#problem/A

这题做题的过程十分痛苦

我又双叒叕看错题意了。。

以为是必须在对角线上

其实是随便n*n的都行。。

大概思路是从一个角开始更新,统计左边和上边相同的长度

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define Mod 1000000007
#define eps 1e-6
#define ll long long
#define INF 0x3f3f3f3f 
#define MEM(x,y) memset(x,y,sizeof(x))
#define Maxn 100005
#define P pair<int,int>
using namespace std;
char a[1005][1005];
int dp[1005][1005];
int n;


int main()
{
    int n, ans;
    while (cin>>n , n)
    {
        ans = 1;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
            {
                cin >> a[j][i];
            }
        for (int i = 0; i < n; i++)
        {
            for (int j = n - 1; j >= 0; j--)
            {
                dp[i][j] = 1;
                if (i == 0 || j == n - 1) continue;
                int q = dp[i - 1][j + 1];
                for (int k = 1; k <= q; k++)
                {
                    if (a[i - k][j] == a[i][j + k]) dp[i][j]++;
                    else break;
                }
                ans = max(ans, dp[i][j]);
            }
        }
        printf("%d
", ans);
    }
    return 0;
}

 b题

https://vjudge.net/contest/355951#problem/B

这题有点难。。

蒟蒻直接下线。。。

那就学下网上大佬的方法吧

dp(j, k)表示,取j 个候选人,使其辩控差为k 的所有方案中,辩控和最大的那个方案(该方案称为“方案dp(j, k)”)的辩控和

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>
#include <string>
#include <map>
using namespace std;

#define N 220
#define met(a, b) memset(a, b, sizeof(a))
#define INF 0xffffff
const long long  Max = 2000000000;
typedef long long LL;

int D[N], P[N];
int dp[30][1000];  ///dp[i][k]代表选 i 个人, 辩控差为 k 的辩控和最大
int pre[30][1000]; ///pre[i][k] 存的是它是上次选的人
int Answer[N];     ///记录选中的这 m 个人的编号

int main()
{
    int n, m, iCase=1;

    while(scanf("%d%d", &n, &m), n||m)
    {
        int i, j, k, Min, t1, t2;

        met(D, 0);
        met(P, 0);
        met(dp, -1);
        met(pre, 0);
        met(Answer, 0);

        for(i=1; i<=n; i++)
            scanf("%d%d", &D[i], &P[i]);

        Min = m*20; ///它的辩控差最大为 m*20  
        dp[0][Min] = 0;  ///起始状态要先置为 0


        /// k 的最大取值范围是[-Min, Min], 但是数组不能表示负数, 因此将数组向右平移 Min,得到[0, 2*Min]
        for(i=0; i<=m; i++)
        {
            for(k=0; k<=Min*2; k++)
            {
                if(dp[i][k]==-1) continue;  ///如果存在,接着找 dp[i][k] 的下一个状态

                for(j=1; j<=n; j++)
                {
                        if(dp[i+1][k+D[j]-P[j]] < dp[i][k]+D[j]+P[j])
                        {
                            t1=i, t2=k;

                            while(t1>0 && pre[t1][t2]!=j)  
                            {
                                t2 -= D[pre[t1][t2]] - P[pre[t1][t2]];
                                t1 --;
                            }
                            if(t1==0)   ///当 t1 为 0 时,编号为 j 这个人在之前没有被选中过
                            {
                                dp[i+1][k+D[j]-P[j]] = dp[i][k] + D[j] + P[j];
                                pre[i+1][k+D[j]-P[j]] = j;
                            }
                        }

                }
            }
        }

        int ff = Min;
        int num = 0, sum1=0, sum2=0;

        ///要选辩控差最小的,所求的 num 便是选 m 个人辩控差最小的 
        while(dp[m][ff-num]==-1 && dp[m][ff+num]==-1) num++;

        if(dp[m][ff-num]>dp[m][ff+num]) t2 = ff-num;
        else    t2 = ff+num;

        t1 = m;

        for(i=1; i<=m; i++)
        {
            Answer[i] = pre[t1][t2];

            sum1 += D[Answer[i]];
            sum2 += P[Answer[i]];

            t1--;
            t2 -= D[Answer[i]] - P[Answer[i]];
        }

        printf("Jury #%d
", iCase++);
        printf("Best jury has value %d for prosecution and value %d for defence:
", sum1, sum2);


        sort(Answer+1, Answer+1+m);

        for(i=1; i<=m; i++)
            printf(" %d", Answer[i]);

        printf("

");
    }
    return 0;
}

  c题

  https://vjudge.net/contest/355951#problem/C

总算有道水题,求子序列,前两天刚做过

大体优化的思路就是用upper踢掉原来的数

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define Mod 1000000007
#define eps 1e-6
#define ll long long
#define INF 0x3f3f3f3f 
#define MEM(x,y) memset(x,y,sizeof(x))
#define Maxn 100005
#define P pair<int,int>
using namespace std;
struct node {
    int l, w;
}a[5005];
int dp[5005];
bool cmp(node a, node b)
{
    if (a.w == b.w)
        return a.l < b.l;
    else
        return a.w < b.w;
}
int main()
{
    int n, t;
    scanf("%d", &t);
    while (t--)
    {
        cin >> n;
        int i;
        for (i = 0; i < n; i++)
            scanf("%d%d", &a[i].l, &a[i].w);
        MEM(dp, INF);
        sort(a, a + n, cmp);
        for (i = n - 1; i >= 0; i--)
        dp[    lower_bound(dp, dp + n, a[i].l)-dp] = a[i].l;
        printf("%d
", lower_bound(dp, dp + n, INF) - dp);
    }
}

d题

https://vjudge.net/contest/355951#problem/D

还是子序列,事实告诉我们不能被英语打败

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define Mod 1000000007
#define eps 1e-6
#define ll long long
#define INF 0x3f3f3f3f 
#define MEM(x,y) memset(x,y,sizeof(x))
#define Maxn 100005
#define P pair<int,int>
using namespace std;
int n;
int a[500005];
int s[500005];
int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        for(int i=1; i<=n; i++)
            scanf("%d", &a[i]);
        s[0]=0;
        int ans=0;
        for(int i=1; i<=n; i++)
        {
            if(a[i]>s[ans])
            {
                s[++ans]=a[i];
                continue;
            }
            int q=upper_bound(s, s+ans, a[i])-s;
            s[q]=a[i];
        }
        printf("%d
", ans);
    }
    return 0;
}

e题

https://vjudge.net/contest/355951#problem/E

2个小时的找bug时间竟然是因为一个大括号。。。

这题的思路是开个二维的dp数组

一边记录从左侧下的最优方案,另一个记录右侧的

然后从底层往上慢慢推就行

重要的是经验:对于这种只有两种选择的题可以尝试分别记录

然后注意细节!!!

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define Mod 1000000007
#define eps 1e-6
#define ll long long
#define INF 0x3f3f3f3f 
#define MEM(x,y) memset(x,y,sizeof(x))
#define Maxn 100005
#define P pair<int,int>
using namespace std;
struct node
{
    int x;
    int y;
    int high;
     bool operator < (const node &other)const{
        return high < other.high;
    }
};
node p[1005];
int dp[1005][2];
int x, y, n, m;
int qw;
void left(int i)
{
    int k = i - 1;
    while (k > 0 && p[i].high - p[k].high <= m)
    {
        if (p[i].x >= p[k].x && p[i].x <= p[k].y) {
            dp[i][0] = p[i].high - p[k].high + min(p[i].x - p[k].x + dp[k][0], p[k].y - p[i].x + dp[k][1]);
            return;
        }
        else
            k--;
    }
    if (p[i].high - p[k].high > m)
        dp[i][0] = INF;
    else
        dp[i][0] = p[i].high;
}
void right(int i)
{
    int k = i - 1;
    while (k > 0 && p[i].high - p[k].high <= m)
    {
        if (p[i].y >= p[k].x && p[i].y <= p[k].y) {
            dp[i][1] = p[i].high - p[k].high + min(p[i].y - p[k].x + dp[k][0], p[k].y - p[i].y + dp[k][1]);
            return;
        }
        else
            k--;
    }
    if (p[i].high - p[k].high > m)
        dp[i][1] = INF;
    else
        dp[i][1] = p[i].high;
}
int main()
{
    int t;
    cin >>t;
    while (t--)
    {
        cin >> n >> x >> y >> m;
        for (int i = 1; i <= n; i++)
            cin >> p[i].x >> p[i].y >> p[i].high;
    
    p[0].high = 0;
        p[0].x = -20000;
        p[0].y = 20000;
        p[n + 1].high = y;
        p[n + 1].x = p[n + 1].y = x;
    
        sort(p, p + n + 2);
        for (int i = 1; i <= n + 1; i++) {
            left(i);
            right(i);
        }
        int ans = min(dp[n + 1][0], dp[n + 1][1]);
        printf("%d
", ans);
    }
}

f题

https://vjudge.net/contest/355951#problem/F

一眼01背包

但是好像又不太一样

这道题存在负数还是两个参数怎么办

负数------坐标横移

两个参数-----把其中的一个作为dp数组的下标

一发过了是我没想到的。。这不是我的风格

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define Mod 1000000007
#define eps 1e-6
#define ll long long
#define INF 0x3f3f3f3f 
#define MEM(x,y) memset(x,y,sizeof(x))
#define Maxn 100005
#define P pair<int,int>
using namespace std;
int s[1005];
int f[1005];
int dp[200050];
int main()
{
    int n;
    while (cin >> n)
    {
        MEM(dp, -INF);
        for (int i = 0; i < n; i++)
        {
            cin >> s[i] >> f[i];
            
        }
        dp[100000] = 0;
        for (int i = 0; i < n; i++)
        {
            if (s[i] < 0 && f[i] < 0)
                continue;
            if (s[i] > 0)
            {
                for (int j = 200000; j > s[i]; j--)
                {
                    if (dp[j - s[i]] > -INF)
                        dp[j] = max(dp[j], dp[j - s[i]] + f[i]);
                }
            }
            else
            {
                for (int j = s[i]; j < 200000 + s[i]; j++)
                    if (dp[j - s[i]] > -INF)
                        dp[j] = max(dp[j], dp[j - s[i]] + f[i]);
            }
        }
        int ans = -INF;
        for (int i = 100000; i <= 200000; i++)
        {
            if (dp[i] >= 0)
                ans = max(ans, dp[i] + i - 100000);
        }
        printf("%d
", ans);

        
    }
}

G题

https://vjudge.net/contest/355951#problem/G

挺简单的题但是还是卡了半天

问题在于我一开始想把dp[i]定义为用前i个牛能达到的最高高度,但是没写出来

然后我定义dp[i]类似为一个bool数组记录该高度是否能达到

希望有大佬看到这里能私聊我讨论讨论第一种思路的可行性

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define Mod 1000000007
#define eps 1e-6
#define ll long long
#define INF 0x3f3f3f3f 
#define MEM(x,y) memset(x,y,sizeof(x))
#define Maxn 100005
#define P pair<int,int>
using namespace std;
struct node
{
    int h, a, c;
}p[405];
bool cmp(node x, node y)
{
    return x.a < y.a;
}
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> p[i].h >> p[i].a >> p[i].c;
    }
    sort(p, p + n, cmp);
    int dp[1000000];
    int sum[100000];
    MEM(dp, 0);
    dp[0] = 1;
    int ans = 0;
    for (int i = 0; i < n; i++) {
        MEM(sum, 0);
        for (int j = p[i].h; j <= p[i].a; j++)
        {
            if (!dp[j] && dp[j - p[i].h] && sum[j-p[i].h] < p[i].c)
            {
                sum[j] = sum[j - p[i].h] + 1;
                dp[j] = 1;
                ans = max(j, ans);
            }
        }
        
    }
    cout << ans;
}

H题

https://vjudge.net/contest/355951#problem/H

数学题!

我数学好差。。公式好难推

推出来也超时。。

不过这题除了推公式没啥别的了。

不会写数学符号直接上代码了

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef  long long LL;
const int inf = 1<<30;
const LL maxn = 1e5+10;
const int mod = 1e6;

int N, M, l, r, a[1010];
int dp[1010][maxn]; 
int main()
{
    int input;
    cin >> N >> M >> l >> r;
    for(int i = 1; i <= M; ++i){
        cin >> input;
        ++a[input];
    }

  
    for(int i = 0; i <= N; ++i)
        dp[i][0] = 1;

    for(int i = 1; i <= N; ++i){
        for(int j = 1; j <= M; ++j){
            if(j-a[i]-1 >= 0) 
                dp[i][j] = (dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1-a[i]]+mod)%mod;
            else              
                dp[i][j] = (dp[i][j-1]+dp[i-1][j]+mod)%mod;
        }
    }
    int ans = 0;
    for(int i = l; i <= r; ++i)
        ans = (ans+dp[N][i]+mod)%mod;
    cout << ans << endl;

    return 0;
}

I题

https://vjudge.net/contest/355951#problem/I

水题没啥说的

啥?你问我为啥水题还re?

----N,M不分

----啊,那没事了

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define Mod 1000000007
#define eps 1e-6
#define ll long long
#define INF 0x3f3f3f3f 
#define MEM(x,y) memset(x,y,sizeof(x))
#define Maxn 100005
#define P pair<int,int>
using namespace std;
struct node
{
    int s;
    int e;
    int v;
}q[1005];
bool cmp(node x, node y)
{
    return x.e < y.e;
}
int main()
{
    int n, m, r;
    int d;
    while(cin >> n >> m >> r)
    {
    
    for (int i = 0; i < m; i++)
    {
        cin >> q[i].s >> q[i].e >> q[i].v;
    }
    ll dp[10005];
    sort(q, q + m, cmp);
    for (int j = 0; j <m; j++) {

        dp[j] = q[j].v;
        for (int i = 0; i < j; i++)
        {
            if (q[j].s >= q[i].e + r)
            {
                dp[j] = max(dp[j], dp[i] + q[j].v);
            }
        }
    }
    ll max = 0;
    for (int i = 0; i < m; i++)
    {
        if (dp[i] > max)
            max = dp[i];
    }
    cout << max<<endl;
}
}

J题

https://vjudge.net/contest/355951#problem/J

自己写的代码三重循环tle了

看一下数据不tle才怪。。

学下网上大佬的思路吧

f[i][j]表示到第i段路满足不下降(只做不下降,不上升待会再做一遍),且当前高度为j所需要的最小花费。它的转移实际上是mink[1,j]f[i1][k]+|h[i]ori[j]|mink∈[1,j]f[i−1][k]+|h[i]−ori[j]|,ori[j]是j对应的离散化之前的数,h[i]是i原来的高度,因为不下降,所以只能从[1,j][1,j]转移。不过这样的复杂度是O(n3)O(n3)的,我们发现,随着jj的增大,kk的取值范围只是上界变大了,因此可以通过更新前缀和来维护minn[i]=minj=[1,i]{f[j]}minn[i]=minj=[1,i]{f[j]}。

真高级。。

来人,上代码!

#include<cstdio>
#include<cstring>
#include<algorithm>
long long Abs(long long x)
{
    return x>0?x:(-x);
}
long long Min(long long x,long long y)
{
    return x<y?x:y;
}
struct node
{
    long long a,b,i;
    friend bool operator <(node x,node y)
    {
        return x.i<y.i;
    }
}a[2001];
bool cmp(node x,node y)
{
    return x.a<y.a;
}
long long ori[2001];
long long f[2001];//f[i]代表f[i]到f[n]的最大值
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a[i].a);
        a[i].i=i;
    }
 
    std::sort(a+1,a+n+1,cmp);
    int cnt=0;
    a[0].a=-1;
    for(int i=1;i<=n;++i)
    {
        if(a[i].a!=a[i-1].a)
        {
            ++cnt;
            ori[cnt]=a[i].a;
        }
        a[i].b=cnt;
    }
    std::sort(a+1,a+1+n);
 
 
    long long ans=1000000000000000ll;
 
    memset(f,0,sizeof(f));
    f[cnt+1]=10000000000000ll;
    for(int i=n;i;--i)
    {
        for(int j=1;j<=cnt;++j)
            f[j]+=Abs(a[i].a-ori[j]);
        for(int j=cnt;j;--j)
            f[j]=Min(f[j],f[j+1]);
    }
    for(int i=1;i<=cnt;++i)
        ans=ans<f[i]?ans:f[i];
    printf("%lld
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/cyq123/p/12296640.html