T1 mine
dp题,考试没想出来,一直在想暴力怎么打,然而这个暴力也打到弃疗,最后爆0。。
【solution】写的:设 f(i,j)表示当前填上了 1~i 中的?,第 i 个字符为 j的方案数,枚举下一个字符转移。
我觉得根本写的不清楚,所以仔细写了一份题解如下:
和solution在设计dp上有一丁点不一样,但整体思路是一致的。(感谢同学的耐心解释和指导)
我们记dp[i][j]为当前考虑到了第i位,j分为0/1/2/3/4时的方案数:
- dp[i][0]表示第i位上是0.
- dp[i][1]表示第i位上是1且左边已有地雷(*)
- dp[i][2]表示第i位上是1且左边无地雷(*)
- dp[i][3]表示第i位上是2
- dp[i][4]表示第i位上是* 地雷
那么,根据题目要求可得出这些dp[i][j]的转移如下:
f[i][0] = f[i-1][0] + f[i-1][1];
f[i][1] = f[i-1][4];
f[i][2] = f[i-1][0] + f[i-1][1];
f[i][3] = f[i-1][4];
f[i][4] = f[i-1][2] + f[i-1][3] + f[i-1][4];
初值:
if(s[1] == '0') f[1][0] = 1;
else if(s[1] == '1') f[1][2] = 1;
else if(s[1] == '*') f[1][4] = 1;
else if(s[1] == '?') f[1][0] = f[1][2] = f[1][4] = 1;
else 不满足要求,答案为0.
目标状态:
由于最后一个格子之后不会再有*,所以答案就是 f[n][0]+f[n][1]+f[n][4].
于是有了如下程序:
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1000010;
const int p = 1e9+7;
int n;
int f[N][7];
char s[N];
int MOD(int x){
return x >= p ? (x-p) : x;
}
int main(){
freopen("mine.in", "r", stdin);
freopen("mine.out","w",stdout);
scanf("%s", s+1);
n = strlen(s+1);
if(s[1] == '0') f[1][0] = 1;
else if(s[1] == '1') f[1][2] = 1;
else if(s[1] == '*') f[1][4] = 1;
else if(s[1] == '?') f[1][0] = f[1][2] = f[1][4] = 1;
else{
puts("0");
return 0;
}
for(int i = 2; i <= n; i++){
if(s[i] == '0') f[i][0] = MOD(f[i-1][0] + f[i-1][1]);
else if(s[i] == '1'){
f[i][1] = f[i-1][4];
f[i][2] = MOD(f[i-1][0] + f[i-1][1]);
}
else if(s[i] == '2') f[i][3] = f[i-1][4];
else if(s[i] == '*') f[i][4] = MOD(f[i-1][2] + MOD(f[i-1][3] + f[i-1][4]));
else{
f[i][0] = MOD(f[i-1][0] + f[i-1][1]);
f[i][1] = f[i-1][4];
f[i][2] = MOD(f[i-1][0] + f[i-1][1]);
f[i][3] = f[i-1][4];
f[i][4] = MOD(f[i-1][2] + MOD(f[i-1][3] + f[i-1][4]));
}
}
printf("%d\n", MOD(f[n][0] + MOD(f[n][1] + f[n][4])));
return 0;
}
爆炸式提醒 : MOD()里只能放a+b 再 +c不能放a+b+c (会爆掉…)!
T2 water 5
这次考试就只有这个5分,最后一题暴力都写错了。
这个5分就是爆搜出来的。
下面讲一下正确做法:
【solution】是这样说的:
一个块的高度就是从这个块走出矩形的所有路径上的最大值的最小值。相邻块连边,权值为两块的较大值,矩形边界的块向“矩形外”连边,权值为 max(高度, 0),做最小生成树。
反正我是看不太明白。
于是再来写一份题解吧:
我们来看POI1999这么一道题:
有这样一块土地,它可以被划分成N×M个正方形小块,每块面积是一平方英寸,第i行第j列的小块可以表示成P(i,j)。这块土地高低不平,每一小块地P(i,j)都有自己的高度H(i,j)(单位是英寸)。
一场倾盆大雨后,这块地由于地势高低不同,许多低洼地方都积存了不少降水。假如你已经知道这块土地的详细信息,你能求出它最多能积存多少立方英寸的降水么?
和此题很像,只是求的内容不一样。一个是求积水体积,一个是要具体每个格子的积水。我们这样来将两题联系起来:
网上有一关于POI1999题解:floodfill算法。
对于矩形最外围的点必然是存不住水的,那我们可以从边界开始扩展。考虑什么样的地方能够积水,一定是他周围的格子都比他高,而最大的存水量,一定是根据他周围最低的格子确定的。
所以我们每次从队列中选取积水量最小的点,用它积水的高度更新它周围的高度,因为每次都是从小的开始更新,所以格子第一次被更新,一定是被他周围最低的格子更新,那么这一定是这个格子的积水高度。所以我们可以保证每个点只遍历一次,时间复杂度O(n*m*log(nm)).
而这个无法处理到边界上是负数的情况,所以water这道题特地强调外圈高度为0,也就是说我们将n*m都向外扩展一圈为0的高度,于是就愉快的把两题变成了一题。如果还是不懂我在说什么,看代码或许是一个更好的选择,简洁易懂。
也就是外圈设一堆0,跑一遍dijkstra。所以又回到了solution的描述。
#include<bits/stdc++.h>
using namespace std;
const int N = 310;
const int oo = 0x7f7f7f7f;
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
int n, m;
struct node{
int x, y, d;
node(int X, int Y, int D):x(X), y(Y), d(D) {}
bool operator < (const node& A)const{
return d > A.d;
}
};
bool vis[N][N];
int h[N][N], dis[N][N];
priority_queue<node> q;
void solve(){
while(!q.empty()){
node o = q.top(); q.pop();
if(vis[o.x][o.y]) continue;
vis[o.x][o.y] = true;
for(int i = 0 ; i <= 3; i++){
int u = o.x+dx[i], v = o.y+dy[i];
if(u <= 0 || v <= 0 || u > n || v > m || vis[u][v]) continue;
if(dis[u][v] < max(o.d, h[u][v])){
dis[u][v] = max(o.d, h[u][v]);
q.push(node(u, v, dis[u][v]));
}
}
}
}
int main(){
freopen("water.in", "r", stdin);
freopen("water.out","w",stdout);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
scanf("%d", &h[i][j]);
dis[i][j] = -oo;
}
}
for(int i = 1; i <= n; i++) q.push(node(i, 0, 0)), q.push(node(i, m+1, 0));
for(int i = 1; i <= m; i++) q.push(node(0, i, 0)), q.push(node(n+1, i, 0));
solve();
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
printf("%d ", dis[i][j] - h[i][j]);
}
printf("\n");
}
return 0;
}
T3 gcd
还没研究这道题,不知道还有没有时间来写这个。
最近太忙了,国庆集训搞得连博客都是半夜十二点写的,身体要紧,洗澡睡觉了。
之后还要记得抽时间整理最近几天讲课的有用的内容。