ICPC Central Europe Regional Contest 2019 Saba1000kg(复杂度均摊-并查集)
题目大意:
n个点,m条边,q个询问
每次给出k个能用的点,把不能用的点的连接的边删除后问有几个联通块
(k[i]的和小于n)
题目思路:
好妙啊~~~,还有点卡常
首先考虑暴力做法
一号暴力选手
对于每次询问,如果我们对给出的点两两合并
假设一次给出n个点,复杂度最坏为O(n*n*q) [这时候q=1]
二号暴力选手
对于每次询问,暴力跑一边所有的边,合并边的两个端点
复杂度最坏O(m*q)
看到上面两种暴力方法其实都是可行的
要想一个方法降低复杂度,我们综合上面两种方法
题目说k[i]的和小于n
所以
我们对于大于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; }