#include<bits/stdc++.h>
#include<math.h>
#include <stdio.h>
#include<vector>
#include<deque>
#include<stdlib.h>
#include <algorithm>
#include<set>
#include<map>
#include <limits>
#include<iomanip>
#include<string>
#define rep(i,n) for(int i = 0; i < n; i++)
typedef long long ll;
using namespace std;
#define llMAX numeric_limits<long long>::max()
#define intMAX numeric_limits<int>::max()
#define ll long long
#define d_2 100000
#define d9_7 1000000007
struct edge{
  struct treev* to;
  ll length;
  int type;//0が道路、1が鉄道
};
struct treev{
  int num;
  vector<edge> nexts;
};
//UNION FIND
//構造体
struct uunion{
  int num;
  int size;
};
uunion par1[200000];//親の番号
vector<bool> record(200000,false);
//n要素で初期化
void init1(int n){
  rep(i,n){
    par1[i].num=i;
    par1[i].size=1;
  }
}
//木の根を求める
int root1(int x){
  if(par1[x].num==x){
    return x;
  }else{
    return par1[x].num=root1(par1[x].num);//経路圧縮
  }
}
//属する集合の要素数を求める
int size1(int x){
  return par1[root1(x)].size;
}
//x,yが同じ集合に属するか否か
bool same1(int x,int y){
  return (root1(x)==root1(y));
}
//x,yの属する集合を併合
void unite1(int x,int y){
  x=root1(x);
  y=root1(y);
  if(x==y)return;
  par1[y].size += par1[x].size;
  par1[x].num = y;
}


uunion par2[200000];//親の番号
//n要素で初期化
void init2(int n){
  rep(i,n){
    par2[i].num=i;
    par2[i].size=1;
  }
}
//木の根を求める
int root2(int x){
  if(par2[x].num==x){
    return x;
  }else{
    return par2[x].num=root2(par2[x].num);//経路圧縮
  }
}
//属する集合の要素数を求める
int size2(int x){
  return par2[root2(x)].size;
}
//x,yが同じ集合に属するか否か
bool same2(int x,int y){
  return (root2(x)==root2(y));
}
//x,yの属する集合を併合
void unite2(int x,int y){
  x=root2(x);
  y=root2(y);
  if(x==y)return;
  par2[y].size += par2[x].size;
  par2[x].num = y;
}
uunion par3[200000];//親の番号
//n要素で初期化
void init3(int n){
  rep(i,n){
    par3[i].num=i;
    par3[i].size=1;
  }
  return;
}
//木の根を求める
int root3(int x){
  if(par3[x].num==x){
    return x;
  }else{
    return par3[x].num=root3(par3[x].num);//経路圧縮
  }
}
//属する集合の要素数を求める
int size3(int x){
  return par3[root3(x)].size;
}
//x,yが同じ集合に属するか否か
bool same3(int x,int y){
  return (root3(x)==root3(y));
}
//x,yの属する集合を併合
void unite3(int x,int y){
  x=root3(x);
  y=root3(y);
  if(x==y)return;
  par3[y].size += par3[x].size;
  par3[x].num = y;
}
//木構造グラフ

struct input{
  int u;
  int v;
};
bool cmp(const input &a, const input &b)//ソート用
{
  if(a.u!=b.u){
    return a.u<b.u;
  }else{
    return a.v<b.v;

  }
}
vector<treev*> vs;
treev* createtreenode(int num){
  treev* newt=new treev;
  if(newt==NULL){
    cout << "memory error" << endl;
    while(1){
      num=1;//メモリエラー対策
    }
  }
  newt->num=num;
  newt->nexts={};
  //cout << "fff" << endl;
  return newt;
}
void insertree(ll len,int Va,int Vb,int type){//結ぶ頂点を
  vs[Va]->nexts.push_back({vs[Vb],len,type});
  vs[Vb]->nexts.push_back({vs[Va],len,type});
  return;
}
void search1(int root,ll depth,int pre){
  record[root]=true;
  //cout << "eee" << endl;
  //ここにやりたい処理を書く
  if(pre!=-1){
    unite1(root,pre);
  }
  for(auto i:vs[root]->nexts){
    if(pre==i.to->num || i.type==1||record[i.to->num]==true){
      continue;
    }
    search1(i.to->num,depth+i.length,root);
  }
  return;
}
void search2(int root,ll depth,int pre){
  record[root]=true;
  //cout << "eee" << endl;
  //ここにやりたい処理を書く
  if(pre!=-1){
    unite2(root,pre);
  }
  for(auto i:vs[root]->nexts){
    if(pre==i.to->num || i.type==0||record[i.to->num]==true){
      continue;
    }
    search2(i.to->num,depth+i.length,root);
  }
  return;
}
void search3(int root,ll depth,int pre){
  record[root]=true;
  if(pre!=-1){
    if(same1(root,pre)&&same2(root,pre)){
      unite3(root,pre);
    }
  }

  //ここにやりたい処理を書く
  for(auto i:vs[root]->nexts){
    if(pre==i.to->num||record[i.to->num]==true){
      continue;
    }
    search3(i.to->num,depth+i.length,root);
  }
  return;
}

int main(void){
  int n,k,l,p,q,r,s;
  cin >> n>>k>>l;
  rep(i,n){
    vs.push_back(createtreenode(i));
  }
  rep(i,k){
    cin>>p>>q;
    p--;q--;
    insertree(1,p,q,0);
  }
  rep(i,l){
    cin>>r>>s;
    r--;s--;
    insertree(1,r,s,1);
  }
  init1(n);
  rep(i,n){
    if(record[i]==false){
      search1(i,0,-1);
    }
  }
  init2(n);
  rep(i,n){
    record[i]=false;
  }
  rep(i,n){
    if(record[i]==false){
      search2(i,0,-1);
      //cout<<i<<endl;
    }
  }
  /*rep(i,n){
    record[i]=false;
  }
  init3(n);
  rep(i,n){
    if(record[i]==false){
      search3(i,0,-1);
    }
  }*/
  /*rep(i,n){
    cout<<par1[i].num<<" ";
  }
  cout<<endl;
  rep(i,n){
    cout<<par2[i].num<<" ";
  }
  cout<<endl;*/
  map<pair<int,int>,int> m;
  rep(i,n){
    if(m.find({root1(i),root2(i)})!=m.end()){
      m[{root1(i),root2(i)}]+=1;
      continue;
    }
    m.insert({{root1(i),root2(i)},1});
  }
  rep(i,n){
    auto c=m[{root1(i),root2(i)}];
    cout<<c<<" ";
  }
  cout<<endl;
  return 0;
}

Measured memory_bytes: 38896