/*
* 匈牙利算法
* 默认给定二分图
*/
#include
#include
#include
#include
#include
#include
using namespace std;
#define map mmap
#define search ssearch
#define maxn (1010)
int n, m, map[maxn][maxn], from[maxn], vis[maxn];
bool search(int u)
{
for(int v = 1; v <= n; v++) if(map[u][v] && !vis[v])
{
vis[v] = 1;
if(!from[v] || search(from[v]))
{
from[v] = u;
from[u] = v;
return true;
}
}
return false;
}
int main()
{
cin >> n >> m;
for(int i = 0, u, v; i < m; i++)
{
scanf("%d%d", &u, &v);
map[u][v] = map[v][u] = 1;
}
int tot = 0;
for(int i = 1; i <= n; i++) if(!from[i])
{
memset(vis, 0, sizeof(vis));
if(search(i)) tot++;
}
cout << tot << endl;
return 0;
}
/*
* 并查集
* 判断图连通性,速度快过宽搜很多
*/
int father[250010 * 2], rank[250010 * 2];
void disjoint_set(int n)
{
for(int i = 1; i <= n; i++)
father[i] = i;
}
int find(int v)
{
return father[v] = father[v] == v? v: find(father[v]);
}
void merge(int x, int y)
{
int a = find(x), b = find(y);
if(rank[a] < rank[b])
father[a] = b;
else
{
father[b] = a;
if(rank[a] == rank[b])
rank[a]++;
}
}
/*
* st表
* st[i][j]表示元素arr[i]开始,长度为2^j区间内的最值
* st[i][0]为arr[i],st[i][j] = max(st[i][j-1], st[i - (1 << j-1)][j-1])
* 求区间[l, r]内最值:
* log2[i]代表i的对数向下取整
* 对于长度len而言,显然2^log2[len]严格大于len的一半
* 令k = log2[r - l + 1],则最值为max(st[l][k], st[r - (1<][k])
*/
#define maxn (55000)
int log2[maxn], st[maxn][32];
void st_prepare(int n, int *arr)
{
log2[1] = 0;
for(int i = 2; i <= n; i++)
{
log2[i] = log2[i - 1];
if((1 << log2[i] + 1) == i)
log2[i]++;
}
for(int i = n; i >= 1; i--)
{
st[i][0] = arr[i];
for(int j = 1; i + (1 << j) - 1 <= n; j++)
{
st[i][j] = max(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}
}
}
int st_query(int l, int r)
{
int len = r - l + 1, k = log2[len];
return max(st[l][k], st[r - (1 << k) + 1][k]);
}
树状数组
/*
* 树状数组
* 可对A[x]增加value,可对A[1]~A[x]区间求和
* 相比线段树,树状数组最大的优势是节约区间(线段树一般要开4倍规模)
*/
int seg[maxn];
inline int lowbit(int x)
{
return (x&-x);
}
void add(int x, int value, int n)
{
for(int i = x; i <= n; i += lowbit(i))
{
seg[i] += value;
}
}
int get(int x)
{
int sum = 0;
for(int i = x; i; i -= lowbit(i))
sum += seg[i];
return sum;
}
二维树状数组
/*
* 二维树状数组
* 可对c[x][y]增加value,可对c[1][1]到c[x][y]矩阵求和
*/
int c[maxn][maxn];
inline int lowbit(int x)
{
return (x&-x);
}
void add(int x, int y, int value, int n)
{
for(int i = x; i <= n; i += lowbit(i))
{
for(int j = y; j <= n; j += lowbit(j))
c[i][j] += value;
}
}
int get(int x, int y)
{
int sum = 0;
for(int i = x; i; i -= lowbit(i))
{
for(int j = y; j; j -= lowbit(j))
sum += c[i][j];
}
return sum;
}
/*
* 基于dfs的树链剖分 & 线段树
*/
#include
#include
#include
#include
using namespace std;
#define maxn (10010)
struct edge
{
int a, b, c;
} e[maxn];
vector<int> son[maxn];
int fa[maxn], dep[maxn], sz[maxn], next[maxn];
void dfs1(int u, int f, int d)
{
fa[u] = f;
dep[u] = d;
sz[u] = 1;
next[u] = 0;
for(int i = 0, v; i < son[u].size(); i++)
if((v = son[u][i]) != f) {
dfs1(v, u, d + 1);
sz[u] += sz[v];
if(sz[next[u]] < sz[v])
next[u] = v;
}
}
int top[maxn], w[maxn], idx[maxn], cnt;
void dfs2(int u, int tp)
{
top[u] = tp;
idx[u] = ++cnt;
if(!next[u]) return ;
dfs2(next[u], tp);
for(int i = 0, v; i < son[u].size(); i++)
if((v = son[u][i]) != fa[u] && v != next[u]) {
dfs2(v, v);
}
}
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
int seg[maxn << 2];
void build(int l, int r, int rt)
{
if(l == r) { seg[rt] = w[l]; return; }
int m = (l + r) >> 1;
build(lson), build(rson);
seg[rt] = max(seg[rt<<1], seg[rt<<1|1]);
}
void update(int x, int val, int l, int r, int rt)
{
if(l == r) { seg[rt] = val; return; }
int m = (l + r) >> 1;
if(x <= m) update(x, val, lson);
else update(x, val, rson);
seg[rt] = max(seg[rt<<1], seg[rt<<1|1]);
}
int query(int L, int R, int l, int r, int rt)
{
if(L <= l && r <= R) return seg[rt];
int m = (l + r) >> 1, ret = -1;
if(L <= m) ret = max(ret, query(L, R, lson));
if(R > m) ret = max(ret, query(L, R, rson));
return ret;
}
int qtree(int u, int v, int n)
{
int topu = top[u], topv = top[v], ans = 0;
while(topu != topv)
{
// printf("while()\n");
if(dep[topu] < dep[topv]) { swap(topu, topv); swap(u, v); }
ans = max(query(idx[topu], idx[u], 1, n, 1), ans);
u = fa[topu];
topu = top[u];
}
if(u == v) return ans;
if(dep[u] > dep[v]) swap(u, v);
ans = max(ans, query(idx[next[u]], idx[v], 1, n, 1));
return ans;
}
/*
* 主席树
* 可持久化线段树
* T[0]预留,作为“底板”,也就是版本0
*/
#define maxn (100010)
int root[maxn], cnt;
struct seg
{
int lson, rson, num;
} T[maxn * 40];
void update(int l, int r, int &now, int pre, int a)
{
now = ++cnt;
T[now] = T[pre];
T[now].num++;
if(l == r) return ;
int mid = (l + r) >> 1;
if(a <= mid) update(l, mid, T[now].lson, T[pre].lson, a);
else update(mid + 1, r, T[now].rson, T[pre].rson, a);
}
int query(int l, int r, int pre, int now, int k)
{
if(l == r) { return l; }
int mid = (l + r) >> 1, lnum = T[T[now].lson].num - T[T[pre].lson].num;
if(k <= lnum)
return query(l, mid, T[pre].lson, T[now].lson, k);
else
return query(mid + 1, r, T[pre].rson, T[now].rson, k - lnum);
}
/*
* 自然溢出字符串哈希
* 可用于字符串判重,或利用字符串单调性结合二分解决问题
*/
typedef unsigned long long ulint;
const ulint seed = 50009uLL;
#define maxn (100010)
ulint xp[maxn], H[maxn];
char s[maxn];
void init_xp(int n)
{
xp[0] = 1;
for(int i = 1; i < n; i++)
xp[i] = xp[i-1] * seed;
}
void init_hash(int n)
{
H[0] = s[0] - 'a' + 1;
for(int i = 1; i < n; i++)
H[i] = H[i-1] * seed + (ulint)(s[i] - 'a' + 1);
}
void ask_hash(int l, int r)
{
if(l == 0) return H[r];
return H[r] - H[l-1] * xp[r - l + 1];
}
void manacher(string& s, int *R, int n)
{
/*
* manacher算法
* 需要将字符串预处理成$#x#x#x#x#x#x#形式
* 若仅求长度为奇数的回文串,最左侧添加特殊字符即可
* 记录当前最右延伸回文半径mx和对应回文中心p
* i若位于mx以内,则将对称位置2*p-i的回文半径的不越界部分作为i的回文半径,并且继续向右侧匹配
* 若得到新的最右延伸回文半径,更新mx和p
*/
int p = 0, mx = 0;
R[0] = 1;
for(int i = 1; i < n; i++)
{
if(mx > i)
R[i] = min(R[2*p - i], mx - i);
else R[i] = 1;
while(s[i - R[i]] == s[i + R[i]])
R[i]++;
if(i + R[i] > mx)
p = i, mx = i + R[i];
}
return;
}
#define maxn (300000)
int next[maxn];
vector<int> pos;
void kmp(string s1, string s2)
{
/*
* kmp实质:对模式串的每个前缀,求出与之后缀匹配的最长真前缀长度
* next[i]表示文本串s2在匹配样式s1[i]时首次失配后的新s1下标
* next[i]也是与s1[i-1](已匹配部分)后缀相匹配的最长真前缀长度
* 以上注意i要大于0,next[0]没有意义
* 与s1[i]后缀匹配的最长真前缀长度为next[i+1]
* 所以第一个for循环也是求s1[i]最长真前缀的过程
*/
// step 1
int n = s1.size();
memset(next, 0, sizeof(int) * (n + 1));
for(int i = 1, j; i < n; i++)
{
j = i; //从j处开始试配
while(j > 0) //第一次手动失配,相当于模拟s2的失配情况
{
j = next[j];
if(pattern[i] == pattern[j])
{
next[i + 1] = j + 1; //如果在i+1处失配,只需要从j+1处开始匹配
break; // next[i + 1] 为s1[i]最长真前缀长度
}
}
}
// step 2
int m = s2.size();
pos.clear();
for(int i = 0, j = 0; i < m; i++)
{
if(s2[i] == s1[j])
{
j++;
}
else
{
while(j > 0) // j最多回退至0
{
j = next[j];
if(s1[j] == s2[i])
{
j++;
break;
}
}
}
if(j == n) pos.push_back(i - n + 1); // 注意s1 s2应以'\0'结尾
}
}
/*
* Trie树
* 一般情况下出度为26,可搜索
* 若需要对相同字符串的插入做不同记录(比如不同id),将ed换为vector即可
* 一个典型的用法是将数字以二进制插入trie树中,方便做异或操作
*/
#define maxn (1000000)
struct Trie
{
int next[maxn][26], ed[maxn];
int L, root;
int newnode()
{
for(int i = 0; i < 26; i++)
next[L][i] = -1;
ed[L] = 0;
return L++;
}
void init()
{
L = 0;
root = newnode();
}
void insert(char s[])
{
int now = root;
for(int i = 0, sz = strlen(s); i < sz; i++)
{
if(next[now][s[i] - 'A'] == -1)
next[now][s[i] - 'A'] = newnode();
now = next[now][s[i] - 'A'];
}
ed[now] = 1;
}
bool query(char s[])
{
int now = root;
for(int i = 0, sz = strlen(s); i < sz; i++)
{
if(next[now][s[i] - 'A'] == -1)
{
return false;
}
now = next[now][s[i] - 'A'];
}
return ed[now] == 1;
}
};
/*
* 后缀数组
* 后缀数组的倍增构造法
* 复杂度为O(nlogn)
*/
#define maxn (1000010)
bool cmp(int *r, int a, int b, int l)
{ return r[a] == r[b] && r[a + l] == r[b + l]; }
int ta[maxn], tb[maxn], bk[maxn];
void da(int *r, int *sa, int n, int m)
{
int i, j, p, *x = ta, *y = tb, *t;
for(i = 0; i < m; i++) bk[i] = 0;
for(i = 0; i < n; i++) bk[x[i] = r[i]]++;
for(i = 1; i < m; i++) bk[i] += bk[i-1];
for(i = 0; i < n; i++) sa[--bk[x[i]]] = i;
for(j = 1, p = 1; p < n; j *= 2, m = p)
{
for(p = 0, i = n - j; i < n; i++) y[p++] = i;
for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i] - j;
for(i = 0; i < m; i++) bk[i] = 0;
for(i = 0; i < n; i++) bk[x[i]]++;
for(i = 1; i < m; i++) bk[i] += bk[i-1];
for(i = n-1; i >= 0; i--) sa[--bk[x[y[i]]]] = y[i];
for(t = x, x = y, y = t, x[sa[0]] = 0, p = 1, i = 1; i < n; i++)
x[sa[i]] = cmp(y, sa[i-1], sa[i], j)? p-1: p++;
}
}
#define rank rrank
int rank[maxn], sa[maxn], height[maxn];
void calheight(int *r, int n)
{
for(int i = 0; i < n; i++) rank[sa[i]] = i;
for(int k = 0, i = 0; i < n; i++)
{
k ? k-- : 0;
if(rank[i] > 0)
while(r[i + k] == r[sa[rank[i] - 1] + k])
k++;
height[rank[i]] = k;
}
}
/*
* AC自动机
* 典型问题:多模式匹配
* 构建了fail树和Trie图,并用vis标记加速的快速解法
*/
#include
#include
#include
#include
using namespace std;
#define maxn (500050)
struct Aho_Corasick
{
int next[maxn][26], nd[maxn], fail[maxn], vis[maxn];
int root, L;
int newnode()
{
for(int i = 0; i < 26; i++)
next[L][i] = -1;
nd[L] = 0;
vis[L] = 0;
return L++;
}
void init()
{
L = 0;
root = newnode();
}
void insert(char *s)
{
int now = root;
for(int i = 0, key, sz = strlen(s); i < sz; i++)
{
key = s[i] - 'a';
if(next[now][key] == -1)
next[now][key] = newnode();
now = next[now][key];
}
nd[now]++;
}
void build()
{
// fail数组含义
// 和i节点代表的前缀的后缀匹配的trie上最长真前缀,由trie树性质得唯一
// 即当i节点的某边发生失配时转移到达的trie上最长真前缀
// if(next[i][j] == -1) next[i][j] = next[fail[i]][j]
queue<int> Q;
fail[root] = root;
for(int i = 0; i < 26; i++)
if(next[root][i] == -1)
next[root][i] = root;
else
{
fail[next[root][i]] = root;
Q.push(next[root][i]);
}
while(!Q.empty())
{
int now = Q.front();
Q.pop();
for(int i = 0; i < 26; i++)
if(next[now][i] == -1)
next[now][i] = next[fail[now]][i];
else
{
fail[next[now][i]] = next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
int query(char *s)
{
int now = root, ret = 0;
for(int i = 0, key, tmp, sz = strlen(s); i < sz; i++)
{
key = s[i] - 'a';
tmp = now = next[now][key];
while(tmp != root && !vis[tmp])
{
ret += nd[tmp];
nd[tmp] = 0;
vis[tmp] = 1;
tmp = fail[tmp];
}
}
return ret;
}
} actree;
char keyword[100];
char text[1000010];
int main()
{
int T;
cin >> T;
while(T--)
{
actree.init();
int n;
cin >> n;
while(n--)
{
scanf("%s", keyword);
actree.insert(keyword);
}
actree.build();
scanf("%s", text);
cout << actree.query(text) << endl;
}
return 0;
}
/*
* spfa最短路算法
* 基于队列优化加速的最短路算法
* 复杂度为O(V*E),但一般情况下很快,速度堪比dijkstra
*/
typedef long long lint;
#define maxn (100010)
int n;
vectorint , int> > e[maxn];
lint dist[maxn];
queue<int> que;
bool inQue[maxn];
void spfa(lint dist[], int src)
{
memset(dist, 63, sizeof(lint) * (n+1));
dist[src] = 0;
que.push(src);
inQue[src] = true;
while(!que.empty())
{
int u = que.front();
que.pop();
for(int i = 0, v, l; i < e[u].size(); i++)
{
v = e[u][i].first, l = e[u][i].second;
if(dist[u] + l < dist[v])
{
dist[v] = dist[u] + l;
if(!inQue[v])
{
que.push(v);
inQue[v] = true;
}
}
}
inQue[u] = false;
}
}
/*
* dijkstra单源最短路算法
* +堆优化
* 注意堆默认是大根堆,定义小根堆的时候需要将符号反向
*/
typedef long long lint;
#define maxn (100010)
const lint oo = 1e16;
vectorint , int> > e[maxn << 1];
lint d1[maxn << 1], dn[maxn << 1];
bool used[maxn << 1];
struct node {
int u; lint d;
node(int _u, lint _d) { u = _u; d = _d; }
bool operator< (const node& b) const
{ return d > b.d; }
};
priority_queue que;
int n, m;
void dijkstra(lint d[], int src)
{
for(int i = 1; i <= n + m; i++)
{ d[i] = oo; used[i] = false; }
d[src] = 0;
que.push(node(src, 0));
while(!que.empty())
{
node top = que.top(); que.pop();
int u = top.u;
if(used[u]) continue; /// pick out same-id node
used[u] = true;
for(int i = 0; i < e[u].size(); i++)
{
int v = e[u][i].first;
int l = e[u][i].second;
if(d[u] + l < d[v])
{
d[v] = d[u] + l;
que.push(node(v, d[v])); /// may out many node having same id
}
}
}
}
typedef long long lint;
lint mod;
lint quick_pow(lint a, lint n)
{
if(!n) return 1 % mod;
lint tmp = quick_pow(a, n >> 1);
tmp = tmp * tmp % mod;
if(n & 1) tmp = tmp * a % mod;
return tmp;
}
typedef long long lint;
int mod;
struct matrix
{
int a[4][4], n;
void clear() { memset(a, 0, sizeof(a)); }
matrix(int k, int type)
{
n = k, clear();
if(type) for(int i = 0; i < n; i++)
a[i][i] = 1;
}
matrix() { n = 2, clear(); a[0][0] = a[0][1] = a[1][0] = 1, a[1][1] = 0; }
matrix operator* (const matrix& b) const
{
matrix o = matrix(n, 0);
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
for(int k = 0; k < n; k++)
{
o.a[i][j] += ((lint)a[i][k] * b.a[k][j]) % mod;
o.a[i][j] %= mod;
}
return o;
}
friend matrix operator^ (matrix tmp, int k)
{
matrix o = matrix(tmp.n, 1);
while(k)
{
if(k & 1) o = o * tmp;
tmp = tmp * tmp;
k >>= 1;
}
return o;
}
};
/*
* | A E | | A 1 |
* | 0 E | | 0 1 |
*/
matrix getKthsum(const matrix& b, int k)
{
matrix tmp = matrix(b.n << 1, 0);
for(int i = 0; i < b.n; i++)
for(int j = 0; j < b.n; j++)
tmp.a[i][j] = b.a[i][j];
for(int i = 0; i < b.n; i++)
{
tmp.a[i][b.n + i] = 1;
tmp.a[b.n + i][b.n + i] = 1;
}
tmp = tmp^(k+1);
matrix o = matrix(b.n, 0);
for(int i = 0; i < b.n; i++)
for(int j = 0; j < b.n; j++)
o.a[i][j] = tmp.a[i][b.n + j];
for(int i = 0; i < b.n; i++)
o.a[i][i] = (o.a[i][i] + mod - 1) % mod;
return o;
}
/*
* 辗转相除法
* 注意a、b不可同时为0
*/
int euclid(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
/*
* 质因数分解
* O(sqrt(n))
* 分结束某数的所有质因数和对应次数
*/
typedef long long lint;
void factor(lint n, lint a[], int b[], int &tot)
{
int temp = (int)((double)sqrt(n) + 1), i;
lint now = n;
tot = 0;
for(i = 2; i <= temp; i++) if(now % i == 0)
{
a[++tot] = i;
b[tot] = 0;
while(now % i == 0)
{
b[tot]++;
now /= i;
}
}
if(now != 1)
{
a[++tot] = now;
b[tot] = 1;
}
}
/*
* 单调队列
* two-pointers
*/
int sq[maxn], st = 0, ed = 0; //初始化
for(int i = l; i <= r; i++) //区间[l, r]入队列
{
while(st < ed && arr[i] > sq[ed - 1]) ed--;
sq[ed++] = arr[i];
}
for(int i = l; i <= r; i++) //区间[l, r]出队列
{
if(arr[i] == sq[st]) st++;
}
/*
* 单调栈
* 用于O(n)时间内对序列中每个元素求出其左端首个小于/大于的元素
* 常用来解决区间问题,也出现过在后缀数组题目中优化计数问题,很神的一个结构
*/
int _top;
pair<int, int> _stack[maxn];
inline void _push_stack(int x[], int w, int p)
{
while(_top > 0)
{
if(w < _stack[_top].first)
_top--;
else
break;
}
if(!_top) x[p] = -1;
else x[p] = _stack[_top].second;
_stack[++_top] = make_pair(w, p);
}
int get()
{
int ans = 0;
char c;
c = getchar();
while( !isdigit(c) ) c = getchar();
while( isdigit(c) ) ans = ans * 10 + c - '0' , c = getchar();
return ans;
}
/*
* 高精度加法、乘法
* MOD用10000而不是10,增加效率,节约空间
*/
struct bign
{
#define MAX_B (100)
#define MOD (10000)
int a[MAX_B], n;
bign() { a[0] = 0, n = 1; }
bign(int num)
{
n = 0;
do {
a[n++] = num % MOD;
num /= MOD;
} while(num);
}
bign& operator= (int num)
{ return *this = bign(num); }
bign operator+ (const bign& b) const
{
bign c = bign();
int cn = max(n, b.n), d = 0;
for(int i = 0, x, y; i < cn; i++)
{
x = (n > i) ? a[i] : 0;
y = (b.n > i) ? b.a[i] : 0;
c.a[i] = (x + y + d) % MOD;
d = (x + y + d) / MOD;
}
if(d) c.a[cn++] = d;
c.n = cn;
return c;
}
bign& operator+= (const bign& b)
{
*this = *this + b;
return *this;
}
bign operator* (const bign& b) const
{
bign c = bign();
int cn = n + b.n, d = 0;
for(int i = 0; i <= cn; i++)
c.a[i] = 0;
for(int i = 0; i < n; i++)
for(int j = 0; j < b.n; j++)
{
c.a[i + j] += a[i] * b.a[j];
c.a[i + j + 1] += c.a[i + j] / MOD;
c.a[i + j] %= MOD;
}
while(cn > 0 && !c.a[cn-1]) cn--;
if(!cn) cn++;
c.n = cn;
return c;
}
friend ostream& operator<< (ostream& _cout, const bign& num)
{
printf("%d", num.a[num.n - 1]);
for(int i = num.n - 2; i >= 0; i--)
printf("%04d", num.a[i]);
return _cout;
}
};