1. 程式人生 > >【BZOJ2576】[JSOI2011]序的計數 (動態規劃)

【BZOJ2576】[JSOI2011]序的計數 (動態規劃)

urn getchar 目標 for getch 能夠 code als 動態規劃

【BZOJ2576】[JSOI2011]序的計數 (動態規劃)

題面

BZOJ

題解

首先構建一個新的虛擬節點連接所有目標節點,強行將其作為第一個被訪問的節點,這樣子就解決了圖不連通的問題。
除了目標節點外,所有其他點都可以縮成一個節點。
這樣子的圖實際上只有\(k+2\)個節點,\(k+1\)個目標節點。
預處理\(G[S][u]\)表示已經在\(dfs\)序中出現過的點的集合為\(S\),當前在點\(u\)能夠訪問到的點。
\(f[S][u]\)表示當前在點\(u\),已經確定\(dfs\)序的集合為\(S\)\(dfs\)序的方案數。
註意如果一個點和不合法的點有連邊,那麽這個點不能回朔。

轉移的時候枚舉一個\(u\)的相鄰點,記憶化搜索即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
#define MAX 120
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,m,K,id[MAX],S[20];
bool g[MAX][MAX],M[20][20];
ll f[1<<19][20];
int G[1<<19][20];
int dfs(int u,int S)
{
    if(~G[S][u])return G[S][u];
    int ret=1<<u;
    for(int i=0;i<K;++i)
        if(M[u][i]&&!(S&(1<<i)))ret|=dfs(i,S|(1<<i));
    return G[S][u]=ret;
}
ll Solve(int u,int S)
{
    if(~f[S][u])return f[S][u];
    if(G[S][u]==1<<u)return S==(1<<K)-1||!M[u][K];
    ll ret=0;
    for(int i=0;i<K;++i)
        if(M[u][i]&&!(S&(1<<i)))
            ret+=Solve(i,S|(1<<i))*Solve(u,S|G[S|(1<<i)][i]);
    return f[S][u]=ret;
}
int main()
{
    n=read();m=read();K=read();K+=1;
    for(int i=1,u,v;i<=m;++i)u=read(),v=read(),g[u][v]=g[v][u]=1;
    for(int i=1;i<=n;++i)id[i]=K;
    for(int i=0;i<K-1;++i)S[i]=read(),id[S[i]]=i,M[i][K-1]=M[K-1][i]=1;
    for(int i=0;i<=K;++i)
        for(int j=1;j<=n;++j)M[i][id[j]]|=g[S[i]][j];
    memset(G,-1,sizeof(G));memset(f,-1,sizeof(f));  
    for(int i=0;i<(1<<K);++i)
        for(int j=0;j<K;++j)
            if(i&(1<<j))dfs(j,i);
    printf("%lld\n",Solve(K-1,1<<(K-1)));
    return 0;
}

【BZOJ2576】[JSOI2011]序的計數 (動態規劃)