// Original C++ solution (slower):
#include <iostream>

#include <string>

#include <vector>

#include <cstdlib>

#include <cstdio>

#include <cmath>

#include <algorithm>

#include <map>

#include <stack>

#include <queue>

#include <set>

#include <cstring>



using namespace std;

// ascending order

#define vsort(v) sort(v.begin(), v.end())

// descending order

#define vsort_r(v) sort(v.begin(), v.end(), greater<int>())

#define vunique(v) unique(v.begin(), v.end())

#define mp make_pair

#define ts(x) to_string(x)

#define rep(i, a, b) for(int i = (int)a; i < (int)b; i++)

#define repm(i, a, b) for(int i = (int)a; i > (int)b; i--)

#define bit(a) bitset<8>(a)

#define des_priority_queue priority_queue<int, vector<int>, greater<int> >

typedef long long ll;

typedef pair<int, int> P;

const ll INF = 1e18;



#define MAX_N 400002

int N, K, L;



struct union_find_tree {

	int par[MAX_N]; // 親

	int depth[MAX_N]; // 木の深さ



	// n要素で初期化

	void init(int n) {

		rep(i, 0, n) {

			par[i] = i;

			depth[i] = 0;

		}

	}



	// 木の根を求める

	int find(int x) {

		if(par[x] == x) return x;

		else return par[x] = find(par[x]);

	}



	// xとyの属する集合を併合

	void unite(int x, int y) {

		x = find(x);

		y = find(y);

		if(x == y) return;



		if(depth[x] < depth[y]) {

			par[x] = y;

		} else {

			par[y] = x;

			if(depth[x] == depth[y]) depth[x]++;

		}

	}



	// xとyが同じ集合に属するか否か

	bool same(int x, int y) {

		return find(x) == find(y);

	}

};



int main(){

	cin.tie(0);

	ios::sync_with_stdio(false);



	cin >> N >> K >> L;



	union_find_tree uft1;

	uft1.init(N);



	rep(k, 0, K) {

		int p, q;

		cin >> p >> q;

		p--, q--;

		uft1.unite(p, q);

	}



	union_find_tree uft2;

	uft2.init(N);



	rep(l, 0, L) {

		int r, s;

		cin >> r >> s;

		r--, s--;

		uft2.unite(r, s);

	}



	map<pair<int, int>, int> m;

	rep(i, 0, N) m[make_pair(uft1.find(i), uft2.find(i))] += 1;



	rep(i, 0, N) cout << m[make_pair(uft1.find(i), uft2.find(i))] << " " ;

	cout << endl;

}


// Optimized solution to the same problem (faster):
#include <iostream>

#include <string>

#include <vector>

#include <cstdlib>

#include <cstdio>

#include <cmath>

#include <algorithm>

#include <map>

#include <stack>

#include <queue>

#include <set>

#include <cstring>



using namespace std;

// ascending order

#define vsort(v) sort(v.begin(), v.end())

// descending order

#define vsort_r(v) sort(v.begin(), v.end(), greater<int>())

#define vunique(v) v.erase(unique(v.begin(), v.end()), v.end())

#define mp make_pair

#define ts(x) to_string(x)

#define rep(i, a, b) for(int i = (int)a; i < (int)b; i++)

#define repm(i, a, b) for(int i = (int)a; i > (int)b; i--)

#define bit(a) bitset<8>(a)

#define des_priority_queue priority_queue<int, vector<int>, greater<int> >

template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; }

template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

typedef long long ll;

typedef pair<int, int> P;

const ll INF = 1e18;



#define MAX_V 1000000

#define MAX_N 200010



int par[MAX_N]; // 親

int depth[MAX_N]; // 木の深さ



struct UnionFind {

    vector<int> par;

    UnionFind(int n) : par(n, -1) { }

    void init(int n) { par.assign(n, -1); }



    // 木の根を求める

    int root(int x) {

        if(par[x] < 0) return x;

        else return par[x] = root(par[x]);

    }



    // xとyの属する集合を併合

    void unite(int x, int y) {

        x = root(x);

        y = root(y);

        if(x == y) return;



        if(par[x] > par[y]) swap(x, y);

        par[x] += par[y];

        par[y] = x;



    }



    // xとyが同じ集合に属するか否か

    bool same(int x, int y) {

        return root(x) == root(y);

    }



    int size(int x) {

        return -par[root(x)];

    }

};



int main(){

	cin.tie(0);

	ios::sync_with_stdio(false);



  int n, k, l;

  cin >> n >> k >> l;

  UnionFind road(n), rail(n);

  rep(i, 0, k) {

    int p, q;

    cin >> p >> q;

    road.unite(--p, --q);

  }

  rep(i, 0, l) {

    int r, s;

    cin >> r >> s;

    rail.unite(--r, --s);

  }

  map<pair<int, int>, int> rsl;

  rep(i, 0, n) rsl[make_pair(road.root(i), rail.root(i))]++;

  rep(i, 0, n) {

    cout << rsl[make_pair(road.root(i), rail.root(i))];

    if(i == n - 1) {

      cout << endl;

      return 0;

    }

    cout << " ";

  }

}




// Fastest known solution for this problem: