n個點,m條邊,q個詢問node
每次給出k個能用的點,把不能用的點的鏈接的邊刪除後問有幾個聯通塊ide
(k[i]的和小於n)spa
好妙啊~~~,還有點卡常code
首先考慮暴力作法blog
對於每次詢問,若是咱們對給出的點兩兩合併it
假設一次給出sqrt(n)個點,複雜度最壞爲O(n*n*q) [這時候q=1]io
對於每次詢問,暴力跑一邊全部的邊,合併邊的兩個端點class
複雜度最壞O(m*q)sed
看到上面兩種暴力方法其實都是可行的方法
要想一個方法下降複雜度,咱們綜合上面兩種方法
因此
咱們對於大於sqrt(n)的k[i]採起二號暴力選手的作法
對於小於等於sqrt(n)的k[i]採起一號暴力選手的作法
爲何能夠呢?
若是此時給出k(k<=sqrt(n))個點
一號選手的複雜度最壞爲(sqrt(n)*sqrt(n)*q)
可是這種詢問咱們最多進行sqrt(n)次
因此複雜度最壞爲O(n*sqrt(n))
若是此時給出k(k>sqrt(n))個點
咱們暴力跑全部的邊,這種詢問最多也是進行sqrt(n)次
複雜度最壞爲O(sqrt(n)*m)
code:
int head[maxn],cnt; struct node { int u,v,w,next; } e[maxn]; void add(int u,int v,int w) { e[cnt].u=u,e[cnt].v=v,e[cnt].w=w; e[cnt].next=head[u],head[u]=cnt++; } int n,m,qq,p[maxn],a[maxn],vis[maxn]; vector<int>s[maxn]; int find(int x) { if(p[x]==x) return x; return p[x] = find(p[x]); } void combine(int x,int y) { int dx = find(x); int dy = find(y); if(dx==dy) return ; p[dx] = dy; } int main() { mst(head,-1); n=read(),m=read(),qq=read(); rep(i,1,n) p[i] = i; for(int i=1 ; i<=m ; i++) { int u,v,w; u=read(),v=read(); add(u,v,1),add(v,u,1); s[u].push_back(v); s[v].push_back(u); } rep(i,1,n) sort(s[i].begin(),s[i].end()); while(qq--) { int temp=0; int k = read(); int t = sqrt(n) ; for(int i=1 ; i<=k ; i++) { a[i] = read(); vis[a[i]]=1; p[a[i]] = a[i]; } if(k<t) { for(int i=1 ; i<=k ; i++ ) { for(int j=1 ; j<=k ; j++) { auto it = lower_bound(s[a[i]].begin(),s[a[i]].end(),a[j]); if(it==s[a[i]].end()||*it!=a[j]) continue; combine(a[i],a[j]); } } } else { for(int i= 0; i<cnt; i++) { int u = e[i].u; int v = e[i].v; if(!vis[u]||!vis[v]) continue; combine(u,v); } } for(int i=1 ; i<=k ; i++) if(find(a[i])==a[i]) temp++; for(int i=1 ; i<=k ; i++) vis[a[i]] = 0; out(temp); puts(""); } return 0; }