题目链接:
CODEVS 2495 水叮当的舞步
BZOJ 3041: 水叮当的舞步


这是道IDA*,迭代加深+估价剪枝;

首先考虑估价函数,一般情况下我们把易于计算的操作次数的下限作为A*的估价函数,易知这道题里每次有效操作只会向外拓宽一个颜色层,那么估价函数就可以确定为剩下未被拓展的颜色层数。这个层数就是答案,这个数的下限就是剩余未探索的颜色数,道理显然。我们把一个难以确定的准确函数用一个相对严格但便于计算的估价函数替代去进行搜索,这就是A*算法的精要。一般情况下,这个函数可以设为错位个体的个数,但要相对严格以保证正确性。

其次,这道题卡常;裸的IDA*剪枝在这道题CODEVS数据上只能跑到60%,所以要考虑优化算法过程。这种题一般最先想到的都是Flood Fill算法,每一次更新状态都要

    \[O(n^2)\]

的时间复杂度确定边界情况,去更新到下一个状态。但由于更新使颜色发生变化的点数量实在有限,所以我们考虑在更新的时候直接使用父亲状态的Flood Fill值,这样可以在状态更新时优化到大概

    \[O(n)\]

的极优复杂度。

事实上,单纯地关注代码,我们会发现这个过程也需要遍历整个地图,但是它和Flood Fill的最大差别,在于它的更新操作太少,在实际运行上优势巨大。我们也可以用链表把这个过程优化到严格

    \[O(n)\]

,但限于码力有限,这个想法我没有实现,写在这里共大家交流。
当然,这里复杂度的分析大多是我的主观臆断,若有纰漏希望指出。

最后,在回溯操作的时候,直接暴力将父亲状态的Flood Fill值回滚就可以了。至于代码为什么要有结构体写,我也不知道qwq


AC代码:

#include<bits/stdc++.h>
#define INF 1000000000
#define fair(x) (x>=1&&x<=n)
using namespace std;
int n,maxstep=0,ans=INF;
struct type{
    int tar[10];
    public: int & operator [] (const int &x) {return tar[x];}
};
struct node{
    type tar[10];
    public: type & operator [] (const int &x) {return tar[x];}
}a,b,ZERO;
int xt[4]={0,0,1,-1};
int yt[4]={1,-1,0,0};
inline int judge()
{
    register int cnt=0,col[10];
    for(register int i=0;i<10;i++)col[i]=0;
    for(register int i=1;i<=n;i++)
    for(register int j=1;j<=n;j++)
    if(b[i][j]!=1)col[a[i][j]]=1;
    for(register int i=0;i<10;i++)if(col[i])cnt++;
    return cnt;
}
void explore(int x,int y,int col)
{
    b[x][y]=1;
    for(register int i=0;i<4;i++)
    {
        register int tx=x+xt[i],ty=y+yt[i];
        if(fair(tx)&&fair(ty)&&b[tx][ty]!=1)
        {
            b[tx][ty]=2;
            if(fair(tx)&&fair(ty)&&a[tx][ty]==col)
                explore(tx,ty,col);
        }
    }
}
int fill(int col)
{
    int cnt=0;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
        if(b[i][j]==2&&a[i][j]==col)
        {
            cnt++;
            explore(i,j,col);
        }
    return cnt;
}
void dfs(int step)
{
    register int now=judge();
    if(!now){ans=step;return;}
    if(step+now>maxstep)return;
    node t=b;
    for(register int i=0;i<=5;i++)
    {
        if(ans!=INF)return;
        if(!fill(i)){continue;}
        dfs(step+1);
        b=t;
    }
}
int main()
{
    while(scanf("%d",&n)==1&&n!=0)
    {
        maxstep=0,ans=INF;b=ZERO;
        for(register int i=1;i<=n;i++)
        for(register int j=1;j<=n;j++)
        scanf("%d",&a[i][j]);
        explore(1,1,a[1][1]);
        while(ans==INF)
        {
            dfs(0);
            maxstep++;
        }
        printf("%d\n",ans);
    }
}


血まみれからの方がさ,勝つ時にはカッコイイだろう.