[清橙A1363][THUSC 2012]水位(并查集+高精度压位)

题目链接

http://www.tsinsen.com/A1363

题目大意

给一个 n×n 大小的、每个格子具有高度的棋盘(姑且看作是个沙盘模型吧)灌水,求这个棋盘灌水后的状态种数。

思路

这个题实在太神了,我跪了半个上午+一个下午才AC,跪跪跪
首先我们可以想到,可能由于某些格子组成的“墙”的阻挡,水被分成了若干个小水池,这些水池之间是互不影响的,一个水池水位高一点、低一点不会另一个水池造成任何影响,除非水位上涨漫过了那些墙,这样几个水池就会连在一起。
那么我们可以模拟水位上涨的过程,假设最开始水位高度为最高的那个格子的高度,并且水从最高的那个格子像泉水一样源源不断地流出来。先对 n2 个格子按照第一关键字高度,第二关键字x坐标,第三关键字y坐标(均为升序)排序,然后从小到大来遍历每个格子,对于每个格子 x 而言,如果其相邻的格子x比它高度低,并且两个格子不在同一个联通块,那么就合并两个格子所在的联通块,相当于模拟水漫过了格子,两个水池合并成一个水池。其间 x 的水位的方案数就增加 |hxhx| ,表示 x 的水位增加的幅度在 [1,hxhx] 内,两个水池是不会合并的,仍然是同一状态。合并以后,新的水池的水位方案数是之前两个水池的水位方案数的乘积(乘法计数)。

后记
其实这个题目思路并不难,然而丧病的是这个题需要写个高精度,而且最终答案的数字特别大,这意味着需要写个压位,然后我因为高精度刚开始没压位,一直没发现为什么WA,然后补上压位( 107 压位)后,没有注意到乘法过程中会爆掉int,高精度中每一位的保存方式改成long long int后,我把别人以前AC掉的pascal标称交上去TLE 2发,一直不知道怎么回事,然后我猜想可能是高精度的长度开小了,果真如此(天杀的hwd改数据了!!!)。然后我也调整了高精度的数字长度范围,交上去RE 2发,发现可能是MLE了(天杀的THUSC卡内存!!!),没办法,只好试图继续调整范围,最后以244MB的内存卡过了此题。感觉自己真的好弱啊,居然会如此无脑地WA+TLE+RE 11发才AC这题
感谢http://www.cnblogs.com/Randolph87/p/3769796.html提供此题的pascal标程供我对拍查错

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 103
#define LEN 3000
#define calc(i,j) (((i)-1)*n+(j))
#define BASE 10000000

using namespace std;

typedef long long int LL;

int n,m;

struct Hugeint
{
    LL num[LEN];
    int len;
    void print()
    {
        printf("%lld",num[len]);
        for(int i=len-1;i>=1;i--) printf("%07lld",num[i]);
    }
}ans[MAXN*MAXN],a,b,t;

struct Point
{
    int x,y,num;
}points[MAXN*MAXN];

bool inMap(int x,int y)
{
    if(x<1||x>n||y<1||y>n) return false;
    return true;
}

Hugeint operator+(Hugeint a,LL b)
{
    a.num[1]+=b;
    for(int i=1;i<=a.len;i++)
    {
        a.num[i+1]+=a.num[i]/BASE;
        a.num[i]%=BASE;
    }
    while(a.num[a.len+1])
    {
        a.len++;
        a.num[a.len+1]+=a.num[a.len]/BASE;
        a.num[a.len]%=BASE;
    }
    return a;
}

Hugeint operator*(Hugeint a,Hugeint b)
{
    Hugeint c;
    memset(c.num,0,sizeof(c.num));
    c.len=a.len+b.len-1;
    for(int i=1;i<=a.len;i++)
        for(int j=1;j<=b.len;j++)
            c.num[i+j-1]+=a.num[i]*b.num[j];
    for(int i=1;i<=c.len-1;i++)
    {
        c.num[i+1]+=c.num[i]/BASE;
        c.num[i]%=BASE;
    }
    while(c.num[c.len]>=BASE)
    {
        c.num[c.len+1]=c.num[c.len]/BASE;
        c.num[c.len]%=BASE;
        c.len++;
    }
    return c;
}

int map[MAXN][MAXN];

int f[MAXN*MAXN];
int h[MAXN*MAXN]; //h[i]=联通块i的水位高度

int findSet(int x)
{
    if(f[x]==x) return f[x];
    return f[x]=findSet(f[x]);
}

int xx[]={1,-1,0,0},yy[]={0,0,1,-1};

bool cmp(Point a,Point b)
{
    if(map[a.x][a.y]==map[b.x][b.y])
    {
        return a.num<b.num;
    }
    return map[a.x][a.y]<map[b.x][b.y];
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&map[i][j]);
            h[calc(i,j)]=map[i][j];
            points[calc(i,j)].x=i,points[calc(i,j)].y=j,points[calc(i,j)].num=calc(i,j);
        }
    for(int i=1;i<=n*n;i++) //初始化第i号格子
    {
        f[i]=i;
        ans[i].len=ans[i].num[1]=1;
    }
    sort(points+1,points+n*n+1,cmp); //!!!!!!
    for(int i=1;i<=n*n;i++) //枚举第i号格子
        for(int dir=0;dir<4;dir++)
        {
            int nowx=points[i].x,nowy=points[i].y;
            int nextx=nowx+xx[dir],nexty=nowy+yy[dir];
            if(!inMap(nextx,nexty)) continue; //!!!!!!!
            int roota=findSet(calc(nowx,nowy)),rootb=findSet(calc(nextx,nexty)); //roota是(nowx,nowy)所属联通块,rootb是(nextx,nexty)所属联通块
            if(h[roota]>=h[rootb]&&roota!=rootb) //a联通块水位比b联通块水位高,而且它们并不是联通的
            {
                ans[rootb]=ans[rootb]+(h[roota]-h[rootb]); //那么b联通块的方案数要加上两个联通块之间的水位差
                ans[roota]=ans[roota]*ans[rootb]; //两个联通块联通后,新联通块的方案数是两个老联通块方案数的乘积(乘法原理)
                f[rootb]=roota; //并查集合并两个联通块
            }
        }
    ans[findSet(1)]=ans[findSet(1)]+(m-map[points[n*n].x][points[n*n].y]);//所有的联通块都在一起,也就是整个地图上没有干旱的高地了,那么要加上剩余的方案(一直灌水到水位上升至m)
    ans[findSet(1)].print();
    printf("\n");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值