「SWTR-05」Chain
阿新 • • 發佈:2021-10-11
Description
給定一個 DAG,每次詢問如果刪除 \(k\leq 15\) 個點,還剩下多少條入度為零的點到出度為零的點的路徑。新增的路徑不參與統計。
Solution
唯一可做題,其他的都沒什麼思路/kel。
\(k\) 很小,容易想到一個列舉子集的做法,那麼只需要預處理出每兩個點之間的距離。可以按拓撲序刪點,然後再一遍拓撲求出所有點到當前點的距離。配合容斥可以過前 75%,複雜度 \(O(nm+q2^k)\)。
發現只需要知道最後一個點是哪個點,把這 \(k\) 個點按拓撲序排序,然後考慮 dp。\(dp_{i,j}\) 表示選了 \(j\) 個點,最後一個點是第 \(i\) 個的從入度為零的點到 \(i\)
再次觀察,發現並不需要知道選了多少個,只需要在轉移的時候容斥,那麼就可以刪掉一維。單次 \(O(k^2)\)。
#include<stdio.h> #include<vector> #include<queue> #include<algorithm> using namespace std; inline int read(){ int x=0,flag=1; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();} return flag? x:-x; } const int N=2e3+7; const int Mod=1e9+7; queue<int> Q; vector<int> G[N]; int dfn[N],in[N],dis[N][N],In[N],a[N],dp[16]; inline bool Cmp(int x,int y){return dfn[x]<dfn[y];} int main(){ freopen("foodchain.in","r",stdin); freopen("foodchain.out","w",stdout); int n=read(),m=read(); for(int i=1;i<=m;i++){ int u=read(),v=read(); G[u].push_back(v),In[v]++; } for(int u=1;u<=n;u++){ if(!G[u].size()) G[u].push_back(n+1),In[n+1]++; if(!In[u]) G[0].push_back(u),In[u]++; in[u]=In[u]; } Q.push(0); int timer=0; while(!Q.empty()){ int u=Q.front(); Q.pop(); dfn[u]=++timer; for(int v:G[u]) if(!(--in[v])) Q.push(v); } for(int i=0;i<=n+1;i++) a[i]=i; sort(a,a+1+n+1,Cmp); for(int i=0;i<=n+1;i++){ const int U=a[i]; for(int v=0;v<=n+1;v++) in[v]=In[v]; dis[U][U]=1; for(int j=i;j<=n+1;j++) if(!in[a[j]]) Q.push(a[j]); while(!Q.empty()){ int u=Q.front(); Q.pop(); for(int v:G[u]){ dis[U][v]=(dis[U][v]+dis[U][u])%Mod; if(!(--in[v])) Q.push(v); } } for(int v:G[U]) --In[v]; } int q=read(); while(q--){ int k=read(); for(int i=1;i<=k;i++) a[i]=read(); sort(a+1,a+1+k,Cmp); for(int i=1;i<=k;i++) dp[i]=Mod-dis[0][a[i]]; for(int i=1;i<=k;i++) for(int j=1;j<i;j++) dp[i]=(dp[i]-1ll*dp[j]*dis[a[j]][a[i]]%Mod+Mod)%Mod; int ans=dis[0][n+1]; for(int i=1;i<=k;i++) ans=(ans+1ll*dp[i]*dis[a[i]][n+1]%Mod)%Mod; printf("%d\n",ans); } }