洛谷试炼场 动态规划TG.lv(1)

矩阵取数游戏

链接:https://www.luogu.org/problemnew/show/P1005

对于一个给定的 n \times mn×m 的矩阵,矩阵中的每个元素 ai,j 均为非负整数。游戏规则如下:

  1. 每次取数时须从每行各取走一个元素,共 n 个。经过 m 次后取完矩阵内所有元素;
  2. 每次取走的各个元素只能是该元素所在行的行首或行尾;
  3. 每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值*2^i ,其中 i 表示第 i次取数(从 1 开始编号);
  4. 游戏结束总得分为 m 次取数得分之和。

帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。

n, m <= 80, ai,j <= 1000;

题解:每行互不影响,对于每行进行dp, dp[i][l]表示这行取了i个,左边取了j个, 右边取的个数就定了,就从上次取左边或右边转移过来就好了;

值得一提的是这题爆long long了,所以可以要c++11的——Int128,可以到2^128,对这题绰绰有余,但输入输出必须手写;

#include<bits/stdc++.h>
using namespace std;
const int M = 88;
//#define lll long long
#define lll __int128
lll dp[M][M][M], ans, mx[M], pf[M];
int a[M][M];
void print(lll x)
{
    if (x==0) return;
    if (x) print(x/10);
    putchar(x%10+'0');
}


int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)scanf("%d", &a[i][j]);
    pf[0] = 1;
    for(int i = 1; i <= 80; i++)pf[i] = pf[i - 1] * 2;
    
    
    for(int i = 1; i <= m; i++)
        for(int j = 1; j <= n; j++)
            for(int lf = 0; lf <= i; lf++){
                lll a1 = -1, a2 = -1;
                if(lf != i) a1 = dp[j][lf][i - lf - 1] + pf[i] * a[j][m - (i - lf) + 1];
                if(lf) a2 = dp[j][lf - 1][i - lf] + pf[i] * a[j][lf];
                dp[j][lf][i - lf] = max(a1, a2);
                if(i == m)mx[j] = max(mx[j], dp[j][lf][i - lf]);
                //printf("%d %d %d %d\n", i, j, lf, dp[j][lf][i-lf]);
            }
    for(int i = 1; i <= n; i++)ans += mx[i];
    if(!ans)puts("0");
    else print(ans);
}
View Code

 

P1373 小a和uim之大逃离

链接;https://www.luogu.org/problemnew/show/P1373

题目描述

瞬间,地面上出现了一个n*m的巨幅矩阵,矩阵的每个格子上有一坨0~k不等量的魔液。怪物各给了小a和uim一个魔瓶,说道,你们可以从矩阵的任一个格子开始,每次向右或向下走一步,从任一个格子结束。开始时小a用魔瓶吸收地面上的魔液,下一步由uim吸收,如此交替下去,并且要求最后一步必须由uim吸收。魔瓶只有k的容量,也就是说,如果装了k+1那么魔瓶会被清空成零,如果装了k+2就只剩下1,依次类推。怪物还说道,最后谁的魔瓶装的魔液多,谁就能活下来。小a和uim感情深厚,情同手足,怎能忍心让小伙伴离自己而去呢?沉默片刻,小a灵机一动,如果他俩的魔瓶中魔液一样多,不就都能活下来了吗?小a和他的小伙伴都笑呆了!

现在他想知道他们都能活下来有多少种方法。

n,m<=800,k <=15;

题解;dp[ i ][ j ][p][ 0/1 ]表示现在在i,j 这个格子,差值为 p, 0小A取, 1uim取的方案数;

初值dp[ i ][ j ][ a[i][j] ][ 0 ] = 1, 小A可以从任意格子取,转移的时候0和1相互转换,如果是小A取,差值就增大,uim差值就减小

注意这题是%k+1!

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 1e9 + 7;
const int M = 105;
int dp[805][805][20][2], ans, a[805][805];
inline int moc(int a){return a >= mod ? a - mod : a;}
int main(){
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++){
            scanf("%d", &a[i][j]);
            dp[i][j][a[i][j]][0] = 1;
        }
    k++;    
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            for(int p = 0; p < k; p++){
                if(i > 1){
                    dp[i][j][p][0] = moc(dp[i][j][p][0] + dp[i - 1][j][(p - a[i][j] + k) % k][1]);
                    dp[i][j][p][1] = moc(dp[i][j][p][1] + dp[i - 1][j][(p + a[i][j]) % k][0]);
                }
                if(j > 1){
                    dp[i][j][p][0] = moc(dp[i][j][p][0] + dp[i][j - 1][(p - a[i][j] + k) % k][1]);
                    dp[i][j][p][1] = moc(dp[i][j][p][1] + dp[i][j - 1][(p + a[i][j]) % k][0]);
                }
                if(p == 0) ans = moc(ans + dp[i][j][p][1]);
            }
    printf("%d\n", ans);
    
            
}
View Code

 

P2279 [HNOI2003]消防局的设立

链接:https://www.luogu.org/problemnew/show/P2279

题目描述

2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地。起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构。如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d。

由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局。消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾。

你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。

题解:贪心,度数深的点的覆盖点是固定的,然后能覆盖就覆盖;

见:

#include <bits/stdc++.h>
const int M = 1e5 +10;
#define inf 1e8
using namespace std;
int tot, n, k, fa[M], t, dep[M], h[M];
bool vis[M];
struct edge{int v,nxt;}G[M<<1];
void add(int u, int v){G[++tot].nxt = h[u]; h[u] = tot; G[tot].v = v; }
void dfs(int u, int f){
    dep[u] = dep[f] + 1;
    fa[u] = f;
    for(int i = h[u]; i; i = G[i].nxt){
        int v = G[i].v;
        if(v == f)continue;
        dfs(v, u);
    }
}
void dst(int u, int dd, int f){
    vis[u] = 1;
    for(int i = h[u]; i; i = G[i].nxt){
        int v = G[i].v;
        if(v == f)continue;
        if(dd < k)dst(v, dd+1, u);
    }
}
struct Node{
    int id, dep;
    bool operator < (const Node& a)const{
        return a.dep > dep;
    }
};
priority_queue <Node> Q;
int main()
{
    int cnt = 0;
    k = 2;
    scanf("%d", &n);
    for(int i = 1; i < n; i++){
        int u;
        scanf("%d", &u);
        add(u, i+1); add(1+i, u);
    }
    dfs(1, 0);
    for(int i = 1; i <= n; i++)
        Q.push((Node){i, dep[i]});
    for(int i = 1; i <= n; i++){
        int aa = Q.top().id;
        Q.pop();
        if(vis[aa])continue;
        int tt = 0;
        while(tt < k){
            if(!fa[aa])break;
            aa = fa[aa];
            tt++;
        }
        dst(aa, 0, aa);
        //printf("%d ", aa);
        cnt++;
    }
    printf("%d\n", cnt);
    return 0;
}
View Code

 

P1220 关路灯

链接:https://www.luogu.org/problemnew/show/P1220

题目描述

某一村庄在一条路线上安装了n盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。

为了给村里节省电费,老张记录下了每盏路灯的位置和功率,他每次关灯时也都是尽快地去关,但是老张不知道怎样去关灯才能够最节省电。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。开始他以为先算一下左边路灯的总功率再算一下右边路灯的总功率,然后选择先关掉功率大的一边,再回过头来关掉另一边的路灯,而事实并非如此,因为在关的过程中适当地调头有可能会更省一些。

现在已知老张走的速度为1m/s,每个路灯的位置(是一个整数,即距路线起点的距离,单位:m)、功率(W),老张关灯所用的时间很短而可以忽略不计。

请你为老张编一程序来安排关灯的顺序,使从老张开始关灯时刻算起所有灯消耗电最少(灯关掉后便不再消耗电了)。

n <= 50;

题解:区间DP,dp[i][j][0/1]表示处理了i到j区间现在在左/右端点的最少耗费功率;

#include<bits/stdc++.h>
using namespace std;
const int M = 55;
int a[M], sum[M], gl[M], dp[M][M][2];
int main(){
    int n, c;
    scanf("%d%d", &n, &c);
    for(int i = 1; i <= n; i++)
        scanf("%d%d", &a[i], &gl[i]), sum[i] = sum[i - 1] + gl[i];
    memset(dp, 127/3, sizeof(dp));
    dp[c][c][0] = dp[c][c][1] = 0;
    for(int l = 2; l <= n; l++)
        for(int i = 1; i + l - 1 <= n; i++){
            int j = i + l - 1;
            int val1 = sum[n] - sum[j] + sum[i - 1];
            dp[i][j][1] = min(dp[i][j - 1][1] + (a[j] - a[j - 1]) * (val1 + gl[j]), dp[i][j - 1][0] + (a[j] - a[i]) * (val1 + gl[j]));
            dp[i][j][0] = min(dp[i + 1][j][0] + (a[i + 1] - a[i]) * (val1 + gl[i]), dp[i + 1][j][1] + (a[j] - a[i]) * (val1 + gl[i]));
            //printf("%d %d %d %d\n", i, j, dp[i][j][0], dp[i][j][1]);
        }
    
    printf("%d\n", min(dp[1][n][0], dp[1][n][1]));
}
View Code

 

 P1156 垃圾陷阱

链接:https://www.luogu.org/problemnew/show/P1156

题目描述

卡门――农夫约翰极其珍视的一条Holsteins奶牛――已经落了到“垃圾井”中。“垃圾井”是农夫们扔垃圾的地方,它的深度为D(2D100) 英尺。

卡门想把垃圾堆起来,等到堆得与井同样高时,她就能逃出井外了。另外,卡门可以通过吃一些垃圾来维持自己的生命。

每个垃圾都可以用来吃或堆放,并且堆放垃圾不用花费卡门的时间。

假设卡门预先知道了每个垃圾扔下的时间t(0<t1000) ,以及每个垃圾堆放的高度 h(1h25 )和吃进该垃圾能维持生命的时间f(1f30) ,要求出卡门最早能逃出井外的时间,假设卡门当前体内有足够持续 1010 小时的能量,如果卡门 1010 小时内没有进食,卡门就将饿死。

输入输出格式

输入格式:

第一行为 2 个整数, D 和G(1G100) , GG 为被投入井的垃圾的数量。

第二到第 G+1行每行包括 3 个整数: T (0<T<=1000) ,表示垃圾被投进井中的时间;F(1F30) ,表示该垃圾能维持卡门生命的时间;和 H(1H25) ,该垃圾能垫高的高度。

 

输出格式:

如果卡门可以爬出陷阱,输出一个整表示最早什么时候可以爬出;否则输出卡门最长可以存活多长时间。

题解:dp[i][j]表示我现在要考虑第i个垃圾,当前最久可以活到j天的最大高度;每天选择吃或者累积,往后更新,注意一下先后顺序;

 

#include<bits/stdc++.h>
using namespace std;
const int M = 55;
int T, dp[105][1005];
struct litter{int t, v, h;}p[105];
bool cmp(litter a, litter b){
    return a.t < b.t;
}
int main(){
    int D, G;
    scanf("%d%d", &D, &G);
    for(int i = 1; i <= G; i++)
        scanf("%d%d%d", &p[i].t, &p[i].v, &p[i].h), T += p[i].t;
    sort(p + 1, p + 1 + G, cmp);
    int now = 10;
    for(int i = 1; i <= G; i++){
        if(now + p[i].v >= p[i + 1].t)now += p[i].v;
        else {
            now += p[i].v; break;
        }
    }
    if(10 < p[1].t){
        printf("%d\n", now);
        return 0;
    }
    int arr = 1e8;
    memset(dp, -1, sizeof(dp));
    dp[1][10] = 0;
    for(int i = 1; i <= G; i++)
        for(int j = max(10, p[i].t); j <= p[G].t; j++)
            if(dp[i][j] != -1){
                if(j >= p[i + 1].t) dp[i + 1][j] = max(dp[i + 1][j], dp[i][j] + p[i].h);
                if(dp[i][j] + p[i].h >= D)arr = min(arr, p[i].t);
                int to = j + p[i].v;
                if(to < p[i + 1].t)continue;
                if(to > p[G].t)to = p[G].t;
                dp[i + 1][to] = max(dp[i + 1][to], dp[i][j]);
            }
    if(arr < 1e8)printf("%d\n", arr);
    else printf("%d\n", now);
}
View Code

 

posted @ 2018-09-25 17:38  Ed_Sheeran  阅读(235)  评论(0编辑  收藏  举报