1.悬线法
luoguP4147玉蟾宫
蓝书上有这种方法的介绍 维护left[i] right[i] up[i]表示当前行第i列能向左/右/上拓展的格子的编号 ,在第一次循环时从上到下,从左到右维护up和left 之后再逆序循环一次维护right
传送门
并不需要像蓝书那样写 递推状态只和上一行有关 直接和01背包一样证明 可以降成一维的
#include
using namespace std;
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A<_B?_A:_B)
inline int read(){
int s=0,f=1;char c=getchar();while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
return s*f;
}
const int maxn = 1001;
int a[maxn][maxn], up[maxn], left[maxn], right[maxn];
int n, m, ans = 0;
/*
每个格子(i,j)对应着一个以第i行为下边界,高度为up(i,j),
左右边界为left(i,j),right(i,j)的矩形
*/
int main(){
n = read(), m = read();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++){
char c = getchar();
while(c != 'F' && c != 'R')
c = getchar();
a[i][j] = c == 'F' ? 0 : 1;
}
for (int i = 1; i <= n; i++){
int lo = 0, ro = m + 1;
for (int j = 1; j <= m; j++){ //维护left和up
if(a[i][j] == 1){
left[j] = up[j] = 0, lo = j;
}else{
left[j] = i == 1 ? lo + 1 : Max(lo + 1, left[j]);
up[j] = i == 1 ? 1 : up[j] + 1;
}
}
for (int j = m; j >= 1; j--) {
if(a[i][j] == 1){
right[j] = m, ro = j;
}else{
right[j] = i == 1 ? ro - 1 : Min(ro - 1, right[j]);
}
ans = Max(ans, (right[j] - left[j] + 1) * up[j]);
}
}
printf("%d\n", ans * 3);
return 0;
}
2.奶牛浴场
王之坤神犇有论文,但是有bug
洛谷题解
这位爷指出来错哪了,我就不再打一遍了。
论文
用上文的算法一的思路算
可以剪枝 在洛谷加了剪枝以后比没加快了一倍
#include
#include
#include
using namespace std;
const int maxn = 5007;
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A<_B?_A:_B)
/*
不能从最左边的那个点开始找 要从地图的左边界开始找
*/
int L, W, n;
struct node{
int x, y;
} p[maxn];
struct cmp{
bool operator()(const node &l,const node &r){
return l.x < r.x || (l.x == r.x && l.y < r.y);
}
};
struct cmp2{
bool operator()(const node &l, const node &r){
return l.y < r.y;
}
};
int tot = 0, ans;
int read(){
char c = getchar();
int s = 0;
while(c > '9' || c < '0')
c = getchar();
while(c >= '0' && c <= '9') {
s = s * 10 + c - '0';
c = getchar();
}
return s;
}
/*极大化思想:算法1:
先枚举极大子矩形的左边界 然后从左到右依次扫描每一个障碍点 并不断修改可行的上下边界
从而枚举出所有以这个定点为左边界的极大子矩形
*/
void solve(){
sort(p + 1, p + tot + 1, cmp2());
for (int i = 1; i <= tot; i++) { //尝试以p[i].y 作为上边界更新答案
for (int j = i + 1, left = 0, right = L, v = W - p[i].y; j <= tot; j++) {
if(p[j].x > right || p[j].x < left)
continue;
if(v * (right - left) <= ans)
break;//最优性剪枝
ans = Max(ans, (p[j].y - p[i].y) * (right - left));
if(p[j].x > p[i].x)
right = Min(p[j].x, right);
else
left = Max(left, p[j].x);
if(left == right)
break;//这之后的矩阵面积均为0了
}
for (int j = i - 1, left = 0, right = L, v = p[i].y; j >= 1; j--) {
if(p[j].x > right || p[j].x < left)
continue;
if(v * (right - left) <= ans)
break;
ans = Max(ans, (p[i].y - p[j].y) * (right - left));
if(p[j].x > p[i].x)
right = p[j].x;
else
left = p[j].x;
if(left == right)
break;//这之后的矩阵面积均为0了
}
}
}
int main(){
L = read(), W = read();
n = read();
p[++tot].x = 0, p[tot].y = 0;
p[++tot].x = 0, p[tot].y = W;
p[++tot].x = L, p[tot].y = 0;
p[++tot].x = L, p[tot].y = W;
for (int i = 1, x, y; i <= n; i++){
x = read(), y = read();
p[++tot].x = x, p[tot].y = y;
}
sort(p + 1, p + tot + 1, cmp());
for (int i = 2; i <= tot; i++) {
//枚举左边界(x)
ans = Max(ans, (p[i].x - p[i - 1].x) * W);
}
solve();
//solve2();
printf("%d", ans);
return 0;
}
3.ZJOI2007 棋盘制作
传送门
悬线法循环的时候就能维护最大正方形的边长了,输入的时候按行编号+列编号把格子里的01异或一下,就变成求全0或全1矩阵了
#include
#include
using namespace std;
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A<_B?_A:_B)
const int maxn = 2001;
int a[maxn][maxn], n, m, rua, ans, ans1, left[maxn], right[maxn], up[maxn];
inline int read(){
int s=0,f=1;char c=getchar();while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
return s*f;
}
int main(){
n = read();
m = read();
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
a[i][j] = read();
if((i+j)&1)
a[i][j] ^= 1; //i+j为奇数的格子01取反 转化问题
}
}
for (int i = 1; i <= n; i++){ //第一次维护的是全0矩阵和全0正方形
int lo = 0, ro = m + 1;
for (int j = 1; j <= m; j++){
if(a[i][j]){
up[j] = left[j] = 0, lo = j;
}else{
up[j] = i == 1 ? 1 : up[j] + 1;
left[j] = i == 1 ? lo + 1 : Max(lo + 1, left[j]);
}
}
for (int j = m; j >= 1; j--) {
if(a[i][j])
right[j] = m + 1, ro = j;
else{
right[j] = i == 1 ? ro - 1 : Min(ro - 1, right[j]);
rua = Min(up[j], right[j] - left[j] + 1);
ans = Max(ans, up[j] * (right[j] - left[j] + 1));
ans1 = Max(ans1, rua * rua);
}
}
}
memset(up, 0, sizeof(up));
memset(left, 0, sizeof(left));
memset(right, 0, sizeof(right));
for (int i = 1; i <= n; i++){ //第二次维护的是全1矩阵和全1正方形
int lo = 0, ro = m + 1;
for (int j = 1; j <= m; j++){
if(a[i][j] == 0){
up[j] = left[j] = 0, lo = j;
}else{
up[j] = i == 1 ? 1 : up[j] + 1;
left[j] = i == 1 ? lo + 1 : Max(lo + 1, left[j]);
}
}
for (int j = m; j >= 1; j--) {
if(a[i][j] == 0)
right[j] = m + 1, ro = j;
else{
right[j] = i == 1 ? ro - 1 : Min(ro - 1, right[j]);
ans = Max(ans, up[j] * (right[j] - left[j] + 1));
rua = Min(up[j], right[j] - left[j] + 1);
ans1 = Max(ans1, rua * rua);
}
}
}
printf("%d\n%d\n", ans1, ans);
return 0;
}