10.9 NOIP[真] 背包 + 状压 + 单调栈 + 最短路

Birthday

内存限制 256MB 时间限制 1S
程序文件名 birthday.pas/birthday.c/birthday.cpp
输入文件 birthday.in 输出文件 birthday.out
前些天是Miss D生日,gnaw去逛街给Miss D买礼物。商店有这样的福利:第i件商品价值为Wi,买k个(k>0),可以送Ai × k + Bi颗Miss D喜欢的糖
最大预算为M元,最多能赚取多少糖呢?
输入
第一行两个整数N ,M表示礼物数和最大预算
之后N行,表示每件物品的W A B
输出
一个整数 表示最多能赚取的糖数
样例输入
2 100
10 2 1
20 1 1 样例输出
21
数据范围
30%:
1 ≤ M ≤ 100
1 ≤ N ≤ 100
100%:
1 ≤ M ≤ 2000 1 ≤ N ≤ 1000
0 ≤ Ai, Bi ≤ 2000 1 ≤ Wi ≤ 2000

很naive的背包… 但是不要以为是纯的完全背包, 因为第1次选会有额外的bi加成…所以用f[v][0]表示当前容量为v还没选过i的可获得的最大糖果数, f[v][1]则是选过当前的. 就可以按照完全背包一样的转移了.

#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 2005;
int n, m, ans;
bool mark[maxn];
int f[maxn][2], w[maxn], a[maxn], b[maxn];
int main(){
    freopen("birthday.in", "r", stdin);
    freopen("birthday.out", "w", stdout);
    scanf("%d%d", &n ,&m);
    for(int i = 1; i <= n; ++i) scanf("%d%d%d", &w[i], &a[i], &b[i]);
    for(int i = 1; i <= n; ++i){
        for(int v = w[i]; v <= m; ++v){
                f[v][1] = max(f[v][1], f[v - w[i]][0] + a[i] + b[i]);
                f[v][1] = max(f[v][1], f[v - w[i]][1] + a[i]);
        }
        for(int v = w[i]; v <= m; ++v) f[v][0] = max(f[v][0], f[v][1]);
    }
    for(int i = 0; i <= m; ++i) ans = max(ans, max(f[i][0], f[i][1]));
    printf("%d\n", ans);
    return 0;
}

chess

内存限制 256MB 时间限制 1S
程序文件名 chess.pas/chess.c/chess.cpp
输入文件 chess.in 输出文件 chess.out
Miss D 拉gnaw陪她玩游戏。有一个有趣的游戏是这样的:在一个n*m的棋盘上,有一些格子上可以放棋子,两个人轮流在棋盘上放棋子。放置规则只有一个,那就是棋子放置的位置不能与其他棋子相邻。
Miss D突然问gnaw,这个棋盘上放置棋子的合法状态有多少种?
输入
两个整数 n m 表示棋盘的大小。
之后n行,每行m个数字,1表示这个格子可以放置棋子,0表示这个格子不能放置棋子。
输出 一个整数 表示一共有多少种合法的棋子放置方法, 答案对100000000取模.
样例输入 2 3 1 1 1 0 1 0 样例输出
9
数据范围
50% 1<=n,m<=5
100% 1<=n,m<=12

最基础的状压?枚举当前行和上一行的状态即可. 可以先把每一行的合法状态预处理出来.

#include<stdio.h>
const int mod = 100000000;
int n, m, ans, lim, top[(1 << 13)];
int dp[13][(1 << 13)], f[13], s[13][(1 << 13)];
inline void init(int x){
    for(int i = 0; i <= lim; ++i){
        if(i & f[x]) continue;
        if(i & (i << 1)) continue;
        s[x][++top[x]] = i;
    }
}
int main(){
    freopen("chess.in", "r", stdin);
    freopen("chess.out", "w", stdout);
    scanf("%d%d", &n, &m);
    lim = (1 << m) - 1;
    for(int i = 1; i <= n; ++i)
        for(int j = 0; j < m; ++j){
            int x;
            scanf("%d", &x);
            if(!x) f[i] |= (1 << j);
        }
    for(int i = 1; i <= n; ++i) init(i);
    for(int i = 1; i <= top[1]; ++i) dp[1][s[1][i]] = 1;
    for(int i = 2; i <= n; ++i)
        for(int j = 1; j <= top[i - 1]; ++j)
            for(int k = 1; k <= top[i]; ++k)
                if(!(s[i][k] & s[i - 1][j]))
                    dp[i][s[i][k]] = (dp[i][s[i][k]] + dp[i - 1][s[i - 1][j]]) % mod;
    for(int i = 0; i <= lim; ++i) ans = (ans + dp[n][i]) % mod;
    printf("%d\n", ans);
    return 0;
}

Question

内存限制 256MB 时间限制 1S
程序文件名 question.pas/ question.c/ question.cpp
输入文件 question.in 输出文件 question.out
Miss D给gnaw准备了n*m个问题(比如miss D最近在追什么电视剧),并让gnaw从中选出一个矩形的区域,这个区域中的问题gnaw都能答对。Gnaw只想知道,这个矩形最大面积是多少?
输入
n m
之后n行 每行有m个数据。
1表示这个问题gnaw会做,0表示这个问题不会做。
输出
一个数 表示最大全1矩形的问题数
样例输入 4 4 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0
样例输出
4
50% 1<=n,m<=100
100% 1<=n,m<=1000

这道题可能算是4道题里面稍微难一点的题了吧… 虽然还是很简单. 其实跟poj2559很像, 只是这里面要枚举每一行而已.
枚举每一行之后, 处理出每一列在以当前行为底的最大向上延伸, 那么每一列都是1个1 * ?的矩阵, 那么相当于就是求当前的最大矩形. 然后每一行枚举之后求一个max.
模型这样稍微转化之后, 用单调栈维护每一列向右的最大延伸. 这里最大延伸是指从当前向右一直是以当前列的高度作为最小值的最大区间. 一个单调递增的单调栈就可以解决这个问题. 然后再用同样的方法处理向左的. 每一列向左向右的最大区间长度乘以这一列的高度就是一个合法矩阵. ans取max即可.
详情看代码.

#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 1005;
int n, m, top, ans;
int a[maxn][maxn], h[maxn], s[maxn], mx[maxn];
int main(){
    freopen("question.in", "r", stdin);
    freopen("question.out", "w", stdout); 
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
            scanf("%d", &a[i][j]);
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= m; ++j){
            if(a[i][j]) h[j] = h[j] + 1;
            else h[j] = 0;
            mx[j] = 0;
        }
        top = 0;
        for(int j = 1; j <= m + 1; ++j){ //FOR到m+1是因为为了全部弹栈 
            while(top && h[j] < h[s[top]]){
                mx[s[top]] += j - s[top] - 1;
                top--;
            }
            s[++top] = j;
        }
        top = 0;
        for(int j = m; ~j; --j){
            while(top && h[j] < h[s[top]]){
                mx[s[top]] += s[top] - j - 1;
                top--;
            }
            s[++top] = j;
        }
        for(int j = 1; j <= m; ++j) ans = max(ans, h[j] * (mx[j] + 1));
    }
    printf("%d\n", ans);
    return 0;
}

road

内存限制 256MB 时间限制 3S
程序文件名road.pas/road.c/road.cpp
输入文件 road.in 输出文件 road.out
(编不出故事了)在一个有向无环图中,每个点都有一个对应的权值,从一个入度为零的点走到出度为零的点的最大权值和是多少?
输入
两个整数 n m
表示n个节点,m条边
之后n行表示每个节点的权值
之后m行 每行两个整数u v 表示从u到v有一天有向边
输出
一个数,符合题意的最大权值和
样例输入
6 5
1
2
2
3
3
4
1 2
1 3
2 4
3 4
5 6
样例输出
7

数据范围
50%的数据
1 ≤ n ≤ 1000, 0 ≤ m ≤ 20000
100%的数据
1 ≤ n ≤ 100000, 0 ≤ m ≤ 2000000
0 ≤ |Vi| ≤ 20000

建一个虚拟源连向所有的入度为0的点, 把所有出度为0的点连向一个虚拟汇. 跑一边spfa最长路即可.

#include<stdio.h>
#include<queue>
#include<cstring>
using namespace std;
typedef long long dnt;
const int maxn = 200005;
queue<int> q;
bool vis[maxn];
dnt dis[maxn];
int h[maxn], w[maxn], in[maxn], out[maxn], n, m, num, S, T;
struct edge{
    int nxt, v, w;
}e[4000005];
inline void add(int u, int v){e[++num].v = v, e[num].nxt = h[u], h[u] = num;}
inline void SPFA(){
    memset(dis, -20, sizeof(dis));
    dis[S] = 0, q.push(S), vis[S] = true;
    while(!q.empty()){
        int u = q.front(); q.pop();
        vis[u] = false;
        for(int i = h[u]; i; i = e[i].nxt){
            int v = e[i].v;
            if(dis[v] < dis[u] + w[v]){
                dis[v] = dis[u] + w[v];
                if(!vis[v]){
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    printf("%I64d\n", dis[T]);
}
int main(){
    freopen("road.in", "r", stdin);
    freopen("road.out", "w", stdout);
    scanf("%d%d", &n, &m);
    S = 0, T = n + 5;
    for(int i = 1; i <= n; ++i) scanf("%d", &w[i]);
    for(int i = 1; i <= m; ++i){
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y);
        out[x]++, in[y]++;
    }
    for(int i = 1; i <= n; ++i)
        if(!in[i]) add(S, i);
    for(int i = 1; i <= n; ++i)
        if(!out[i]) add(i, T);
    SPFA();
    return 0;
}

总结

要是noip考试有今天这么简单就好了…
虽然这么说, 第一题还是自己没有考虑周到的地方, 竟然只拿了20分, 所以以320告终.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值