1. 程式人生 > >bzoj 1814: Ural 1519 Formula 1【插頭dp】

bzoj 1814: Ural 1519 Formula 1【插頭dp】

設f[i][j][s]為輪廓線推到格子(i,j),狀態為s的方案數
括號表示一段線的左端和右端,表示成左括號和右括號,狀壓的時候用1和2表示,0表示已經閉合
下面的藍線是黃色格子的輪廓線,dp轉移要把它轉到橙色輪廓線,設已經在狀壓的s中取到兩條邊的狀態記為b1,b2

然後分很多情況討論:
(i,j)是障礙:那就只能什麼都不放的轉移,也就是隻能從b1=0,b2=0轉移到新輪廓線的b1=0,b2=0

if(!a[i][j])
                {
                    if(!b1&&!b2)
                        add(x,v);
                }

b1=0,b2=0:因為不能空,所以只能轉移一個拐角

else if(!b1&&!b2)
                {
                    if(a[i+1][j]&&a[i][j+1])
                        add(x+b[j-1]+2*b[j],v);
                }

b1=0或者b2=0:根據有無障礙判斷能不能轉移,如果(i,j+1),(i+1,j)都沒有障礙的話就有兩種轉移,以b1=0,b2!=0為例:
一種是接上然後拐彎,這樣轉移後的輪廓線括號狀態不變

另一種是接上直著走,轉移後的輪廓線括號狀態b1b2互換


b1!=0,b2=0同理

else if(!b1&&b2)
                {
                    if(a[i][j+1])
                        add(x,v);
                    if(a[i+1][j])
                        add(x-b[j]*b2+b[j-1]*b2,v);
                }
                else if(b1&&!b2)
                {
                    if(a[i][j+1])
                        add(x-b[j-1]*b1+b[j]*b1,v);
                    if(a[i+1][j])
                        add(x,v);
                }

b1=b2=1或2:這樣這兩條線會在(i,j)格子連起來,兩隊括號合成一對,以b1=b2=1為例:

else if(b1==1&&b2==1)
                {
                    for(int t=1,l=j+1;l<=m;l++)
                    {
                        if((x>>(l*2))%4==1)
                            t++;
                        if((x>>(l*2))%4==2)
                            t--;
                        if(!t)
                        {
                            add(x-b[j]-b[j-1]-b[l],v);
                            break;
                        }
                    }
                }
                else if(b1==2&&b2==2)
                {
                    for(int t=1,l=j-2;l>=0;l--)
                    {
                        if((x>>(l*2))%4==1)
                            t--;
                        if((x>>(l*2))%4==2)
                            t++;
                        if(!t)
                        {
                            add(x+b[l]-2*b[j]-2*b[j-1],v);
                            break;
                        }
                    }
                }

b1=2,b2=1:和上面差不多,就是把這兩個括號合併就行了

else if(b1==2&&b2==1)
                    add(x-2*b[j-1]-b[j],v);

b1=1,b2=2:這個只有到最後一個沒障礙的點才能轉移,因為這是把一條線連成一個迴路的最後一步
其實不用轉移,直接加進答案就行了

else if(i==tx&&j==ty)
                    ans+=v;
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=15,mod=299989;
int n,m,la,nw,a[N][N],b[N],c[2],h[300005],tx,ty;
long long ans;
char s[N];
struct qwe
{
    int ne,to[2];
    long long va[2];
}e[300005];
void add(int x,long long v)
{
    int u=x%mod+1;
    for(int i=h[u];i;i=e[i].ne)
        if(e[i].to[nw]==x)
        {
            e[i].va[nw]+=v;
            return;
        }
    e[++c[nw]].ne=h[u];
    e[c[nw]].to[nw]=x;
    e[c[nw]].va[nw]=v;
    h[u]=c[nw];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        for(int j=1;j<=m;j++)
            if(s[j]=='.')
                a[i][j]=1,tx=i,ty=j;
    }
    b[0]=1;
    for(int i=1;i<=12;i++)
        b[i]=b[i-1]<<2;
    c[0]=1,e[1].va[0]=1,e[1].to[0]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c[nw];j++)
            e[j].to[nw]<<=2;
        for(int j=1;j<=m;j++)
        {
            la=nw,nw^=1;
            memset(h,0,sizeof(h));
            c[nw]=0;
            for(int k=1;k<=c[la];k++)
            {
                int x=e[k].to[la],b1=(x>>(j*2-2))%4,b2=(x>>(j*2))%4;
                long long v=e[k].va[la];
                if(!a[i][j])
                {
                    if(!b1&&!b2)
                        add(x,v);
                }
                else if(!b1&&!b2)
                {
                    if(a[i+1][j]&&a[i][j+1])
                        add(x+b[j-1]+2*b[j],v);
                }
                else if(!b1&&b2)
                {
                    if(a[i][j+1])
                        add(x,v);
                    if(a[i+1][j])
                        add(x-b[j]*b2+b[j-1]*b2,v);
                }
                else if(b1&&!b2)
                {
                    if(a[i][j+1])
                        add(x-b[j-1]*b1+b[j]*b1,v);
                    if(a[i+1][j])
                        add(x,v);
                }
                else if(b1==1&&b2==1)
                {
                    for(int t=1,l=j+1;l<=m;l++)
                    {
                        if((x>>(l*2))%4==1)
                            t++;
                        if((x>>(l*2))%4==2)
                            t--;
                        if(!t)
                        {
                            add(x-b[j]-b[j-1]-b[l],v);
                            break;
                        }
                    }
                }
                else if(b1==2&&b2==2)
                {
                    for(int t=1,l=j-2;l>=0;l--)
                    {
                        if((x>>(l*2))%4==1)
                            t--;
                        if((x>>(l*2))%4==2)
                            t++;
                        if(!t)
                        {
                            add(x+b[l]-2*b[j]-2*b[j-1],v);
                            break;
                        }
                    }
                }
                else if(b1==2&&b2==1)
                    add(x-2*b[j-1]-b[j],v);
                else if(i==tx&&j==ty)
                    ans+=v;
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}