F - Tree and Constraints Editorial by Mitarushi


解説と違い、条件を満たす塗り方を直接数え上げます。

「状態」を、その数の\(i\)番目のビットが立っている場合にi番目の制約が満たされていることを表すものとします。

i番目の辺についてその辺のみ黒く塗ったときの状態\(X_i\)と置きます。これは各辺について「\(a\)\(b\)を結ぶ\(i\)番目の辺が、\(u_i\)\(v_i\)を結ぶパス上に存在しているか」ということを\(M\)個調べればよく、「頂点\(a\)と頂点\(b\)は、どちらも\(u_i\)\(v_i\)を結ぶパス上に存在しているか」ということと同じなので、LCAを使って計算できます。

この時、次のようなDPを考えます。
\(dp[i][j] := i\)番目までの辺の色を確定させた後、状態が\(j\)であるような塗り方の通り数
\((0 \leqq i\leqq N-1, 0\leqq j\leqq2^M-1)\)

どの辺の色も確定していない場合どの制約も満たされていないため、初期条件は\(dp[0][0]=1\)、他は\(0\)です。

ここで、\(i+1\)番目の辺を白く塗る場合、以下が成り立ちます。
\(dp[i+1][j] += dp[i][j]\)
これは\(i+1\)番目の辺を白く塗った場合、状態が変わることはありえないからです。

また、\(i+1\)番目の辺を黒く塗る場合、次のようになります。
\(dp[i+1][j \vert X_{i+1}] += dp[i][j]\)
(\(a\vert b\)\(a\)\(b\)の論理和をあらわします)
これは状態が\(j\)のときに\(i+1\)番目の辺を黒く塗る場合、既に満たされている制約はそのまま満たされ、\(i+1\)番目の辺を黒く塗ったことで初めて満たされる制約のみが状態に影響を及ぼすからです。

求めるべき解は\(dp[N-1][2^M-1]\)です。
以上の方針により、\(O(N 2^M)\)で解くことができます。

(by Mitsubachi)

実装例

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

vector<vector<int>> graph(55);
//graph[i]に含まれる数jは、頂点iと頂点jを結ぶ辺があることを示す
int LCA[55][7];
//LCA[i][j] := 頂点iの2^j先の頂点
int dist[55];
//dist[i] := 頂点iと頂点1の距離
int edge[55][2];
//edge[i][0] := i番目の辺が結ぶ点
//edge[i][1] := i番目の辺が結ぶもう一方の点
vector<int> edgescore(55,0);
//edgescore[i] := i番目の辺の「状態」

void dfs(int v,int p){
  LCA[v][0]=p;
  for(int i=1;i<7;i++){
    LCA[v][i]=LCA[LCA[v][i-1]][i-1];
  }
  for(int it:graph[v]){
    if(it==p)continue;
    dist[it]=dist[v]+1;
    dfs(it,v);
  }
}//根からの距離
int find_lca(int u,int v){
  if(dist[u]>dist[v])swap(u,v);
  int d=dist[v]-dist[u];
  for(int i=0;i<7;i++){
    if(d&(1<<i)){
      v=LCA[v][i];
    }
  }
  if(u==v)return u;
  for(int i=6;i>=0;i--){
    if(LCA[u][i]!=LCA[v][i]){
      u=LCA[u][i];
      v=LCA[v][i];
    }
  }
  return LCA[u][0];
}//uとvの最近共通祖先
int length(int u,int v){
  return dist[u]+dist[v]-2*dist[find_lca(u,v)];
}//uとvの距離
bool is_on_path(int u,int v,int a){
  return length(u,a)+length(a,v)==length(u,v);
}//u-vパス上に頂点aがあるか

int main(){
  int n;
  cin>>n;
  
  for(int i=0;i<n-1;i++){
    int a,b;
    cin>>a>>b;
    a--;
    b--;
    edge[i][0]=a,edge[i][1]=b;
    graph[a].push_back(b);
    graph[b].push_back(a);
  }//木の入力
  
  dist[0]=0;
  dfs(0,0);//LCA配列,dist配列を作成
  
  int m;
  cin>>m;
  
  for(int i=0;i<m;i++){
    int a,b;
    cin>>a>>b;
    a--;
    b--;
    for(int j=0;j<n-1;j++){
      if(is_on_path(a,b,edge[j][0])==true&&is_on_path(a,b,edge[j][1])==true){
        edgescore[j]+=pow(2,i);
      }//j番目の辺のみ黒く塗ったとき、i番目の制約を満たすか
    }
  }
  
  int key=(1<<m);
  ll dp[n][key]={};
  //dp[i][j] := i番目までの辺の色を確定させた後、状態がjであるような塗り方の通り数
  dp[0][0]=1;
  
  for(int i=0;i<n-1;i++){
    for(int j=0;j<key;j++){
      dp[i+1][j]+=dp[i][j];//i+1番目の辺を白く塗る場合
      
      int check=(edgescore[i]|j);
      dp[i+1][check]+=dp[i][j];//i+1番目の辺を黒く塗る場合
    }
  }
  
  cout<<dp[n-1][key-1]<<endl;
}

posted:
last update: