POJ1009

转载自:POJ1009---------无奈了

想刷一点水题,就随便看了看,发现1009,30%。嗯于是就它了。结果这道题,无奈了我很久。

题目:http://poj.org/problem?id=1009

首先,暴力必挂,这是题目的善意提醒。

于是,一直在想不暴力的各种判断计算方法,关于各种跳跃移动,后来都无奈想用STL。原谅我的蒟蒻。

再然后就思维混乱了。于是看网上各位大神的解题报告。很神奇的一个搞法。瞬间被震惊。发现了一个道理,有时候从这个角度搞不通的时候,换一个更直接的角度往往一下就搞通了。是的。

首先可以看出来,这个最终的答案中,肯定会有某些连续段,答案是不变的,这些连续段又和原图有关。我自己的思路就是,先找出在原图的某一连续段中,答案值可能改变的几个像素(做标记),再一次次跳跃到这几个像素中计算它们的答案值,但是问题就是,怎么样的像素才是需要计算的像素。

如图的一个map:



第一个x显然是需要标记的。然后,可以直接跳跃到第4个x,因为此时它的周围多了一个z,答案可能改变,而第二、三个x显然答案和第一个是一样的。再然后,y的第一个也肯定要标记,因为主体变了。然后到了坐标为(2,3)的y,它需要标记是因为它的周围多了一个m。于是以此类推完成。虽然这个搞法看似可以,但这么多的细节要考虑,况且格子数是10^9,以我的水平注定挂。该怎么办呢。在看了各位大神的题解之后,发现其实我只要把需要标记的格子标记出来,并且把每一连续段的分段画出来,就可以发现很神奇的东西:


紫色标注的都是要标记的格子,红色边框的代表这一个连续段的起始格。我们可以发现,每个连续段的起始格,都是要标记的格子,同时,每个要标记的格子,都是一个连续段起始格的周围8个格子中的一个。所以,改进的搞法就很清楚了:只需要一个个枚举每个连续段的起始格,并计算它和它四周8个格子的答案值,最后统计答案的时候按照位置先后排序,答案中相同的连续段就合并。因为最多只有1000个连续段,所以不管是时间还是空间都不会超。

有些具体的东西程序里注释:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define size 1005
using namespace std;
struct pix
{
    int pos;    //表示答案中这个点的位置
    int code;   //这个点上的答案值
}outmap[size*8];

int inmap[size][2];//inmap[][0]表示这个连续段的数值,inmap[][1]表示这个连续段的长度
int width,cntp,tot;

int cmp(pix x,pix y)//排序比较函数,最后以pos升序排序
{
    return x.pos<y.pos;
}
inline int abs(int x)//cmath库里其实有abs函数,这里无聊写了一个
{
    return x>0?x:-x;
}

int getnum(int pos)//返回原图中pos位置上的数值
{
    int p=0,i=0;
    while (p<pos)
        p+=inmap[i++][1];
       
    return inmap[i-1][0];
}
int getcode(int pos)//计算pos位置上的答案
{
    int num=getnum(pos),ret=0;
   
    int row=(pos-1)/width;//关于row和col的原理主程序中有
    int col=(pos-1)%width;
   
    for (int i=row-1;i<=row+1;i++)
        for (int j=col-1;j<=col+1;j++)
        {
            int tpos=i*width+j;
            if (i<0||j<0||j>=width||tpos>=tot || tpos==pos-1)
                continue;//这里计算差的绝对值时要排除pos自己
               
            int tmp=getnum(tpos+1);
            if (abs(tmp-num)>ret)ret=abs(tmp-num);//更新ret
        }
    return ret;
}
int main()
{
   
   
    while (scanf("%d",&width)&& width>0)
    {
       
       
        int num,len;
        cntp=tot=0;//必须得每次都赋0
        while (scanf("%d%d",&num,&len)&& len>0)
        {
            inmap[cntp][0]=num;
            inmap[cntp++][1]=len;
            tot+=len;//tot是map中像素的个数
        }
        printf("%d\n",width);//按照同样格式输出
       
       
       
        int pos=1,k=0;//pos从1开始标号
        for (int p=0;p<=cntp;p++)//枚举每一个连续段
        {   
           
            int row=(pos-1)/width;
            int col=(pos-1)%width;
           
           
            for (int i=row-1;i<=row+1;i++)
                for (int j=col-1;j<=col+1;j++)
                {
                    int tpos=i*width+j;//这里算出来的tpos其实是tpos的标号减一
                    if (i<0 || j<0 || j>=width || tpos>=tot)
                        continue;//tpos在map的外面了
                       
                    outmap[k].pos=tpos+1;
                    outmap[k++].code=getcode(tpos+1);//答案存入outmap   
                }
               
            pos+=inmap[p][1];//跳跃到下一个连续段的起始格
        }
       
       
        sort(outmap,outmap+k,cmp);
       
       
        pix tmp=outmap[0];
        for (int i=0;i<k;i++)
        {
            if (outmap[i].code==tmp.code) //表明连续,则跳过不输出
                continue;
            printf("%d %d\n",tmp.code,outmap[i].pos-tmp.pos);
            tmp=outmap[i];
        }
        printf("%d %d\n",tmp.code,tot-tmp.pos+1);//最后一部分
        printf("0 0\n");//按照格式输出
    }
    printf("0\n");//格式
    return 0;
}



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值