HFUT&AHU组团训练(一)----DP专题

网址:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=42394#overview

密码:akak

A题:

题意:求两个字符串的最长公共子串,特别的是,这个子串的每块连续的部分必须大于等于题目所给的k。

思路:第一遍打时完全时一片浆糊,采用常规方法,各种细节没有考虑到,果断删了重打。既然段都要求>=k,那么我一次就先找那连续的长度k,再给这一段记录。相当于先局部,再整体。

#include <cstdio>
#include <iostream>
#include <vector>

using namespace std;

const int MAX_SIZE = 1010;

int K;
string s[2];
int v[MAX_SIZE][MAX_SIZE];

inline int common_len(int i, int j) {
  for (int k = 0; k < MAX_SIZE; k++) {
    if(i+k >= s[0].length() || j+k >= s[1].length()) return k;
    if(s[0][i+k] != s[1][j+k]) return k;
  }
  return -1;
}

inline void check(int i, int j) {
  const int len = common_len(i,j);
  v[i+1][j+1] = max(v[i+1][j+1], max(v[i][j+1], v[i+1][j]));
  if(len >= K) {
    for (int k = K; k < len+1; k++)
        v[i+k][j+k] = max(v[i+k][j+k], v[i][j] + k);
  }
}

int main() {

    for (int i = 0; i < MAX_SIZE; i++)
        v[0][i] = v[i][0] = 0;
    while(cin >> K) {
        if(K == 0) break;
        cin >> s[0];
        cin >> s[1];
        const int n = s[0].size(), m = s[1].size();

        for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            v[i+1][j+1] = 0;
        for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            check(i,j);

        cout << v[n][m] << endl;
  }
}


B题:

题意:有很多俄罗斯套娃,讲其恢复原装,求最后桌子上最少有几个套娃?

思路:先将宽度由小到达排序,再讲高度从大到小排序,为了保证同宽度的只能有一个存在在一组套娃。用一个数组维护高度,每个元素代表一组套娃,值为当前该组套娃高度,数组整体是递减的。每读取一个高度,去覆盖当前能覆盖最高套娃组,若没有能覆盖的,则新建一个元素,即增加一个套娃组。最后求数组长度。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;

struct aaa{
    int w;
    int h;
}a[40005];

int n;
int t;

int cmp(aaa a, aaa b){
    if (a.w == b.w)
        return b.h < a.h;
    return b.w > a.w;
}

int d[40005];


int main(){
    memset(d,1,sizeof(d));
    cout << d[1] << endl;
    scanf("%d",&t);
    while(t--){
        scanf("%d", &n);
        for (int i = 0; i < n; i++)
        scanf("%d%d", &a[i].w ,&a[i].h);
        sort(a, a+n, cmp);

    int len = 0;
    for (int i = 0; i < n; i++){
        int l = 0, r = len;
        int m;
        while(l < r){
            m = (l + r) >> 1;
            if (a[i].h <= d[m])
                l = m+1;
            else r = m;
/*
            if (a[i].h >= d[m])
                r = m
            else l = m;*/
        }
        d[l] = a[i].h;
        len+=l==len;
    }

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

    

C题:

题意:有c(1<=c<=4)种颜色的牌,每种牌n(1<=n<=100)张。给出起始顺序,问最少移动几张牌,可使牌组有序,即牌从小到大,且同颜色为一组。

思路:关键在于c只有4种颜色,方便很多。枚举颜色出现的顺序,定一个序列,计算最长上升子序列s。用序列长度减去子序列长度,即是答案。

#include<iostream>
#include<string>
#include<queue>
#include<stack>
#include<cstdio>
#include<cmath>
using namespace std;

struct aaa{
    int col;
    int val;
}w[500];

int MAX = (1 << 31) -1;
int c, n, sum =MAX;
int d[500];
int a[5]={0};
bool v[500];
int q[500];

void card(){
    int s = c*n;
    for (int i = 1; i <= s; i++){
        d[i] = w[i].val * a[w[i].col];
    }

    q[1] = d[1];
    int len = 1;
    for (int i = 2; i <= s; i++){
        if (d[i] > q[len]) q[++len] = d[i];
        else{
            int l = 1; int r = len;
            while(l <= r){
                int m = (l + r) >> 1;
                if (q[m] < d[i]) l = m + 1;
                else r = m - 1;
            }
            q[l] = d[i];
        }
    }

    if (sum > s - len) sum = s-len;
}
int main(){

    scanf("%d%d", &c, &n);
    int s = c*n;
    for (int i = 1; i <= s; i++){
        scanf("%d%d", &w[i].col, &w[i].val);
    }
    s = 1;
    for (int i = 2; i <= c; i++)
        s = s*i;

    while(s--){
        for (int i = 1; i <= c; i++){
            a[i] = 1;
            if (c > 1){
                for (int j = 1; j <= c; j++)
                if (i != j){
                    a[j] = 101;
                    if (c > 2){
                        for (int k = 1; k <= c; k++)
                        if (i!=k && j!= k){
                            a[k] = 20101;
                            if (c > 3){
                                for (int l = 1; l <= c; l++)
                                if (i != l && j != l && k!= l){
                                    a[l] = 4010101;
                                    card();
                                }
                            }else card();
                        }
                    }else card();
                }
            }else card();
        }
    }
    printf("%d\n", sum);
}

D题:

题意:有一排药水,药水有自己的颜色(a,b...),每次混合相邻药水,会产生a*b的气体,位置不变,颜色变为(a+b) mod 100.最后求生成最小体积是多少

思路:用记忆化搜索即可,无注意点。

#include<iostream>
#include<cstring>
using namespace std;

struct aaa{
    int c;
    int v;
};

aaa f[500][500];
int n;

aaa dp(int l, int r){
    if (f[l][r].c < 100) return f[l][r];

    aaa t,mag1,mag2;
    t.v = (1 << 31) -1;
    for (int i = l; i < r; i++){
        mag1 = dp(l, i);
        mag2 = dp(i+1, r);
       // cout << l << " " << i << mag1.c << endl;
        //cout << i+1 << " " << r << mag2.c << endl;
        if (mag1.c*mag2.c+ f[l][i].v + f[i+1][r].v < t.v) {
            t.v = mag1.c*mag2.c + f[l][i].v + f[i+1][r].v;
            t.c = (mag1.c+mag2.c) %100;
        }
    }

    f[l][r] = t;
    return f[l][r];
}

int main(){

    while(cin >> n){
        for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++){
            f[i][j].c = 100;
            f[i][j].v = 0;
        }
        for (int i = 1; i <= n; i++)
        cin >> f[i][i].c;

        aaa t = dp(1,n);
        cout << t.v << endl;
    }
}

F题:

题意:一帮人参加聚会,要求他们的美丽度和强壮度都必须同时满足大于任何一个人,或者小于任何一个人(不能及格比A强,一个比A弱)最多邀请多少人,并输出顺序(任意)

思路:最长上升序列变形,记录前驱节点即可。

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;

struct aaa{
    int s;
    int b;
    int p;
    bool operator < (aaa a)const {
        if (s == a.s) return b > a.b;
        return s < a.s;
    }
}a[100010];

int d[100010];
int pos[100010]={0};
int pre[100010]={0};

int main(){
    int n;

    scanf("%d", &n);
    for (int i = 1; i <= n; i++){
        scanf("%d%d", &a[i].s, &a[i].b);
        a[i].p = i;
    }

    sort(a+1,a+n+1);

    int len = 1;
    pos[1] = 1;
    for (int i =2; i <= n; i++){
        if (a[i].b > a[pos[len]].b){
            pos[++len] = i;
            pre[i] = pos[len-1];
        }
        else{
            int l = 1; int r = len;
            while(l <= r){
                int m = (l +r) >> 1;
                if (a[pos[m]].b < a[i].b) l = m + 1;
                else r = m - 1;
            }
            pos[l] = i;
            pre[i] = pos[l-1];
        }
    }

    printf("%d\n", len);
    for (int i = pos[len]; i; i = pre[i])
        printf("%d ",a[i].p);
    cout << endl;
}



I题:

题意:求一个点集最少点的数目,要求满足一棵树的任意一条边都至少有一个端点在这个点集中。

思路:开始想的二分图,结果发现很容易反正。最后使用树形dp,简单解决。

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

vector<int> f[100001];
int d[100001][2];
int n,x,y;

void treedp(int u, int rt){
    d[u][0] = 0;
    d[u][1] = 1;
    for (int i = 0; i < f[u].size(); i++){
        int temp = f[u][i];
        if (f[u][i] == rt) continue;

        treedp(temp, u);
        d[u][0] += d[temp][1];
        d[u][1] += min(d[temp][1], d[temp][0]);
    }
}

int main(){
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        f[i].clear();

    for (int i = 1; i < n; i++){
        scanf("%d%d", &x, &y);
        f[x-1].push_back(y-1);
        f[y-1].push_back(x-1);
    }

    treedp(0,-1);

    cout << min(d[0][0], d[0][1]) << endl;
}

E题:

题意:潜水员需要OL升氧气和NL升氮气,有若干组气体瓶o升氧气,n升氮气,w重量。最后要求至少满足OL和NL的情况下,潜水员最轻要背多少?

思路:01背包变形,f[i][j][k]代表到第i组气体瓶时j氧气k氮气最少的重量。开始想可以用2维优化,但是发现因为状态转移方程式加法,无法改变。

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

int test,OL,NL,n,ol,on;
int f[1001][22][80];
struct aaa{
    int o;
    int n;
    int w;
}a[1001];

int main(){
    int MAX= (1 <<31) -1;
    cin >> test;
    while(test--){
        cin >> OL >> NL;
        cin >> n;
        for (int i = 1; i <= n; i++)
        cin >> a[i].o >> a[i].n >> a[i].w;

        for (int j = OL; j >= 0; j--)
        for (int k = NL; k >= 0; k--){
            f[0][j][k] = MAX;
        }

        for (int i = 1; i <= n; i++){
        for (int j = OL; j >= 0; j--)
        for (int k = NL; k >= 0; k--){
            f[i][j][k] = f[i-1][j][k];
            ol = j + a[i].o > OL ? OL : j + a[i].o;
            on = k + a[i].n > NL ? NL : k + a[i].n;
            if ( j == 0 && k == 0 && f[i][ol][on] > a[i].w)
                f[i][ol][on] = a[i].w;
            if (f[i-1][j][k] < MAX && f[i-1][j][k] + a[i].w < f[i][ol][on])
                f[i][ol][on] = f[i-1][j][k] + a[i].w;
            else f[i][j][k] = f[i-1][j][k];
        }
        }
        cout << f[n][OL][NL] <<endl;
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值