第二章 练习 Part I
先刷POJ上的
POJ 1964 City Game
这个题就是求在R和F组成的矩阵中,由F构成的最大面积的矩阵为多少。感觉这个题非常的有意思,这是个二维地图里面找最大矩形!我想到了一个前缀和的方法,要两次遍历每一种(m,n)对的话,需要
O
(
n
4
)
O(n^4)
O(n4)的时间复杂度
先想一下朴素的算法即暴力算法,遍历每个点,把每个点当做矩形的左下方的点 时间复杂度为
O
(
n
4
)
O(n^4)
O(n4)
有点难受 想不出来 ,不想去找答案,我感觉找答案找下去 我提升的只是经验,没有提升水平。
搜了一下解析
https://blog.csdn.net/A_Bright_CH/article/details/81630908?utm_source=distribute.pc_relevant.none-task
这个题 用的是单调栈,我还是没懂 单调栈是狩猎范围是怎样的,单调栈的特点是怎样的
博文里面的思想是 将每行作为底,从这行的每个元素能到达的最大高度为相应的高度 然后化作POJ2259的问题,单调栈来解决,感觉有思路了,自己写一下(写之前 我觉得这个算法不是很快,这个时间复杂度为
O
(
m
n
)
O(mn)
O(mn),这已经是最快了,计算一个矩形是否合法就需要这么长的时间)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
#include<set>
#include<utility>
using namespace std;
typedef long long ll;
const int N = 1004;
int h[N][N],st[N],w[N];
char s[3];
int main()
{
int k,m,n;
scanf("%d",&k);
for(int i=1;i<=k;i++){
scanf("%d%d",&m,&n);
memset(h,0,sizeof(h));
for(int u=1;u<=m;u++)
for(int v=1;v<=n;v++){
scanf("%s",s);
if(s[0] == 'R')
h[u][v] = 0;
else h[u][v] = h[u-1][v] + 1;
}
int mx = 0;
for(int u=1;u<=m;u++){
int top = 0;
h[u][n+1] = 0;
st[++top] = h[u][1];
w[top] = 1;
for(int v=2;v<=n+1;v++){
int ans = 0;
if(st[top] < h[u][v]){
st[++top] = h[u][v];
w[top] = 1;
}
else if(st[top] == h[u][v])
w[top]++;
else{
int cnt = 0;
while(top > 0 && h[u][v] < st[top]){
cnt += w[top];
ans = max(st[top]*cnt,ans);
top--;
}
if(top > 0 && h[u][v] == st[top])
w[top] += cnt + 1;
else{
w[top] += cnt;
s[++top] = h[u][v];
w[top] = 1;
}
}
mx = max(ans,mx);
}
}
printf("%d\n",mx*3);
}
return 0;
}
检查发现 s [ + + t o p ] = h [ u ] [ v ] s[++top] = h[u][v] s[++top]=h[u][v]这里是有问题的 s不是栈啊 应该是st,改了以后再加上 m = = 0 或 n = = 0 m == 0 或 n == 0 m==0或n==0 的case 就AC了 4040K 407MS AC 代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
#include<set>
#include<utility>
using namespace std;
typedef long long ll;
const int N = 1004;
int h[N][N],st[N],w[N];
char s[3];
int main()
{
int k,m,n;
scanf("%d",&k);
for(int i=1;i<=k;i++){
scanf("%d%d",&m,&n);
if(m == 0 || n == 0) {printf("%d\n",0);continue;}
memset(h,0,sizeof(h));
for(int u=1;u<=m;u++)
for(int v=1;v<=n;v++){
scanf("%s",s);
if(s[0] == 'R')
h[u][v] = 0;
else h[u][v] = h[u-1][v] + 1;
}
int mx = 0;
for(int u=1;u<=m;u++){
int top = 0;
h[u][n+1] = 0;
st[++top] = h[u][1];
w[top] = 1;
for(int v=2;v<=n+1;v++){
int ans = 0;
if(st[top] < h[u][v]){
st[++top] = h[u][v];
w[top] = 1;
}
else{
int cnt = 0;
while(top > 0 && h[u][v] < st[top]){
cnt += w[top];
ans = max(st[top]*cnt,ans);
top--;
}
if(top > 0 && h[u][v] == st[top])
w[top] += cnt + 1;
else{
w[top] += (top == 0 ? 0 : cnt);//虽然AC了,但是这个处理是有问题的
st[++top] = h[u][v];
w[top] = 1;
}
/*
else里面的代码应该改成
else{
st[++top] = h[u][v];
w[top] = cnt + 1;
}
*/
}
mx = max(ans,mx);
}
}
printf("%d\n",mx*3);
}
return 0;
}
单调栈在巩固一下
POJ 2796 Feel Good
这题用暴力的话 前缀和 加上区间最小值,遍历所有对偶
(
i
,
j
)
(i,j)
(i,j),求得最大值,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)
很明显这题有更快的方法,感觉这题很像POJ2559的那道题
写到这里我发现 我上一题的单调栈写错了!!! 对于之前的写法 栈弹出的时候 中间的宽度是不是加在了新加入的元素,那么当栈为空时 中间弹出的宽度就会被忽略掉!!!
做了以后发现 这个题和POJ2559是有区别的,如果是2559 这题最大值应该为12 而非60
类比一下POJ2559的分析思路,首先假设这个数组是严格单调递增的,那么最大值就是
m
a
x
(
a
[
i
]
∗
s
u
m
(
i
,
n
)
)
max(a[i] * sum(i,n))
max(a[i]∗sum(i,n)),
s
u
m
(
i
,
n
)
sum(i,n)
sum(i,n)是
a
[
i
]
,
a
[
i
+
1
]
,
.
.
.
,
a
[
n
]
a[i],a[i+1],...,a[n]
a[i],a[i+1],...,a[n]的和,i的取值为1,2,…,n
用单调栈 写出代码:示例通过了 但是是Wrong Answer!
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<sstream>
#include<climits>
#include<set>
#include<utility>
using namespace std;
typedef long long ll;
const int N = 100004;
ll a[N],sum[N],st[N],top,w[N];
pair<int,int> p,m;
int main()
{
int n;
ll mx = 0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",a+i);
sum[i] = a[i] + sum[i-1];
}
st[++top] = a[1];
w[top] = 1;
for(int k=2;k<=n+1;k++){
ll temp = 0;
if(st[top] < a[k]){
st[++top] = a[k];
w[top] = 1;
}
else{
ll cnt = 0;
while(top > 0 && st[top] > a[k]){
cnt += w[top];
temp = max(temp,(sum[k-1] - sum[(int)(k-cnt-1)]) * st[top]);
p.second = k - 1;
p.first = k - cnt;
top--;
}
if(top > 0 && st[top] == a[k])
w[top] += cnt + 1;
else{
st[++top] = a[k];
w[top] = cnt + 1;
}
}
if(temp > mx){
mx = temp;
m.first = p.first;
m.second = p.second;
}
}
printf("%lld\n",mx);
printf("%d %d\n",m.first,m.second);
return 0;
}
肉眼debug 发现两个错误 1,p应该是记录这个循环当中的最大值的索引,所以应该是
if(temp < (sum[k-1] - sum[(int)(k-cnt-1)]) * st[top]){
p.second = k - 1;
p.first = k - cnt;
temp = (sum[k-1] - sum[(int)(k-cnt-1)]) * st[top];
}
当数组里面全是0 我的代码会有问题m不会得到赋值,这个时候应该设置一个默认值m.first = m.second = 1;
改了以后AC 3216K 860MS
康了一下别人的代码 感觉最后在a[n+1]的位置 设置值为-1比较好!
POJ 1193 内存分配
这个题在POJ也算比较少见的,中文描述的题目。
我的思路是用一个数组链表来记录所有的内存块,无论是已分配的还是未分配的,然后用两个指针链分别表示已分配的内存块的链表和未分配的内存块的链表。
主要的问题就是当已分配的块释放空间后,在其相邻的块都是已分配块的情况下,怎么找到上一个未分配块和下一个未分配块的位置
由于这个内存的分配特点是,一个未分配块的相邻两个块一定是已分配的(空闲块在相邻位置就会被合并),如果我记录每个已分配块的下一个和上一个未分配块的位置,那么每回进行分配和释放操作的时候就会线性遍历周围相连的块。
这个内存结构还有一个特点:两个内存地址不相邻的逻辑上相邻的分配块中间一定有一个未分配块,光盘里的代码就是用的这一个思想:记录每个已分配的块,若每次要分配,就在已分配的块中的间隙中找可以被分配出去的块!!! 我根据这个思想 写的代码:
这个代码是Wrong Answer DEBUG DEBUG
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<utility>
#include<climits>
using namespace std;
const int N = 10004;
struct DBlock{//distributed block
int sm,len,st,rt,prv,nxt;
}dblist[N],x,q[N];
int tot,l=1,r=0,head,n,tail,w = INT_MAX,time;
bool alloc_block(int t){
if(head == 0 || dblist[head].sm >= x.len){
dblist[++tot] = x;
dblist[tot].sm = 0;
dblist[tot].st = t;
dblist[tot].nxt = head;
dblist[tot].prv = 0;
if(head) dblist[head].prv = tot;
head = tot;
if(tail == 0) tail = tot;
return 1;
}
else{
for(int i=dblist[head].nxt;i;i=dblist[i].nxt){
if(dblist[i].sm - (dblist[dblist[i].prv].sm + dblist[dblist[i].prv].len) >= x.len){
dblist[++tot] = x;
dblist[tot].st = t;
dblist[tot].sm = dblist[dblist[i].prv].sm + dblist[dblist[i].prv].len;
dblist[tot].nxt = i;
dblist[tot].prv = dblist[i].prv;
dblist[dblist[i].prv].nxt = tot;
dblist[i].prv = tot;
return 1;
}
}
}
if(n - (dblist[tail].sm + dblist[tail].len) >= x.len){
dblist[++tot] = x;
dblist[tot].st = t;
dblist[tot].sm = dblist[tail].sm + dblist[tail].len;
dblist[tot].nxt = 0;
dblist[tot].prv = tail;
dblist[tail].nxt = tot;
tail = tot;
}
return 0;
}
void free_block(){
int nw = INT_MAX;
for(int i=head;i;i = dblist[i].nxt){
if(dblist[i].st + dblist[i].rt == w){
if(dblist[i].prv)
dblist[dblist[i].prv].nxt = dblist[i].nxt;
else head = dblist[i].nxt;
if(dblist[i].nxt)
dblist[dblist[i].nxt].prv = dblist[i].prv;
else tail = dblist[i].prv;
i = dblist[i].prv;
}
else nw = min(nw,dblist[i].st + dblist[i].rt);
}
while(l <= r){
x = q[l];
if(alloc_block(w)){
nw = min(nw,q[l].st + q[l].rt);
l++;
}
else break;
}
w = nw;
}
void handle(int start,int length,int run_time){
while(start >= w) free_block();
x.st = start;
x.len = length;
x.rt = run_time;
if(alloc_block(start)) w = min(w,x.st + x.rt);
else q[++r] = x;
}
int main()
{
int t,m,p;
scanf("%d",&n);
while(scanf("%d%d%d",&t,&m,&p) && (t || m || p))
handle(t,m,p);
while(l <= r)
free_block();
time = w;
for(int i=head;i;i = dblist[i].nxt)
time = max(time,dblist[i].st + dblist[i].rt);
printf("%d\n%d\n",time,r);
return 0;
}
示例数据 我的输出是16 和 4 明显不对
看了半天 发现alloc_block函数里面最后的if里面没有return 1;
加了return 1;示例是对的 但是还是Wrong Answer!!!
再检查一下 发现free_block() 里面的 i = dblist[i].prv 这句很可疑
改成了:
if(dblist[i].prv) i = dblist[i].prv;
else i = head; 还是错 肉眼debug以后
nw = min(nw,q[l].st + q[l].rt); nw应该是w + q[l].rt 比较合适
还是Wrong Answer!!!!!!!!!!!
今天一天就搞这一道题 到nm晚上9点还是没想出来哪里错了 你能感受我的崩溃吗????????
Wrong Answer的当前版本:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<utility>
#include<climits>
#include<cassert>
using namespace std;
const int N = 10004;
struct DBlock{//distributed block
int sm,len,st,rt,prv,nxt;//sm是分配块的起始地址,len是块的长度,st是这个进程开始运行的时间,rt是进程运行的时间,prv和nxt是链表的前后项在dblist的索引位置
}dblist[N],x,q[N];//用静态数组dblist模拟链表 q是队列
int tot,l=1,r=0,head,n,tail,w = INT_MAX,time,cnt = 0;
//cnt用不到 l,r标识队列状态,head指向链表的头,tail指向链表的尾 链表为空时head和tail均为0
//tot是扩展新位置用的,w表示正在运行的所有进程中 最先运行完的进程结束时间
bool alloc_block(int t){//时间t的时候分配一个块给进程运行
if(head == 0 || dblist[head].sm >= x.len){//当链表为空或者第一块分配块前面有足够空间可以放得下分配块
dblist[++tot] = x;//开辟新空间放x
dblist[tot].sm = 0;//新空间的起始位置为0
dblist[tot].st = t;
dblist[tot].nxt = head;//进程是插入到链表的头部 更新进程的后一个进程的位置head
dblist[tot].prv = 0;//进程的前驱应该为空
if(head) dblist[head].prv = tot;//插入之前链表不为空,更新head位置的前驱
head = tot;//head指向新的链表首
if(tail == 0) tail = tot;//之前链表为空的话 更新tail指向的位置
return 1;
}
else{//不为空,而且第一块分配块的前面空闲的部分不能满足需求
for(int i=dblist[head].nxt;i;i=dblist[i].nxt){//遍历所有的分配块
if(dblist[i].sm - (dblist[dblist[i].prv].sm + dblist[dblist[i].prv].len) >= x.len){//找到一个分配块的前面的空闲位置足够放下
dblist[++tot] = x;//开辟新空间放分配块
dblist[tot].st = t;
dblist[tot].sm = dblist[dblist[i].prv].sm + dblist[dblist[i].prv].len;//空间的起始位置就为这个式子
dblist[tot].nxt = i;//这个结点是插入到i和dblist[i].prv的中间 结点的nxt为i
dblist[tot].prv = dblist[i].prv;//结点的prv就为dblist[i].prv
dblist[dblist[i].prv].nxt = tot;
dblist[i].prv = tot;
return 1;//这个情况head和tail 都不用更新
}
}
}
if(n - (dblist[tail].sm + dblist[tail].len) >= x.len){//最后一块分配块的后面的位置可以放下请求
dblist[++tot] = x;
dblist[tot].st = t;
dblist[tot].sm = dblist[tail].sm + dblist[tail].len;
dblist[tot].nxt = 0;//因为放在最后 其nxt为0
dblist[tot].prv = tail;//新的块的前驱 就为原链表中的tail结点
dblist[tail].nxt = tot;//原来的tail结点的后继就为tot的位置
tail = tot;//更新tail的位置
return 1;
}
return 0;
}
void free_block(){//释放正在运行的进程 其结束时间为w
int nw = INT_MAX;
for(int i=head;i;i = dblist[i].nxt){//遍历所有进程
if(dblist[i].st + dblist[i].rt == w){//找到一个进程的结束时间为w 就把它从链表中删掉
if(dblist[i].prv)//如果删的不是头结点
dblist[dblist[i].prv].nxt = dblist[i].nxt;//前驱的后继就指向删除结点的后继
else{
head = dblist[i].nxt;//头结点就指向其后继
if(head) dblist[head].prv = 0;//如果新头结点不为空,更新新头结点的prv
}
if(dblist[i].nxt)//如果删的不是尾结点
dblist[dblist[i].nxt].prv = dblist[i].prv;//后继的前驱指向删除结点的前驱
else{
tail = dblist[i].prv;//tail就之前其前驱
if(tail) dblist[tail].nxt = 0;//如果新尾结点不为空,更新新尾结点的nxt
}
if(dblist[i].prv) i = dblist[i].prv;//原来删除的不是头结点,回溯到前一个结点
else i = head;//不然从head开始
if(i == 0) break;//此时为0 就时间break 不进行i = dblist[0].nxt的操作
}
else nw = min(nw,dblist[i].st + dblist[i].rt);//求剩余的所有进程的最早结束时间
}
while(l <= r){//队列不为空
x = q[l];
if(alloc_block(w)){//看能不能分配队首的元素,若能分配
nw = min(nw,w + x.rt);//更新进程链表,注意此时的进程开始运行时间为w
l++;//弹出队首
}
else break;
}
w = nw;
}
void handle(int start,int length,int run_time){
while(start >= w) free_block();//首先检查start时间前面是否有进程结束,有的话就进行删除操作
x.st = start;
x.len = length;
x.rt = run_time;
if(alloc_block(start)){//分配
w = min(w,x.st + x.rt);
}
else q[++r] = x;//分配不了就入队
}
int main()
{
int t,m,p;
scanf("%d",&n);
while(scanf("%d%d%d",&t,&m,&p) && (t || m || p))
handle(t,m,p);
while(l <= r)
free_block();
time = w;
for(int i=head;i;i = dblist[i].nxt)
time = max(time,dblist[i].st + dblist[i].rt);
printf("%d\n%d\n",time,r);
return 0;
}
这题做的我超级绝望!!!!
我放弃这道题了 几个月以后 或者几年以后再来看看!!!!!!!
当天晚上 有大佬帮忙指点,对于POJ 要去找到他的测试数据,找到以后debug就好多了,错误原因是:
free_block中,如果删除的是头结点,由于i = head;这个head是新的未访问过的结点,i = head之后 会进行下一次循环 即会通过i = dblist[i].nxt 这时新的head就被跳过 没有被访问到!!!
完整的AC代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<utility>
#include<climits>
#include<cassert>
using namespace std;
const int N = 10004;
struct DBlock{
DBlock():sm(0),len(0),st(0),rt(0),prv(0),nxt(0){}
DBlock(const DBlock& z){
sm = z.sm;
len = z.len;
st = z.len;
rt = z.rt;
prv = z.prv;
nxt = z.nxt;
}
int sm,len,st,rt,prv,nxt;
}dblist[N],x,q[N];
int tot,l=1,r=0,head,n,tail,w = INT_MAX,masaka;
bool alloc_block(int t){
if(head == 0 || dblist[head].sm >= x.len){
dblist[++tot] = x;
dblist[tot].sm = 0;
dblist[tot].st = t;
dblist[tot].nxt = head;
dblist[tot].prv = 0;
if(head) dblist[head].prv = tot;
head = tot;
if(tail == 0) tail = tot;
return 1;
}
else{
for(int i=dblist[head].nxt;i;i=dblist[i].nxt){
if(dblist[i].sm - (dblist[dblist[i].prv].sm + dblist[dblist[i].prv].len) >= x.len){
dblist[++tot] = x;
dblist[tot].st = t;
dblist[tot].sm = dblist[dblist[i].prv].sm + dblist[dblist[i].prv].len;
dblist[tot].nxt = i;
dblist[tot].prv = dblist[i].prv;
dblist[dblist[i].prv].nxt = tot;
dblist[i].prv = tot;
return 1;
}
}
}
if(n - (dblist[tail].sm + dblist[tail].len) >= x.len){
dblist[++tot] = x;
dblist[tot].st = t;
dblist[tot].sm = dblist[tail].sm + dblist[tail].len;
dblist[tot].nxt = 0;
dblist[tot].prv = tail;
dblist[tail].nxt = tot;
tail = tot;
return 1;
}
return 0;
}
void free_block(){
int nw = INT_MAX;
for(int i=head;i;){
if(dblist[i].st + dblist[i].rt == w){
if(dblist[i].prv)
dblist[dblist[i].prv].nxt = dblist[i].nxt;
else{
head = dblist[i].nxt;
if(head) dblist[head].prv = 0;
}
if(dblist[i].nxt)
dblist[dblist[i].nxt].prv = dblist[i].prv;
else{
tail = dblist[i].prv;
if(tail) dblist[tail].nxt = 0;
}
if(dblist[i].prv) {
i = dblist[i].prv;
i = dblist[i].nxt;
}
else {
i = head;
if(i == 0) break;
}
}
else {
nw = min(nw,dblist[i].st + dblist[i].rt);
i = dblist[i].nxt;
}
}
while(l <= r){
x = q[l];
if(alloc_block(w)){
nw = min(nw,w + x.rt);
l++;
}
else break;
}
w = nw;
}
void handle(int start,int length,int run_masaka){
while(start >= w) free_block();
x.st = start;
x.len = length;
x.rt = run_masaka;
if(alloc_block(start)){
w = min(w,x.st + x.rt);
}
else q[++r] = x;
}
int main()
{
int t,m,p;
scanf("%d",&n);
while(scanf("%d%d%d",&t,&m,&p) && (t || m || p))
handle(t,m,p);
while(l <= r)
free_block();
for(int i=head;i;i = dblist[i].nxt)
masaka = max(masaka,dblist[i].st + dblist[i].rt);
printf("%d\n%d\n",masaka,r);
return 0;
}
584K 79MS 比光盘的代码要快
masaka是为了不适用time这个变量,怕出问题 所以改了!!!