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; }