// Slower C++ solution:
#include<iostream>

#include<vector>

#include<algorithm>

#include<utility>

#include<string>

#include<cmath>

#include<cstring>

#include<queue>

#include<map>

#include<climits>

#include<set>



#define REP(i, n) for(int i = 0;i < n;i++)

#define REPR(i, n) for(int i = n;i >= 0;i--)

#define FOR(i, m, n) for(int i = m;i < n;i++)

#define FORR(i, m, n) for(int i = m;i >= n;i--)

#define SORT(v, n) sort(v, v+n);

#define VSORT(v) sort(v.begin(), v.end());

#define llong long long

#define pb(a) push_back(a)

using namespace std;

typedef pair<int, int> pii;

typedef long long int ll;

typedef pair<ll,ll> pll;

int dx[4] = {1,0,0,-1};

int dy[4] = {0,1,-1,0};

#define MOD 1000000007

#define ARRAY_MAX 200005



const int INF = 1e9+7;



int n,m;

vector<int> g[ARRAY_MAX];

bool used[ARRAY_MAX];



void dfs(int now){



    used[now] = true;



    for(int i = 0;i < g[now].size();i++){

        if(used[g[now][i]] == true){

            continue;

        }

        dfs(g[now][i]);

    }

    return;

}



int main(){



    cin >> n >> m;



    for(int i = 0;i < n;i++){

        int k;

        cin >> k;

        for(int j = 0;j < k;j++){

            int a;

            cin >> a;

            a--;

            g[i].push_back(a+n);//人iが話せる言語

            g[a+n].push_back(i);//言語a-1を話せる人の番号

        }

    }

    

    dfs(0);

    //１回のdfsで終わらないなら連結でない



    for(int i = 0;i < n;i++){

        if(used[i] == false){

            cout << "NO" << endl;

            return 0;

        }

    }

    cout << "YES" << endl;



    return 0;

}

// Faster C++ solution for the same problem (measured speedup: 6.455385757932166x):
#include<iostream>

#include<vector>

#include<algorithm>

#include<utility>

#include<string>

#include<cmath>

#include<cstring>

#include<queue>

#include<map>

#include<climits>

#include<set>



#define REP(i, n) for(int i = 0;i < n;i++)

#define REPR(i, n) for(int i = n;i >= 0;i--)

#define FOR(i, m, n) for(int i = m;i < n;i++)

#define FORR(i, m, n) for(int i = m;i >= n;i--)

#define SORT(v, n) sort(v, v+n);

#define VSORT(v) sort(v.begin(), v.end());

#define llong long long

#define pb(a) push_back(a)

using namespace std;

typedef pair<int, int> pii;

typedef long long int ll;

typedef pair<ll,ll> pll;

int dx[4] = {1,0,0,-1};

int dy[4] = {0,1,-1,0};

#define MOD 1000000007

#define ARRAY_MAX 200005



const int INF = 1e9+7;



typedef struct union_find{

     

    vector<int> pa;//親

    vector<int> ra;//木の深さ

    //n要素で初期化

    void init(int n){

        pa.resize(n);

        ra.resize(n);

        for(int i = 0;i < n;i++){

            pa[i] = i;/*各ノードに番号を振る,この時par[x]=xとなる時は木の根となる*/

            ra[i] = 0;/*各ノード自体の高さは0*/

            

        }

    }

    //木の根を求める

    int find(int x){

        if(pa[x] == x){

            return x;/*根ならそのノードの番号を返す*/

        }else{

            return pa[x] = find(pa[x]);/*根でないならさらにノードの根を探す*/

        }

    }



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

    void unite(int x,int y){

        x = find(x);//xの根の番号を探す

        y = find(y);//yの根の番号を探す

        if(x == y){//一致すればつながっている

            return;

        }

        if(ra[x] < ra[y]){//xの高さが低いなら高いほうにつなぐ、そして高さは大きい方(y)になる

            pa[x] = y;

        }else{

            pa[y] = x;//yの高さが低いなら高いほうにつなぐ、そして高さは大きいほう(x)になる

            if(ra[x] == ra[y]){//高さが一致しているなら併合の高さは1増える

                ra[x]++;

            }

        }

    }



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

    bool same(int x,int y){

        return find(x) == find(y);//同じ集合なら1、違うなら0が返る

    }

}UF;





int main(){



    UF tree;

    int n,m;

    cin >> n >> m;

    tree.init(n+m);



    for(int i = 0;i < n;i++){

        int k;

        cin >> k;

        for(int j = 0;j < k;j++){

            int a;

            cin >> a;

            a--;

            tree.unite(i,a+n);

        }

    }



    int r = tree.find(0);



    for(int i = 0;i < n;i++){

        if(tree.find(i) != r){

            cout << "NO" << endl;

            return 0;

        }

    }

    cout << "YES" << endl;



    return 0;

}