【xdoj新手教学】WEEKLY MISSION 5

本来准备了很多poj的专题想做,结果没想到poj最近坏了。。。没办法,就先把这周的题给做了,这周的的确不难,都是比较基本的dp,做着做着就完了。我估计懂的人应该不会有什么难度,不懂的可能会比较费劲,所以就先把这周的题解写了吧。

我还是建议看书系统地学习,如果只是从题目里学习某种思想的话,一定要全面地搜索网上的资料,如果只是懂个模板,那你基本和不懂没区别。

什么是dp,一种递推的思想,更加高效的搜索,也可以认为是一种由方向性的记忆化搜索,反正思维很重要就是了,找到递推关系,那么一切问题就迎刃而解了。

初学dp并不是很容易理解,要花一些时间。

A
很基本的dp,因为到每一个点只能是从上面两个(有时候是一个)走过来,所以一层一层找,每次看看哪边的的值更大,就选择哪边的值,加上当前点的权值即可。

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

using namespace std;

const int MAX_N = 100;

int A[MAX_N][MAX_N];
int dp[MAX_N][MAX_N];
int N, T;

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

    while(T--)
    {
        scanf("%d", &N);

        int i, j;
        for(i = 0 ; i < N ; i++)
        {
            for(j = 0 ; j <= i ; j++)
            {
                scanf("%d", &A[i][j]);
            }
        }

        memset(dp , 0 , sizeof(dp));
        dp[0][0] = A[0][0];
        for(i = 1 ; i < N ; i++)
        {
            for(j = 0 ; j <= i ; j++)
            {
                if(j)
                    dp[i][j] = max(dp[i][j] , A[i][j] + dp[i - 1][j - 1]);

                if(j < i)
                    dp[i][j] = max(dp[i][j] , A[i][j] + dp[i - 1][j]);
            }
        }

        int ans = 0;
        for(i = 0 ; i < N ; i++)
            ans = max(ans , dp[N - 1][i]);

        printf("%d\n", ans);
    }

    return 0;
}

B
背包模板题,结果因为忘了细节wa了几次,一个很经典的问题,有很多很炫技的做法,我用的这个是比较朴素的。
考虑前i种情况重量不超过j的情况,那么只有两种情况可以到达这个状态,只拿了前i-1种,或者拿了i种,这样他的最大价值就只能从这两种情况下查找了。

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

using namespace std;

const int MAX_N = 2000;

int dp[MAX_N + 1][MAX_N + 1];
int T, N, V;

int Va[MAX_N], W[MAX_N];

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

    while(T--)
    {
        scanf("%d %d", &N, &V);

        int i, j;
        for(i = 0 ; i < N ; i++)
            scanf("%d", &Va[i]);

        for(i = 0 ; i < N ; i++)
            scanf("%d", &W[i]);

        memset(dp , 0 , sizeof(dp));
        for(i = 1 ; i <= N ; i++)
        {
            for(j = 0 ; j < W[i - 1] ; j++)
                dp[i][j] = dp[i - 1][j];

            for(j = W[i - 1] ; j <= V ; j++)
            {
                dp[i][j] = max(dp[i - 1][j] , dp[i - 1][j - W[i - 1]] + Va[i - 1]);
            }
        }

        printf("%d\n", dp[N][V]);
    }

    return 0;
}

C
读题稍稍有点复杂,说的是找到一条能量最小的从上往下的路,每次向下只能最多向左右移动一格,也是一个dp,因为到达ij时的最优解只看是从上面三个点出发的,所以找一找就是了,另外因为n最大只有100,所以直接每个点存路径是没有问题的,这样时间空间复杂度都是n立方(当然用滚动数组会节约内存,不过没必要)。

# include <cstdio>
# include <cstring>
# include <vector>
# include <algorithm>

using namespace std;

const int MAX_N = 100;

int A[MAX_N][MAX_N];
int d[MAX_N][MAX_N];

vector<int> p[MAX_N][MAX_N];

int T, N, M;

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

    int tt = 1;
    while(T--)
    {
        scanf("%d %d", &N, &M);

        int i, j;
        for(i = 0 ; i < N ; i++)
        {
            for(j = 0 ; j < M ; j++)
            {
                scanf("%d", &A[i][j]);
                p[i][j].clear();
            }
        }

        memset(d , 0x3f , sizeof(d));

        for(i = 0 ; i < M ; i++)
        {
            d[0][i] = A[0][i];
            p[0][i].push_back(i);
        }

        for(i = 1 ; i < N ; i++)
        {
            for(j = 0 ; j < M ; j++)
            {
                if(j && d[i][j] >= d[i - 1][j - 1] + A[i][j])
                {
                    d[i][j] = d[i - 1][j - 1] + A[i][j];
                    p[i][j] = p[i - 1][j - 1];
                }

                if(d[i][j] >= d[i - 1][j] + A[i][j])
                {
                    d[i][j] = d[i - 1][j] + A[i][j];
                    p[i][j] = p[i - 1][j];
                }

                if(j < M - 1 && d[i][j] >= d[i - 1][j + 1] + A[i][j])
                {
                    d[i][j] = d[i - 1][j + 1] + A[i][j];
                    p[i][j] = p[i - 1][j + 1];
                }

                p[i][j].push_back(j);
            }
        }

        int ans = 1e8;
        int nn;
        for(i = 0 ; i < M ; i++)
        {
            if(ans >= d[N - 1][i])
            {
                ans = d[N - 1][i];
                nn = i;
            }
        }

        printf("Case %d\n", tt++);

        int pp = p[N - 1][nn].size();
        for(i = 0 ; i < pp ; i++)
            printf("%d%s", p[N - 1][nn][i] + 1 , (i == pp - 1) ? "\n" : " ");
    }

    return 0;
}

D
和刚才那道题差不多,每次考察前一个时间的三个可以达到这里的状态的最大值即可。另外这道题t好像超限了,有点坑。

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

using namespace std;

const int MAX_N = 11;
const int MAX_T = 1e5 + 100;

int box[MAX_T][MAX_N];
int N;

int dp[MAX_T][MAX_N];

int main()
{
    while(1)
    {
        scanf("%d", &N);
        if(!N)
            break;

        int i, j;

        memset(box , 0 , sizeof(box));
        memset(dp , 0 , sizeof(dp));

        int x, t, mt = 0;
        for(i = 0 ; i < N ; i++)
        {
            scanf("%d %d", &x, &t);
            box[t][x]++;
            mt = max(mt , t);
        }


        dp[1][4] = box[1][4];
        dp[1][5] = box[1][5];
        dp[1][6] = box[1][6];

        for(i = 2 ; i <= mt ; i++)
        {
            for(j = 0 ; j <= 10 ; j++)
            {
                dp[i][j] = dp[i - 1][j];

                if(j < 10)
                    dp[i][j] = max(dp[i][j] , dp[i - 1][j + 1]);

                if(j)
                    dp[i][j] = max(dp[i][j] , dp[i - 1][j - 1]);

                dp[i][j] += box[i][j];
            }
        }

        int ans = 0;
        for(i = 0 ; i <= 10 ; i++)
            ans = max(ans , dp[mt][i]);

        printf("%d\n", ans);
    }


    return 0;
}

E
模板题,详情参考挑战程序设计初级篇dp最长公共子串即可,我直接套的当时的代码。
看挑战如果不把所有出现的代码都敲一遍,不把所有能交的例题都交一遍,不把每道题的思想都理解,等于没看,所以看的时候如果达不到这个标准,建议重看。

虽然进行了一些修改,但是这个代码依然充满了我过去落后的风格

# include <stdio.h>
# include <string.h>

const int MAX_N = 1001;

int n, m;
char t[MAX_N], s[MAX_N];
int dp[MAX_N][MAX_N];

int max(int a , int b)
{
    if(a > b)
        return a;
    return b;
}
void solve()
{
    memset(dp , 0 , sizeof(dp));
    for(int i = 0 ; i < n ; i++)
    {
        for(int j = 0 ; j < m ; j++)
        {
            if(s[i] == t[j])
            {
                dp[i + 1][j + 1] = dp[i][j] + 1;
            }
            else
            {
                dp[i + 1][j + 1] = max(dp[i][j + 1] , dp[i +1][j]);
            }
        }
    }
    printf("%d\n",dp[n][m]);
}
int main()
{
    while(~scanf("%s %s", t, s))
    {
        n = strlen(s);
        m = strlen(t);
        solve();
    } 

    return 0;
}

F
其实和亦或没什么关系,因为f的关系本来就是一个递推的关系,所以先用dp把每一种情况下的f值求出来,然后再用相似的方法求范围内的最大值。

另外说一下这道题的空间,我这个数组开的大约是5千万,通常是会mle的,但是这道题给的内存范围是正常题目的4倍左右,所以是够的。

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

using namespace std;

const int MAX_N = 5e3;

int A[MAX_N][MAX_N];
int dp[MAX_N][MAX_N];
int N, Q;

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

    int i, j;
    for(i = 0 ; i < N ; i++)
        scanf("%d", &A[i][i]);


    for(i = 1 ; i < N ; i++)
    {
        for(j = 0 ; j + i < N ; j++)
        {
            A[j][j + i] = A[j][j + i - 1] ^ A[j + 1][j + i];
        }
    }

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

    for(i = 1 ; i < N ; i++)
    {
        for(j = 0 ; j + i < N ; j++)
        {
            dp[j][j + i] = max(A[j][j + i] , max(dp[j + 1][j + i] , dp[j][j + i - 1]));
        }
    }

    scanf("%d", &Q);

    int t, s;
    for(i = 0 ; i < Q ; i++)
    {
        scanf("%d %d", &t, &s);
        printf("%d\n", dp[t - 1][s - 1]);
    }

    return 0;
}

G
当时没看到连续,所以被坑了,花了很长时间思考如何把n方的算法优化成nlogn的,不过我还真有一些想法,依靠某些数据结构是可以成功的,不过可能还需要研究一下。

由于发现这道题这么水之后实在懒得写了,所以这个是某个人写的。

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

using namespace std;

const int maxn = 200000;
int a[maxn+5];//1串的桶,表示以i为结尾的最长LCIS的长度
int b[maxn+5];//2串的桶

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, m,t;
        scanf("%d%d", &n, &m);

        memset(a, 0, sizeof(a));
        memset(b, 0, sizeof(b));
        for (int i = 0; i < n; i++)
        {
            scanf("%d", &t);
             a[t] = a[t-1] + 1;
        }
        for (int i = 0; i < m; i++)
        {
            scanf("%d", &t);
            b[t] = b[t-1] + 1;
        }

        int ans = -1;
        int mmin;
        for (int i = 0; i <maxn; i++) {
            mmin=min(a[i], b[i]);
            ans = max(ans, mmin);
        }
        printf("%d\n", ans);

    }
    return 0;
}

H
记得之前在一个比赛里看到过相似的题目,只不过那道题是两个人分别走一个对角线,难度肯定是大多了,这道题只要基本的dp即可,把这道题视为是走两条路线时最小的值,所以每次枚举一下,复杂度是n方,总共n复杂度的路程,所以最后n立
这里写代码片方来查找,完全够用。

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

using namespace std;

const int MAX_N = 10;

int dp[MAX_N * MAX_N][MAX_N * MAX_N];
int maz[MAX_N][MAX_N];          

int dx1[4] = {-1 , 0 , -1 , 0}, dy1[4] = {0 , -1 , 0 , -1}, dx2[4] = {-1 , -1 , 0 , 0}, dy2[4] = {0 , 0 , -1 , -1};
int N;

int main()
{
    while(~scanf("%d", &N))
    {
        memset(dp , 0 , sizeof(dp));
        memset(maz , 0 , sizeof(maz));
        int i, j, k, u;
        while(1)
        {
            int x, y, v;
            scanf("%d %d %d", &x, &y, &v);

            if(!x && !y && !v)
                break;

            maz[x - 1][y - 1] = v;
        }

        dp[0][0] = maz[0][0];
        for(i = 1 ; i < 2 * N ; i++)
        {
            int siz = min(i , N - 1);
            for(j = max(0 , i - N + 1) ; j <= siz ; j++)
            {
                for(k = j ; k <= siz ; k++)
                {
                    int x1 = j, y1 = i - j , x2 = k, y2 = i - k;

                    for(u = 0 ; u < 4 ; u++)
                    {
                        int nx1 = x1 + dx1[u], ny1 = y1 + dy1[u], nx2 = x2 + dx2[u], ny2 = y2 + dy2[u];

                        if(nx1 >= 0 && ny1 >= 0 && nx2 >= 0 && ny2 >= 0)
                        {
                            dp[x1 * N + y1][x2 * N + y2] = max(dp[x1 * N + y1][x2 * N + y2] , dp[nx1 * N + ny1][nx2 * N + ny2]);
                        }
                    }

                    if(j == k)
                        dp[x1 * N + y1][x2 * N + y2] += maz[x1][y1];
                    else
                        dp[x1 * N + y1][x2 * N + y2] += maz[x1][y1] + maz[x2][y2];
                }
            }
        }

        printf("%d\n", dp[N * N - 1][N * N - 1]);
    }

    return 0;
} 

I
这道题还算有点难度,不过重点并不是dp,而是找圈比较烦,dp的话只要把26个字母在一个点的最长数量存起来即可,然后用记忆化搜索来找最长的,可以认为是一个树形dp,然而找圈真的很烦,我直接用了tarjan加自己找自环的方法,看不懂tarjan就自行百度吧。

# include <cstdio>
# include <cstring>
# include <algorithm>
# include <set>

using namespace std;

const int MAX_N = 3e5;

typedef pair<int , int> P;

struct node
{
    int to;
    int next;
};

node edge[MAX_N + 1];
int edg, num, top;

char s[MAX_N + 1];
int head[MAX_N];

bool f;
int dfn[MAX_N], low[MAX_N];
int ss[MAX_N];

bool used[MAX_N];

int dp[MAX_N][26];
int N, M;

int ans;

inline void ad(int from , int to)
{
    edge[edg].to = to;
    edge[edg].next = head[from];
    head[from] = edg++;
}

void dfs(int x)
{
    if(f)
        return;

    dfn[x] = low[x] = ++num;
    used[x] = 1;
    ss[++top] = x;

    int i, j;
    for(i = head[x] ; i ; i = edge[i].next)
    {
        int t = edge[i].to;

        if(!dfn[t])
        {
            dfs(t);
            low[x] = min(low[x] , low[t]);
        }
        else if(used[t])
            low[x] = min(low[x] , dfn[t]);

        for(j = 'a' ; j <= 'z' ; j++)
        {
            dp[x][j - 'a'] = max(dp[x][j - 'a'] , dp[t][j - 'a']);
        }
    }

    if(dfn[x] == low[x])
    {
        used[x] = 0;

        if(ss[top] != x)
            f = 1;

        top--;
    }

    dp[x][s[x] - 'a']++;
    for(i = 0 ; i < 26 ; i++)
    {
        //printf("%d %c %d\n", x, 'a' + i, dp[x][i]);
        ans = max(dp[x][i] , ans);
    }
}

int main()
{
    scanf("%d %d\n", &N, &M);

    gets(s);

    edg = 1;

    int i;
    int t, s;
    set<P> st;
    for(i = 0 ; i < M ; i++)
    {
        scanf("%d %d", &t, &s);

        if(t-- == s--)
        {
            f = 1;
            break;
        }

        if(st.find(P(s , t)) == st.end())
        {
            ad(s , t);
            st.insert(P(s , t));
        }
    }

    if(f)
    {
        puts("-1");
        return 0;
    }

    for(i = 0 ; i < N ; i++)
    {
        if(f)
            break;

        if(!dfn[i])
        {
            dfs(i);
        }
    }

    if(f)
        puts("-1");
    else
        printf("%d\n", ans);

    return 0;
}

2018、6、6
之前poj挂了,之后这三体又挂上去了。

I
第三周的原题,记忆化dfs,之前我的确用过dp的做法,但是要比dfs慢一些。从上往下和从下往上是没有区别的,代码一个字都不用改。

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

using namespace std;

const int MAX_N = 1e3;

int dx[4] = {1 , 0 , -1 , 0}, dy[4] = {0 , 1 , 0 , -1};
int d[MAX_N][MAX_N];
int h[MAX_N][MAX_N];
int N, M, T;

int dfs(int x , int y)
{
    if(d[x][y])
        return d[x][y];

    d[x][y] = 1;

    int i;
    for(i = 0 ; i < 4 ; i++)
    {
        int nx = dx[i] + x, ny = dy[i] + y;
        if(nx >= 0 && nx < N && ny >= 0 && ny < M && h[nx][ny] > h[x][y])
            d[x][y] = max(d[x][y] , 1 + dfs(nx , ny));
    }

    return d[x][y];
}

int main()
{
    scanf("%d %d", &N, &M);

    int i, j;
    for(i = 0 ; i < N ; i++)
        for(j = 0 ; j < M ; j++)
            scanf("%d", &h[i][j]);

    int ans = 0;        
    for(i = 0 ; i < N ; i++)
    {
        for(j = 0 ; j < M ; j++)
        {
            if(!d[i][j])
                dfs(i , j);

            ans = max(d[i][j] , ans);
        }
    }

    printf("%d\n", ans);

    return 0;
}

J
挑战程序设计初级篇习题集题目,一道很简单的区间dp,首先把所有结束时间加R,N也加上R,R就可以去掉了(题目说了所有开始时间都小于N),然后把区间按照开始顺序进行dp。每个时间dp一次,先更新一次之前的最大值,若这里是某区间开始,那么对结束时间最大值进行更新。

# include <stdio.h>
# include <algorithm>

using namespace std;

struct tim
{
    int from, to, cost;
};

const int MAX_M = 1000;

int dp[MAX_M + 1];
int en[MAX_M + 1];
tim mil[MAX_M];
int N, M, R;

bool com(tim a , tim b)
{
    return a.to < b.to;
}

int max(int a , int b)
{
    return (a > b) ? a : b; 
}

void solve()
{
    fill(dp , dp + M + 1 , 0);
    sort(mil , mil + M , com);
    sort(en , en + M + 1);

    int i;
    for(i = 0 ; i < M ; i++)
    {
        int op = lower_bound(en , en + M + 1 , mil[i].from) - en;

        if(en[op] != mil[i].from)
            op--;

        dp[i + 1] = max(dp[i] , dp[op] + mil[i].cost);
    }

    printf("%d\n", dp[M]);
}

int main()
{
    scanf("%d %d %d", &N, &M, &R);

    int i;
    for(i = 0 ; i < M ; i++)
    {
        scanf("%d %d %d", &mil[i].from, &mil[i].to, &mil[i].cost);
        mil[i].to += R;
        en[i + 1] = mil[i].to;
    }
    en[0] = 0;

    solve();

    return 0;
}

H
挑战程序设计初级篇习题集题目,读题比较费劲,就说说怎样求单调不增路径把(方向同理),首先把所有高度排序,可以发现即使移动了,花费最小的时候肯定高度都是之前出现过的,所以用一个离散化,然后用dpij表示把第i个变成第j高的时候的最小花费,然后每次j升高更新之前的最小花费,因为高度单增,所以最小花费不用每次都一个一个找,直接更新即可(否则复杂度变为n立方),再加上变成这么高的花费,这样可以在n方完成。

# include <stdio.h>
# include <math.h>
# include <algorithm>

using namespace std;

# define min(A , B) (A < B) ? A : B;

typedef pair<int , int> P;

const int MAX_N = 2000;
const int INF = 100000000;

int dp[MAX_N + 1][MAX_N + 1], fr[MAX_N + 1];
P nu[MAX_N], fan[MAX_N];

int N;

bool com(P a , P b)
{
    return a.first < b.first;
}

void solve()
{
    //fill(fr , fr + N , INF);
    //fill(ho , ho + N , INF);

    int i, j, sum;

    for(i = N - 2 ; i >= 0 ; i--)
    {
        for(j = 0 ; j < N ; j++)
        {
            dp[i][j] = fr[j] + abs(nu[j].first - fan[i].first);
            fr[j] = dp[i][j];
            if(j > 0) 
                fr[j] = min(fr[j - 1] , dp[i][j]);
        }
    }

    sum = fr[N - 1];
    fill(fr , fr + N , 0);

    for(i = 1 ; i < N ; i++)
    {
        for(j = 0 ; j < N ; j++)
        {
            dp[i][j] = fr[j] + abs(nu[j].first - fan[i].first);
            fr[j] = dp[i][j];
            if(j > 0) 
                fr[j] = min(fr[j - 1] , dp[i][j]);
        }
    }

    sum = min(sum , fr[N - 1]);

    printf("%d\n", sum);
}

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

    int i;
    for(i = 0 ; i < N ; i++)
    {
        scanf("%d", &nu[i].first);
        nu[i].second = i;
    }

    sort(nu , nu + N , com);

    for(i = 0 ; i < N ; i++)
    {
        fan[nu[i].second].first = nu[i].first;
        fan[nu[i].second].second = i;
    }

    solve();

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40772738/article/details/80572962