初见安~这次卡过了200的门槛!立个Flag!
Poker
传送门:入门OJ P2046
Description
一副扑克牌有n张牌。一般你买的一副新扑克牌里除了这n张牌外还会有一些张特殊的牌,如
果你不小心弄丢了n张牌中的某一张,就可以用特殊牌来代替,但是如果你弄丢两张的话就
没有办法了,因为特殊牌上的图案是一样的。
现在你得到了很多扑克牌,准确来说,n种牌你各有a1、a2、……、an张,同时你还有b张特
殊牌,现在你需要从这些牌中整理出若干副牌供大家使用。整理出的一副牌可以由n种普通
牌各一张组成,也可以由n-1种普通牌各一张再加一张特殊牌组成。
请你设计出一种方案,整理出尽可能多的牌。
Input
输入包括2行
第一行给出n和b1
第二行给出a1,a2…an。
1<=n<=1000000牌的数量<=10^6
Output
输出最多能整理出的牌的副数。
Sample Input
5 5
5 5 5 5 5
Sample Output
6
Sol
这个题其实第一反应是很懵的……感觉是个贪心,但是如果一开始就先把能凑起来的都凑起来似乎也不对。所以给样例点个赞!!!提醒了我们一件事——特殊牌的作用不应该为当有牌不够的时候才用上,而是能用就用,用来省牌以多凑几副,因为每一副牌只能用一次特殊牌。
所以贪心的思路就有了——每次都选牌数最少的一种牌发一张特殊牌,尽量把牌用完。如果出现了两种牌同时缺牌的情况,那就break;如果特殊牌用完了,但是最少的牌还能继续凑,那就继续。
思路就这么简单,具体实现的话这里因为优先队列常数太大怕TLE就很愚蠢地写了个 线段树来维护……【至少本机测数据等给我的感觉是线段树更快】并且每发一张牌,并不是所有牌直接数量减一,而是仅仅让被发的牌数量加一,判断维护的牌数减去当前轮数【也就是每种牌已经用掉的牌数】是否出现了两次为0即可break;而特殊牌用完后,我们已经进行了b轮;最少的牌首先也要减去b,才是剩下的可以用的牌数,所以化简一下直接输出即可。
#include<bits/stdc++.h>
#define maxn 1000005
using namespace std;
int read()
{
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, b;
struct node
{
int num, x;
bool operator < (const node &tmp) const {return x < tmp.x;}
}minn[maxn << 2];
void build(int p, int l, int r)
{
if(l == r) {minn[p].x = read(), minn[p].num = l; return;}
register int mid = l + r >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
minn[p] = min(minn[p << 1], minn[p << 1 | 1]);
}
void change(int p, int l, int r, int x)
{
if(l == r) {minn[p].x++; return;}
register int mid = l + r >> 1;
if(x <= mid) change(p << 1, l, mid, x);
else change(p << 1 | 1, mid + 1, r, x);
minn[p] = min(minn[p << 1], minn[p << 1 | 1]);
}
signed main()
{
// freopen("in.txt", "r", stdin);
n = read(), b = read();
build(1, 1, n);//关于线段树……
register int tmp1, tmp2, i;
for(i = 1; i <= b; i++)
{
tmp1 = minn[1].x; tmp2 = minn[1].num;//还要记录是第几个
change(1, 1, n, tmp2);//修改
if(minn[1].x == i) break;//如果==i,就相当于仍有一个牌用完了的,break了
}
printf("%d\n", minn[1].x);
return 0;
}
就这样。
工业时代
传送门:入门OJ P6216
Description
小FF的第一片矿区已经开始运作了, 他着手开展第二片矿区……小FF的第二片矿区, 也是"NewBe_One"计划的核
心部分, 因为在这片矿区里面有全宇宙最稀有的两种矿物,科学家称其为NEW矿和BE矿。矿区是被划分成一个n*m
的矩形区域。 小FF探明了每一小块区域里的NEW矿和BE矿的蕴藏量, 并且小FF还在矿区的北边和西边分别设置了N
EW矿和BE矿的收集站。你的任务是设计一个管道运输系统,使得运送的NEW矿和BE矿的总量最多。管道的型号有两
种,一种是东西向,一种是南北向。在一个格子内你能建造一种管道,但不能两种都建。如果两个同类型管道首位
相接,它们就可以被连接起来。另外这些矿物都十分不稳定,因此它们在运送过程中都不能拐弯。这就意味着如果
某个格子上建有南北向管道,但是它北边的格子建有东西向管道,那么这根南北向管道内运送的任何东西都将丢失
。进一步地,运到NEW矿收集站的BE矿也会丢失,运到BE矿收集站的NEW矿也会丢失。
Input
第一行包含两个整数n和m,表示矿区大小。
以下n行,每行m个整数,其中第i行第j个整数G[ i , j ] 描述各个格子上的BE矿数量。
接下来以类似的矩阵表示各个格子上的NEW矿数量。
0<= n, m <=1000;
0<= G[ i, j ] <=1000.
Output
仅一个整数, 表示最多可以采集到的NEW矿和BE矿的总量。
Sample Input
4 4
0 0 10 9
1 3 10 0
4 2 1 3
1 1 20 0
10 0 0 0
1 1 1 30
0 0 5 5
5 10 10 10
Sample Output
98
Sol
一看就是一个dp题。但是考试的时候我想复杂了……还一横一竖地考虑……忽略了一个很关键的信息——对于有意义的一行,一定是左边一部分向左,右边一部分向上。这样的话,只要上一行的状态满足向上的包含了当前状态的向上的部分,就是可以直接dp下去的。有一个小小的优化操作——单调栈单调递减维护上一行的最大值。
上代码——
#include<bits/stdc++.h>//左右走只能从左边出发往右,所以剩下的都是上边的
#define maxn 1005
using namespace std;
int read()
{
int x = 0, ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x;
}
int n, m, w[maxn][maxn][2];
int dp[maxn][maxn];
int stc[maxn], top = 0;
int main()
{
// freopen("in.txt", "r", stdin);
n = read(), m = read();
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) w[i][j][0] = w[i][j - 1][0] + read();
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) w[i][j][1] = w[i][j - 1][1] + read();//每一行前缀和
for(int i = 1; i <= n; i++)
{
top = 0;
for(int j = 0; j <= m; j++)
{
while(top && stc[top] < dp[i - 1][j]) top--;//单调栈
stc[++top] = dp[i - 1][j];//dp是直到当前第i行第j列的最优解
dp[i][j] = stc[1] + w[i][j][0] + w[i][m][1] - w[i][j][1];
}
}
int ans = 0;
for(int i = 0; i <= m; i++) ans = max(ans, dp[n][i]);
printf("%d\n", ans);
}
第三题其实dp也很好写,但是由于涉及到高精过于繁琐所以就不整理了QAQ不开高精只能过40的。