题目链接:洛谷-网络流24题-按难度排序
按在洛谷里的难度排序由易到难去刷,而不是原来的题号。
最大流模板:dinic 算法
struct Dinic
{
struct Edge
{
int from, to, cap, flow;
};
int s, t; //节点数,边数,源点编号,汇点编号
vector<Edge> edges; //边表,edges[e]和edges[e^1]互为反向弧
vector<int> G[M]; //邻接表,G[i][j]表示节点i的第j条边在e中的序号
bool vis[M]; //bfs用
int d[M]; //从起点到i的距离
int cur[M]; //当前弧下标
void addEdge(int from, int to, int cap)
{
edges.push_back({from, to, cap, 0});
edges.push_back({to, from, 0, 0});
G[from].push_back(edges.size() - 2);
G[to].push_back(edges.size() - 1);
}
bool BFS()
{
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);
d[s] = 0;
vis[s] = 1;
while(!q.empty())
{
int u = q.front();
q.pop();
for(int id : G[u])
{
Edge &e = edges[id];
if(!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = 1;
d[e.to] = d[u] + 1;
q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int u, int a)
{
if(u == t || a == 0) return a;
int flow = 0, f;
for(int &i = cur[u]; i < (int)G[u].size(); ++i)
{
Edge &e = edges[G[u][i]];
if(d[u] + 1 == d[e.to] &&
(f = DFS(e.to, min(a, e.cap - e.flow))) > 0)
{
e.flow += f;
edges[G[u][i] ^ 1].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
}
return flow;
}
int maxflow(int _s, int _t)
{
s = _s;
t = _t;
int flow = 0;
while(BFS())
{
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
}dinic;
最小割
最小割=最大流
最小费用最大流模板:MCMF
struct MCMF
{
struct Edge
{
int from, to, cap, flow, cost;
Edge(int u, int v, int c, int f, int w) : from(u), to(v), cap(c), flow(f), cost(w) {}
};
vector<Edge>edges;
vector<int>G[M];
int inq[M], p[M], a[M], d[M];
void addEdge(int from, int to, int cap, int cost)
{
edges.push_back(Edge(from, to, cap, 0, cost));
edges.push_back(Edge(to, from, 0, 0, -cost));
G[from].push_back(edges.size() - 2);
G[to].push_back(edges.size() - 1);
}
bool spfa(int s, int t, int &flow, ll &cost)
{
memset(d, 127, sizeof(d));
memset(inq, 0, sizeof(inq));
d[s] = 0;
inq[s] = 1;
p[s] = 0;
a[s] = INF;
queue<int>Q;
Q.push(s);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
inq[u] = 0;
for(int i = 0; i < (int)G[u].size(); i++)
{
Edge &e = edges[G[u][i]];
if(e.cap > e.flow && d[e.to] > d[u] + e.cost)
{
d[e.to] = d[u] + e.cost;
p[e.to] = G[u][i];
a[e.to] = min(a[u], e.cap - e.flow);
if(!inq[e.to])
{
Q.push(e.to);
inq[e.to] = 1;
}
}
}
}
if(d[t] >= INF) return false;
flow += a[t];
cost += (ll)d[t] * (ll)a[t];
for(int u = t; u != s; u = edges[p[u]].from)
{
edges[p[u]].flow += a[t];
edges[p[u] ^ 1].flow -= a[t];
}
return true;
}
pair<int,ll> solve(int s, int t)
{
int flow = 0; ll cost = 0;
while(spfa(s, t, flow, cost));
return {flow, cost};
}
}mcmf;
一共有n(100)个人,其中有m个外国人,给定哪些本国人和外国人可以配对,问最多配多少对,并输出方案。
二分图最大匹配问题,输出答案
环形道路上有n(100)个仓库,每个仓库都存储着一定量的物资。每个仓库可以往周围的仓库搬运物资,求使得所有仓库存储量相同的最小搬运量。
最小费用最大流
一款软件有n(20)个错误,现在有m(100)个补丁,
每个补丁需要在软件有某些特定错误,没有某些特定错误时才能使用。
每个补丁会给软件修复某些特定错误,会给软件添加某些特定错误。
每个补丁有自己的修复耗时。
问将软件的错误全部修复最少耗时多少,无解输出0.
虚假的网络流。
状压+dijkstra过。
有n(55)根柱子,要依次把编号为1,2,3,…的球放在柱子上,每次只能放在柱子最上面且要保证同一柱子中相邻球编号之和均为完全平方数,问最多能放多少个球,输出方案。
反向思考,拆点,最大流
依次枚举每个球数对应的柱子数,直到得到n个柱子最多放下的球数为止。
优化: 为了避免每次枚举都需要求网络流带来的时间浪费,每次求网络流前把所有弧的当前流置0,即可接着上一次的图直接插入再求。
输出方案: 从1到最大球数枚举,依次找到每个球的左部分连着哪个球的右部分,即可输出每个柱子上的答案。可以直接对着最后ans+1个球的网络图输出答案,注意判断边界即可。
int vis[2048]={};
for(int i=1; i<=ans; ++i) if(!vis[i])
{
for(int u=i,v; u; u=v)
{
printf("%d ",u );
vis[u] = 1;
v = 0;
for(auto eid : dinic.G[u*2]) if(dinic.edges[eid].flow==1)
{
v = dinic.edges[eid].to/2;
if(v>ans) v = 0;
break;
}
}
printf("\n");
}
给一个n*m(10*10)的网格地图,网格之间可以连通、有墙或有门,一共有p(10)种门,对应于放在地图中的10把钥匙,每把钥匙可以开无限次门,问从(1,1)走到(n,m)的最短步数。
虚假的网络流,状压bfs过
给一张n*n(100)的网格图,一辆汽车处于格点(1,1)处,有着容量为k单位的油箱,每走一步需要消耗1单位的油。问行驶到(n,n)处的最少花费,价目表如下:
分层图+费用流
参考链接: https://www.luogu.org/blog/I-love-saber/solution-p4009
MCMF mcmf;
inline int encode(int f, int r, int c)
{
return f*101*101 + r*101 + c;
}
int main(void)
{
int n=read(), k=read(), A=read(), B=read(), C=read();
int src = 11*101*101+1, dst = 11*101*101+2;
mcmf.addEdge(src, encode(k,1,1), 1, 0); //源边
for(int i=0; i<=k; ++i) //汇边
mcmf.addEdge(encode(i,n,n), dst, 1, 0);
const int go[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};
for(int r=1; r<=n; ++r)
for(int c=1; c<=n; ++c)
{
int have = read();
for(int i=0; i<k; ++i) //加油
mcmf.addEdge(encode(i,r,c), encode(k,r,c), 1, have?A:A+C);
for(int dir=0; dir<4; ++dir)
{
int nr=r+go[dir][0], nc=c+go[dir][1];
if(nr>=1&&nr<=n&&nc>=1&&nc<=n)
for(int i=(have?k:1); i<=k; ++i) //走格,要求当前位置满油或者没有加油站
mcmf.addEdge(encode(i,r,c), encode(i-1,nr,nc), 1, dir>>1?B:0);
}
}
cout << mcmf.solve(src,dst).second << endl;
return 0;
}
似乎分层之后按最短路也可以做
m(150)个有若干人的单位要在n(270)张可以容纳若干人的餐桌上聚餐,要求同一单位的人餐桌各不相同。输出方案。
最大流
一共有k(20)种试题类型,n道试题(1000),每道试题都属于若干个试题类型。现在要选出m道题且每道题都必须属于给定的一种试题类型,输出方案。
最大流
给定一张有向无环图,求最小路径覆盖,输出方案。
最小路径覆盖是指一个最小的有向路径集合,且图中的每个节点恰好在其中的一条路径上(路径长度可以为0,即只包含1个点)。
和魔术球问题有些相似,最坏情况的路径数等于节点数,通过将拆点连弧获得的最大流即为最多可以合并几条路径。拿节点数一减就是答案。
拆点最大流
输出答案时,可以依次遍历未被访问的所有点,首先沿着反向弧找到路径起点,再依次输出并标记访问过即可。
有n(100)个工作要分配给n个人,已知每个人做每个工作的收益,分别求最小和最大的时间和。
费用流
这道题告诉我们,费用是负数时,费用流也可以很好的求出结果。
有m个实验和n个仪器,做实验会有收入,采购仪器需要花钱,仪器可以重复使用。每个实验依赖于若干不同的器材,求做哪些实验可以获得最大净收益。
最小割模型:最大权闭合子图
对上述网络跑最小割,显然不会割去中间的INF边,所以此时的S集合即为可用的(实验,仪器)组合。注意最小割的数值里包括了(1.不选择的实验收入,2.选择的仪器花费)。此时拿所有实验的总收入减去最小割的数值,就是(1. 选择的实验收入,2.选择的仪器花费的相反数),即为答案。
输出方案即输出除源点外的S集合,这里有一个待研究的性质:dinic最后一次bfs后,vis为1的节点集合即为最小割的S集合。
这题输入有些烦,列代码如下:
Dinic dinic;
int main(void)
{
int m, n; scanf("%d %d\n", &m, &n);
int src=0, dst=m+n+1, sum=0;
for(int exp=1; exp<=m; ++exp)
{
string line; getline(cin, line);
stringstream sin(line);
int earn; sin>>earn; sum+=earn;
int inses=0, ins;
while(sin >> ins)
{
++inses;
dinic.addEdge(exp, ins+m, INF);
}
dinic.addEdge(src, exp, earn);
}
for(int i=m+1; i<=m+n; ++i)
{
int cost = read();
dinic.addEdge(i, dst, cost);
}
int ans = sum-dinic.maxflow(src, dst);
for(int i=1; i<=m; ++i) if(dinic.vis[i])
printf("%d ",i );
printf("\n");
for(int i=m+1; i<=m+n; ++i) if(dinic.vis[i])
printf("%d ",i-m );
printf("\n");
cout << ans << endl;
return 0;
}
有m个存有货物的仓库和n个需要货物的商店,给定每个仓库向每个商店运输的单位花费,求最小的总运输花费。
费用流裸题。
m*n的网格中存放着数字,现在要从方格中取数,使得数字之和最大且任意两个被选中的方格没有相邻边。
最小割模型:二分图最大权独立集
弧:
(源点,偶数点,点权)
(偶数点,相邻奇数点,INF)
(奇数点,汇点,点权)
答案 = 所有点的点权之和 - 最小割。
给定长为n(500)的数字序列,求
第一问
首先使用O(n^2)或者O(nlogn)方法求得LIS长度,并且记录以每个位置为结尾的最长不下降子序列长度dp[i]。
第二问
建立网络图:
(源点,dp为1的点,1)
(i,i的后继,1),节点 j j j是 i i i的后继当且仅当 j > i j>i j>i, s a v e [ j ] > s a v e [ i ] save[j]>save[i] save[j]>save[i], d p [ j ] = d p [ i ] + 1 dp[j]=dp[i]+1 dp[j]=dp[i]+1
(dp为s的点,汇点,1)
最大流即为答案。
第三问
建立网络图:
(源点,dp为1的点,i==1?INF:1
)
(i,i的后继,1)
(dp为s的点,汇点,i==n?INF:1
)
最大流即为答案。
第三问有坑点,需要特判n=1的情况。
给定一条实直线,n(500)个直线上开端点的线段,一个正整数k。求一个线段集合,使得集合中的线段在直线上的覆盖次数不超过k,且集合中线段长度之和最大。
费用流经典题:流量控制
此时最大费用即是答案。
这道题中用主干流上的流量来表示还可以被覆盖的次数,每次脱离主干流时流量减一费用加上对应权值,汇入主干流后流量又增加。
给定一个P*Q的网格,每段网格边有对应的价值,现在在几个选定位置释放机器人,每个位置有固定的释放数量。每个机器人只能向东或向南走,每段价值只能收集一次。有几个选定位置可以收集机器人,有固定的收集数量。问最终收集的最大价值。
多源多汇费用流
有一个经典做法可以处理只能收集一次的价值:建立两组边
(a,b,1,value)
(a,b,0,INF)
在n*n的棋盘上有m个障碍,计算棋盘上最多可以放多少个骑士(马)使得它们不能互相攻击。(国际象棋不蹩马腿。)
可以证明,棋盘上的格子及互相攻击的关系构成了一张二分图。
最小割模型:二分图最大独立集
因为没有涉及到权,只需要对(源点,集合A),(集合A,相连的集合B),(集合B,汇点)都建立容量为1的边即可。
答案 = 总的可用点数 - 最小割。
给定一个平面上的n条开线段,一个正整数k。求一个线段集合,使得集合中线段在x轴上的投影覆盖次数不超过k,且集合中线段长度最大。
费用流:流量控制,分点
类似于P3358 最长k可重区间集问题,但是会有垂直于x轴的线段的情况。
所以要把直线上每个点分成左部分和右部分,垂直于x轴的线段从一个点的左部分连到右部分。其它线段从左端点的右部分连道右端点的左部分。
餐厅在未来的N天内需要不尽相同的餐巾数量,餐巾使用一次就会脏。
1)在需要时可以购买餐巾,每条花费为p。
2)可以把脏餐巾送去快洗,快洗每条花费为f,需要m天洗好。
3)可以把脏餐巾送去慢洗,慢洗每条花费为s,需要n天洗好。
4)脏餐巾可以暂时不洗。
求总的最小花费。
费用流:分点,反向源汇边(?)
餐巾计划问题-费用流
这道题分点好想,但是分点之后无法进行建图。
把每一天分成早上和晚上,正确的做法是从源点向晚上连边,表示获得了脏餐巾,从早上向汇点连边,表示消耗了干净餐巾。
一个神奇的模型。
给定一个n行,第一行有m个数字的数字梯形。现在要从顶部m个数字开始找到m条路径,求分别满足如下规则时的最大m条路径上数字之和。
1)m条路径互不相交。
2)数字节点处可以相交,边不能相交。
3)数字节点和边都可以相交。
费用流:分点,多模型
因为数字上有价值,自然考虑分点,上部分与上层节点相连,下部分与下层节点相连,均无费用,称为外部弧。数字内部上部分与下部分节点相连,称为内部弧,有费用(价值)。
注意,在三个子问题中源点向顶层节点上部分的弧容量总是1.
在问题1中,底层节点下部分向汇点的弧容量是1,问题2和3中是INF。
分别求最大费用即可。
放一下自认为写的不错的代码:
MinCostMaxFlow pro1, pro2, pro3;
inline int upp(int i, int j){return (i*64+j)*2;}
inline int dow(int i, int j){return (i*64+j)*2+1;}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int m=read(), n=read();
int src=0, dst=1;
for(int i=1; i<=n; ++i) for(int j=1; j<=m+i-1; ++j)
{
if(i==1) //源边
{
pro1.addEdge(src, upp(i,j), 1, 0);
pro2.addEdge(src, upp(i,j), 1, 0);
pro3.addEdge(src, upp(i,j), 1, 0);
}
int v=read(); //拆点边
pro1.addEdge(upp(i,j), dow(i,j), 1, -v);
pro2.addEdge(upp(i,j), dow(i,j), INF, -v);
pro3.addEdge(upp(i,j), dow(i,j), INF, -v);
for(int nj:{j,j+1}) //下层边
{
pro1.addEdge(dow(i,j), upp(i+1, nj), 1, 0);
pro2.addEdge(dow(i,j), upp(i+1, nj), 1, 0);
pro3.addEdge(dow(i,j), upp(i+1, nj), INF, 0);
}
if(i==n) //汇边
{
pro1.addEdge(dow(i,j), dst, 1, 0);
pro2.addEdge(dow(i,j), dst, INF, 0);
pro3.addEdge(dow(i,j), dst, INF, 0);
}
}
cout << -pro1.solve(src,dst).second << endl;
cout << -pro2.solve(src,dst).second << endl;
cout << -pro3.solve(src,dst).second << endl;
return 0;
}
在n*m的网格中,每个网格要么是空地,要么是障碍,要么是具有价值的资源。现在在(1,1)放下P个机器人,每个机器人只能朝南或东移动,机器人可以在(n,m)被回收。要求在机器人回收数量最多的前提下收集到的资源价值最大。输出所有机器人的路径。
费用流:分点,一次价值,输出路径
因为是节点有价值,所以要分点。
因为价值只能收集一次,所以建立两条边(1,v)和(INF,0)。
输出路径可以考虑dfs,每次走一步就给边上流量减一。
给定一张图,找到一条从最左侧点到最右侧点再回到最左侧点的经过节点最多的路径。输出答案。
费用流:分点,输出路径
模型类似于[P4013 数字梯形问题]的问题一。
当最大流大于等于2时有解,输出路径也可采用dfs。
注意处理1->n->1的情况。
这道题还涉及字符串与数字的转化,码量较大。
MinCostMaxFlow mcmf;
struct Discretization
{
vector<string> save;
map<string, int> dcter;
inline int get(string name)
{
if(dcter.count(name))
return dcter[name];
save.push_back(name);
return dcter[name] = save.size();
}
inline string get(int id) {return save[id-1];}
}dct; //将城市名映射为1-n的数字
/*
费用流
节点:
源点: 城市1的左部分
汇点: 城市n的右部分
城市i: 左部分i*2, 右部分i*2+1
弧:
(城市左部分, 城市右部分, 1, -1), 城市为1或n时容量为2
(左边城市右部分, 右边城市左部分, INF, 0)
最小费用最大流:
最大流>=2, 表示有解.
输出路径
*/
inline int lef(int i){return i*2;}
inline int rig(int i){return i*2+1;}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int n=read(), v=read();
for(int i=1; i<=n; ++i)
{
string name; cin>>name; dct.get(name);
mcmf.addEdge(lef(i), rig(i), i==1||i==n?2:1, -1);
}
while(v--)
{
string city1, city2;
cin >> city1 >> city2;
int c1 = dct.get(city1), c2=dct.get(city2);
if(c1>c2) swap(c1, c2);
mcmf.addEdge(rig(c1), lef(c2), INF, 0);
}
auto res = mcmf.solve(lef(1), rig(n));
if(res.first<2)
{
printf("No Solution!\n" );
return 0;
}
vector<int> ans[2];
for(int i=0; i<2; ++i)
{
int now = 1;
while(now!=n)
{
for(int eid : mcmf.G[rig(now)]) if(mcmf.edges[eid].flow>0)
{
--mcmf.edges[eid].flow;
ans[i].push_back(now);
now = mcmf.edges[eid].to/2;
break;
}
}
}
ans[0].push_back(n);
ans[0].insert(ans[0].end(),ans[1].rbegin(), ans[1].rend());
cout << ((int)ans[0].size()-1) << endl;
for(int c : ans[0])
{
cout << dct.get(c) << endl;
}
return 0;
}
有n(13)个空间站,编号为1到n。此外,地球的编号为0,月球编号为-1。现在有m(20)个宇宙飞船,每个都有一定容量,且在某几个空间站或星球之间周期性移动。现在有k(50)个人在地球上,需要被送到月球上。求最小的时间。
按时间分层的最大流算法(zht算法)
整体思路是使用并查集判断是否有解,再按时间分层建图,然后枚举时间在残量网络中跑最大流看什么时候最大流大于等于人数,当前时间就是答案。
需要注意的是,因为没有单独建源点和汇点,跑出的最大流可能会恰大于人数k,如果以等于人数k去判断,会TLE到爆炸(血泪教训)。
因为是实际意义上的最后一篇,放一下代码。
需要C++11
1. dinic板子
可以调用的函数一共有两个,分别是添加边的addEdge和求最大流的maxflow。
addEdge会自动添加反向弧。
struct Dinic
{
void addEdge(int from, int to, int cap);
int maxflow(int _s, int _t);
}dinic;
需要两个全局常数 const int M = 100016, INF = 1000000007;
2. 飞船
get_path可以接收天数,返回某条弧的起始点。
struct SpaceShip
{
int limit; //人数限制
int cycle; //周期
vector<int> path; //路径
pair<int,int> get_path(int day)
{
return {path[day%cycle], path[(day+1)%cycle]};
}
}ship[32];
3. 并查集
为了减少全局变量和全局函数强行struct。
struct UnionFindSet
{
int fa[20]={};
void init()
{
for(int i=1;i<20;++i)
fa[i] = i;
}
int find(int a)
{
return fa[a]==a ? a : fa[a]=find(fa[a]);
}
void add(int a, int b)
{
fa[find(a)] = find(b);
}
bool check(int a, int b)
{
return find(a) == find(b);
}
}ufs;
4. 主干代码: 读入与判断无解
int n=read(), m=read(), k=read(); //太空站数,太空船数,人数
ufs.init();
for(int i=0; i<m; ++i)
{
ship[i].limit = read();
ship[i].cycle = read();
for(int j=0; j<ship[i].cycle; ++j)
{
int star=read();
if(star==-1) star=n+1; //将月球的-1变成n+1
ship[i].path.push_back(star);
ufs.add(star, ship[i].path[0]);
}
}
if(!ufs.check(0,n+1))
{
printf("0\n");
return 0;
}
5. 主干代码:枚举答案
for(int day=1, maxflow=0; ; ++day)
{
int off1 = (day-1)*(n+2), off2 = day*(n+2); //上一层和本层的偏移量
for(int i=0; i<=n+1; ++i)
dinic.addEdge(off1+i, off2+i, INF); //不转移
for(int i=0, s, t; i<m; ++i)
{
tie(s,t) = ship[i].get_path(day-1);;
dinic.addEdge(off1+s, off2+t, ship[i].limit); //转移
}
maxflow += dinic.maxflow(0, off2+n+1);
if(maxflow>=k)
{
printf("%d\n",day );
return 0;
}
}
除了这些之外,还有一个快读板子,因为和题目关系不大,就不单独列出了。
完整代码
/* LittleFall : Hello! */
#include
using namespace std; typedef long long ll; inline int read();
const int M = 100016, INF = 1000000007;
struct Dinic
{
struct Edge
{
int from, to, cap, flow;
};
int s, t; //节点数,边数,源点编号,汇点编号
vector<Edge> edges; //边表,edges[e]和edges[e^1]互为反向弧
vector<int> G[M]; //邻接表,G[i][j]表示节点i的第j条边在e中的序号
bool vis[M]; //bfs用
int d[M]; //从起点到i的距离
int cur[M]; //当前弧下标
void addEdge(int from, int to, int cap)
{
edges.push_back({from, to, cap, 0});
edges.push_back({to, from, 0, 0});
G[from].push_back(edges.size() - 2);
G[to].push_back(edges.size() - 1);
}
bool BFS()
{
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);
d[s] = 0;
vis[s] = 1;
while(!q.empty())
{
int u = q.front();
q.pop();
for(int id : G[u])
{
Edge &e = edges[id];
if(!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = 1;
d[e.to] = d[u] + 1;
q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int u, int a)
{
if(u == t || a == 0) return a;
int flow = 0, f;
for(int &i = cur[u]; i < (int)G[u].size(); ++i)
{
Edge &e = edges[G[u][i]];
if(d[u] + 1 == d[e.to] &&
(f = DFS(e.to, min(a, e.cap - e.flow))) > 0)
{
e.flow += f;
edges[G[u][i] ^ 1].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
}
return flow;
}
int maxflow(int _s, int _t)
{
s = _s;
t = _t;
int flow = 0;
while(BFS())
{
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
}dinic;
/* 按时间分层的最大流算法
层: 表示时间,从0开始,每层有(n+2)的偏移.
节点:
源点: 0
汇点: (n+2)*t+n+1
第f层地球: (n+2)*f+0
第f层空间站i: (n+2)*f+i
第f层月球: (n+2)*f+n+1.
弧:
(t层节点, t+1层对应节点, INF)
(t层节点, t时刻飞船路径到达的节点, 飞船容量)
最大流:
满足最大流等于初始人数的最小时间t即为答案
*/
struct SpaceShip
{
int limit;
int cycle;
vector<int> path;
pair<int,int> get_path(int day)
{
return {path[day%cycle], path[(day+1)%cycle]};
}
}ship[32];
struct UnionFindSet
{
int fa[20]={};
void init()
{
for(int i=1;i<20;++i)
fa[i] = i;
}
int find(int a)
{
return fa[a]==a ? a : fa[a]=find(fa[a]);
}
void add(int a, int b)
{
fa[find(a)] = find(b);
}
bool check(int a, int b)
{
return find(a) == find(b);
}
}ufs;
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int n=read(), m=read(), k=read(); //太空站数,太空船数,人数
ufs.init();
for(int i=0; i<m; ++i)
{
ship[i].limit = read();
ship[i].cycle = read();
for(int j=0; j<ship[i].cycle; ++j)
{
int star=read();
if(star==-1) star=n+1;
ship[i].path.push_back(star);
ufs.add(star, ship[i].path[0]);
}
}
if(!ufs.check(0,n+1))
{
printf("0\n");
return 0;
}
for(int day=1, maxflow=0; ; ++day)
{
int off1 = (day-1)*(n+2), off2 = day*(n+2); //上一层和本层的偏移量
for(int i=0; i<=n+1; ++i)
dinic.addEdge(off1+i, off2+i, INF); //不转移
for(int i=0, s, t; i<m; ++i)
{
tie(s,t) = ship[i].get_path(day-1);;
dinic.addEdge(off1+s, off2+t, ship[i].limit); //转移
}
maxflow += dinic.maxflow(0, off2+n+1);
if(maxflow>=k)
{
printf("%d\n",day );
return 0;
}
}
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}