引言
对于求解有向图的最小均值环的问题一般由两种做法:【二分均值+spfa判定负环】【公式法】,本文将对两种做法进行推导,并提供相应的例题和变式训练。
本文以[Usaco2006 Mar]Milk Team Select产奶比赛为例题对两种方法进行介绍
法一:二分均值+spfa判定负环
原理详解:
设有向图中的所有回路的最小平均权值为ans,于是可以对ans进行二分处理,不过注意到当题目给出的条件中未明确要求环的权值非负的时候,ans可以是负数,这时候需要特殊处理。这里假设ans>=0,那么二分ans,每次对ans进行检查,检查的方式是先将所有边权都减去一个ans,再对改建后的图跑spfa,判定是否存在负环,如果没有负环,说明ans偏小,否则偏大。对于spfa而言可以采用dfs优化的spfa,能够优化运行速度,不过spfa最坏情况下仍然会被卡成
O
(
∣
V
∣
∣
E
∣
)
O(|V||E|)
O(∣V∣∣E∣)。
总复杂度:
O
(
∣
V
∣
∣
E
∣
l
o
g
(
1
e
9
)
)
O(|V||E|log(1e9))
O(∣V∣∣E∣log(1e9))
代码详解:
后续待补充。。。
法二:公式法
Karp在1977年的论文,讲述了一种
O
(
∣
V
∣
∣
E
∣
)
O(|V||E|)
O(∣V∣∣E∣)的算法,算法的核心是一个公式:
m
i
n
v
∈
V
{
m
a
x
0
≤
k
≤
n
−
1
{
F
[
n
]
[
v
]
−
F
[
k
]
[
v
]
n
−
k
}
}
=
a
n
s
\mathbf{min_{v\in V}\{max_{0\le k\le n-1}\{\frac{F[n][v]-F[k][v]}{n-k}\}\}=ans}
minv∈V{max0≤k≤n−1{n−kF[n][v]−F[k][v]}}=ans
其中
F
[
k
]
[
v
]
\mathbf{F[k][v]}
F[k][v]代表从s点出发经过k条边到达v点的最短距离(s点任意选取),适用条件为强联通图。
下面先给出Karp的原始论文,接着给出中文详细推导。
原论文:
个人详细推导:
前置定义:
1.设s为出发点,我们记
F
[
k
]
[
v
]
\mathbf{F\lbrack k\rbrack\lbrack v\rbrack}
F[k][v]为从s点出发经过k条边到达v点的最短距离。
2.设ans为所有回路的最小平均权值。
3.设
d
i
s
t
[
v
]
\mathbf{dist\lbrack v\rbrack}
dist[v]为从s点出发到达 v点的最小权值(暂时不考虑图中存在负环的情况)
现在从最简单的情形出发,进而推广到一般情
况。
考虑一张含有n个点的强联通图上存在边权和为0的环,后面称之为零环(注意零环不是指边权为0,而是边权和为0),且不存在负环,那么有如下两个结论:
1.
a
n
s
=
0
\mathbf{1.ans=0}
1.ans=0
2.
d
i
s
t
[
v
]
=
m
i
n
0
≤
k
≤
n
−
1
{
F
[
k
]
[
v
]
}
\mathbf{2.dist[v]=min_{0\le k\le n-1}\{F[k][v]\}}
2.dist[v]=min0≤k≤n−1{F[k][v]}
结论1即假设,容易理解。
结论2可以用反正法,当
k
≥
n
k\ge n
k≥n的时候,从s点出发经过的边数大于n条边,意味着会经过n+1个点,大于全图的总点数。因此会有点被重复经过,也就形成了环,由于环非负,因此去掉该环所形成的路径长度一定会更优,因此
k
<
n
k< n
k<n一定成立。
对结论2的公式做如下变形:
F
[
n
]
[
v
]
−
d
i
s
t
[
v
]
=
F
[
n
]
[
v
]
−
m
i
n
0
≤
k
≤
n
−
1
{
F
[
k
]
[
v
]
}
=
m
a
x
0
≤
k
≤
n
−
1
{
F
[
n
]
[
v
]
−
F
[
k
]
[
v
]
}
\mathbf{F\lbrack n\rbrack\lbrack v\rbrack-dist\lbrack v\rbrack=F\lbrack n\rbrack\lbrack v\rbrack-min_{0\le k\le n-1}\{F[k][v]\}=max_{0\le k\le n-1}\{F[n][v]-F[k][v]\}}
F[n][v]−dist[v]=F[n][v]−min0≤k≤n−1{F[k][v]}=max0≤k≤n−1{F[n][v]−F[k][v]}
由于
F
[
n
]
[
v
]
≥
d
i
s
t
[
v
]
\mathbf{F[n][v]\ge dist[v]}
F[n][v]≥dist[v],因此
m
a
x
0
≤
k
≤
n
−
1
{
F
[
n
]
[
v
]
−
F
[
k
]
[
v
]
}
≥
0
\mathbf{max_{0\le k\le n-1}\{F[n][v]-F[k][v]\}\ge0}
max0≤k≤n−1{F[n][v]−F[k][v]}≥0
接下来我将证明存在一个点v使得这个等于号一定
可以被取到
现在的目的是找到一个点v使得
F
[
n
]
[
v
]
=
d
i
s
t
[
v
]
\mathbf{F[n][v]=dist[v]}
F[n][v]=dist[v]。
构造方法:由于图中存在零环,不妨从零环上任取一个点x,然后选定一条从s点到x的最短路径,沿着最短路径从s点走到x点,由前面的推导可知这条路径长度不会大于n-1,为了构造出n条边的路径,因此从x点出发,沿着这个零环继续走,直到走到一个点v使得s->x->v的路径长度恰好为n(见下图1)。
然后可以得到这样一个断言:通过这种构造方式得到的路径一定是从s点到v点的一条最短路。
下面通过反证法证明这个断言是正确的。
假设上述路径并不是从s点到v点的一条最短路径,那么必然存在另外一条更短的路径到达v点(见下图2)
我们设这条更短的路径为s->y->v,即图中沿着紫色箭头走的路径。
下面约定
v
a
l
点
1
点
2
点
3..
点
n
\mathbf{val_{点1点2点3..点n}}
val点1点2点3..点n 为沿着点1->点2->点3->…->点n的路径上的权值之和。
由于零环的边权之和为0,所以
v
a
l
v
g
x
+
v
a
l
v
h
x
=
0
\mathbf{val_{vgx}+val_{vhx}=0}
valvgx+valvhx=0。
由于
v
a
l
v
g
x
\mathbf{val_{vgx}}
valvgx与
v
a
l
v
h
x
\mathbf{val_{vhx}}
valvhx地位对等,因此不妨设
v
a
l
v
g
x
≤
0
\mathbf{val_{vgx}\le 0}
valvgx≤0。
则
v
a
l
v
h
x
≥
0
\mathbf{val_{vhx}\ge 0}
valvhx≥0,故
v
a
l
s
y
v
g
x
=
v
a
l
s
y
v
+
v
a
l
v
g
x
≤
v
a
l
s
y
v
+
0
=
v
a
l
s
y
v
]
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
(
1
)
\mathbf{val_{syvgx}= val_{syv}+val_{vgx}\le val_{syv}+0=val_{syv}]}.................(1)
valsyvgx=valsyv+valvgx≤valsyv+0=valsyv].................(1)
v
a
l
s
x
g
v
=
d
i
s
t
[
x
]
+
v
a
l
x
g
v
=
d
i
s
t
[
x
]
+
v
a
l
v
g
x
≤
d
i
s
t
[
x
]
+
0
=
d
i
s
t
[
x
]
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
(
2
)
\mathbf{val_{sxgv}=dist[x]+val_{xgv}=dist[x]+val_{vgx}\le dist[x]+0=dist[x]}.....................(2)
valsxgv=dist[x]+valxgv=dist[x]+valvgx≤dist[x]+0=dist[x].....................(2)
由于假设s->y->v是一条比上文中构造法形成的路径更短的一条s到v的路径,即假设
v
a
l
s
y
v
<
v
a
l
s
x
g
v
\mathbf{val_{syv}<val_{sxgv}}
valsyv<valsxgv成立。
代入(1)(2)式后,也就转化为
v
a
l
s
y
v
g
x
≤
v
a
l
s
y
v
<
v
a
l
s
x
g
v
≤
d
i
s
t
[
x
]
\mathbf{val_{syvgx}\le val_{syv}<val_{sxgv}\le dist[x]}
valsyvgx≤valsyv<valsxgv≤dist[x]
即
v
a
l
s
y
v
g
x
<
d
i
s
t
[
x
]
\mathbf{val_{syvgx}<dist[x]}
valsyvgx<dist[x]
而由前文定义可知
d
i
s
t
[
x
]
\mathbf{dist[x]}
dist[x]代表从s点到x点的最短路径,现在发现一条s->y->v->g->x更短的路径,说明与定义矛盾!故初始假设错误,也就是说前面的构造法构造出来的s->x->v的路径一定是从s点到v点的最短路径,并且这条路径长度为n,也就是满足了该等式:
F
[
n
]
[
v
]
=
d
i
s
t
[
v
]
\mathbf{F[n][v]=dist[v]}
F[n][v]=dist[v]
现在改写一下等式:
m
i
n
v
∈
V
{
F
[
n
]
[
v
]
−
d
i
s
t
[
v
]
}
=
0
⇒
\mathbf{min_{v\in V}\{F[n][v]-dist[v]\}=0\Rightarrow}
minv∈V{F[n][v]−dist[v]}=0⇒
m
i
n
v
∈
V
{
m
a
x
0
≤
k
≤
n
−
1
{
F
[
n
]
[
v
]
−
F
[
k
]
[
v
]
}
}
=
0
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
(
3
)
\mathbf{min_{v\in V}\{max_{0\le k\le n-1}\{F[n][v]-F[k][v]\}\}=0}.......................(3)
minv∈V{max0≤k≤n−1{F[n][v]−F[k][v]}}=0.......................(3)
最后这个等式(3)就是这个算法的核心。
现在我们开始考虑更加一般的情形,假设是任意一张强联通图,我们考虑为每条边减去一个边权c(可以为负),使得整张图的所有环中权值和最小为0。在这种情况下(3)式一定成立,然后考虑再将图“变回去”,也就是对所有边权执行一个“加c”的操作。观察(3)式会做何变化:
F
[
n
]
[
v
]
+
=
n
∗
c
\mathbf{F[n][v]+=n*c}
F[n][v]+=n∗c
F
[
k
]
[
v
]
+
=
k
∗
c
\mathbf{F[k][v]+=k*c}
F[k][v]+=k∗c
(3)式的结果相应地会变成
(
n
−
k
)
∗
c
\mathbf{(n-k)*c}
(n−k)∗c
如果将(3)式除以
(
n
−
k
)
\mathbf{(n-k)}
(n−k),结果也就会变成c,我们可以得到如下等式:
m
i
n
v
∈
V
{
m
a
x
0
≤
k
≤
n
−
1
{
F
[
n
]
[
v
]
−
F
[
k
]
[
v
]
n
−
k
}
}
=
c
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
(
4
)
\mathbf{min_{v\in V}\{max_{0\le k\le n-1}\{\frac{F[n][v]-F[k][v]}{n-k}\}\}=c}.......................(4)
minv∈V{max0≤k≤n−1{n−kF[n][v]−F[k][v]}}=c.......................(4)
而这个c不正是原图中的最小平均权值吗,所以这个神奇的等式就被巧妙地推导出来了,(4)式即是我们想要并最终证明的公式。
代码详解:
要利用上述公式实现具体的算法,还需要得到预处理出
F
[
k
]
[
v
]
\mathbf{F[k][v]}
F[k][v]的值,利用dp的思想,很容易得到如下递推关系式:
F
[
k
]
[
v
]
=
m
i
n
边
u
−
>
v
存
在
{
F
[
k
−
1
]
[
u
]
+
w
[
u
]
[
v
]
}
\mathbf{F[k][v]=min_{边u->v存在}\{F[k-1][u]+w[u][v]\}}
F[k][v]=min边u−>v存在{F[k−1][u]+w[u][v]}
由于k最大更新到n,因此我们只需要
O
(
∣
V
∣
∣
E
∣
)
\mathbf{O(|V||E|)}
O(∣V∣∣E∣)的预处理即可。而
m
i
n
v
∈
V
{
m
a
x
0
≤
k
≤
n
−
1
{
F
[
n
]
[
v
]
−
F
[
k
]
[
v
]
n
−
k
}
\mathbf{min_{v\in V}\{max_{0\le k\le n-1}\{\frac{F[n][v]-F[k][v]}{n-k}\}}
minv∈V{max0≤k≤n−1{n−kF[n][v]−F[k][v]}
该式子可以
O
(
∣
V
∣
2
)
\mathbf{O(|V|^2)}
O(∣V∣2)更新答案,于是总复杂度为
O
(
∣
V
∣
(
∣
V
∣
+
∣
E
∣
)
)
\mathbf{O(|V|(|V|+|E|))}
O(∣V∣(∣V∣+∣E∣)),对于稠密图而言复杂度约为
O
(
∣
V
∣
∣
E
∣
)
\mathbf{O(|V||E|)}
O(∣V∣∣E∣)。
总复杂度: O ( ∣ V ∣ ∣ E ∣ ) \mathbf{O(|V||E|)} O(∣V∣∣E∣)
后续待补充。。。
推广:
例题一
题目来源:The 2019 China Collegiate Programming Contest Harbin Site(2019CCPC哈尔滨站)A题:Artful Paintings
题面:
官方题解:
(方法一实现代码):
采用二分边权+spfa判负环的思路。首先,设被涂黑的
格子个数为x,显然x越大越容易满足条件,于是二分x
的值,注意到有三种类型的边会包含x的边权:第二种
限制条件下建的边(由r到l-1建边,边权为x-k)、由n到0
建边,边权为x、由0到n建边,边权为-x。二分x以后,
就从0点出发开始跑spfa看是否存在负环,注意到0
一定可以到达全图所有的点,经过所有的边。
不过本题需要注意的是要对spfa进行优化。
spfa优化一: 根据建图特点,我们可以发现任意一个非0节点都可以经过若干条边权为0的边到达0点,也就是说,从0点出发,如果到达某个非0节点时的距离为负数,那么再从该非0节点经过若干条0边权回到0节点,就一定能够形成一个负环。所以当发现某个节点v满足dist[v]<0的时候就可以得知图中存在负环,这时候可以停止跑spfa。
spfa优化二: 考虑SLF优化,SLF优化的具体内容是,对于每一个新入队的节点v,如果满足dist[v]<dist[队首节点],就可以将该节点提前加到队首,否则加到队尾。
spfa优化三: 考虑LLL优化,不过很遗憾,我尝试着在spfa中加入LLL优化,最终却取得了TLE的结果,因此本题中不适用LLL优化,不过具体原因是为什么,受限于我的能力还暂时不能给出解答。
不加优化时程序会TLE,如果加了优化一就可以跑出140ms的好成绩,加上优化二可以跑出31ms的好成绩。
#include <bits/stdc++.h>
#define FOR(i,a,b) for(register int i=(a);i<(b);++i)
#define ROF(i,a,b) for(register int i=(a);i>=(b);--i)
#define pi pair<int,int>
#define mk(a,b) make_pair(a,b)
#define mygc(c) (c)=getchar()
#define mypc(c) putchar(c)
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef double db;
const int maxn = 3005;
const int maxm = 30000;
const int inf = 2147483647;
typedef long long ll;
const double eps = 1e-9;
const long long INF = 9223372036854775807ll;
ll qpow(ll a,ll b,ll c){ll ans=1;while(b){if(b&1)ans=ans*a%c;a=a*a%c;b>>=1;}return ans;}
inline void rd(int *x){int k,m=0;*x=0;for(;;){mygc(k);if(k=='-'){m=1;break;}if('0'<=k&&k<='9'){*x=k-'0';break;}}for(;;){mygc(k);if(k<'0'||k>'9')break;*x=(*x)*10+k-'0';}if(m)(*x)=-(*x);}
inline void rd(ll *x){int k,m=0;*x=0;for(;;){mygc(k);if(k=='-'){m=1;break;}if('0'<=k&&k<='9'){*x=k-'0';break;}}for(;;){mygc(k);if(k<'0'||k>'9')break;*x=(*x)*10+k-'0';}if(m)(*x)=-(*x);}
inline void rd(db *x){scanf("%lf",x);}
inline int rd(char c[]){int i,s=0;for(;;){mygc(i);if(i!=' '&&i!='\n'&&i!='\r'&&i!='\t'&&i!=EOF) break;}c[s++]=i;for(;;){mygc(i);if(i==' '||i=='\n'||i=='\r'||i=='\t'||i==EOF) break;c[s++]=i;}c[s]='\0';return s;}
inline void rd(int a[],int n){FOR(i,0,n)rd(&a[i]);}
inline void rd(ll a[],int n){FOR(i,0,n)rd(&a[i]);}
template <class T, class S> inline void rd(T *x, S *y){rd(x);rd(y);}
template <class T, class S, class U> inline void rd(T *x, S *y, U *z){rd(x);rd(y);rd(z);}
template <class T, class S, class U, class V> inline void rd(T *x, S *y, U *z, V *w){rd(x);rd(y);rd(z);rd(w);}
inline void wr(int x){if(x < 10) putchar('0' + x); else wr(x / 10), wr(x % 10);}
inline void wr(int x, char c){int s=0,m=0;char f[10];if(x<0)m=1,x=-x;while(x)f[s++]=x%10,x/=10;if(!s)f[s++]=0;if(m)mypc('-');while(s--)mypc(f[s]+'0');mypc(c);}
inline void wr(ll x, char c){int s=0,m=0;char f[20];if(x<0)m=1,x=-x;while(x)f[s++]=x%10,x/=10;if(!s)f[s++]=0;if(m)mypc('-');while(s--)mypc(f[s]+'0');mypc(c);}
inline void wr(db x, char c){printf("%.15f",x);mypc(c);}
inline void wr(const char c[]){int i;for(i=0;c[i]!='\0';i++)mypc(c[i]);}
inline void wr(const char x[], char c){int i;for(i=0;x[i]!='\0';i++)mypc(x[i]);mypc(c);}
template<class T> inline void wrn(T x){wr(x,'\n');}
template<class T, class S> inline void wrn(T x, S y){wr(x,' ');wr(y,'\n');}
template<class T, class S, class U> inline void wrn(T x, S y, U z){wr(x,' ');wr(y,' ');wr(z,'\n');}
template<class T> inline void wra(T x[], int n){int i;if(!n){mypc('\n');return;}FOR(i,0,n-1)wr(x[i],' ');wr(x[n-1],'\n');}
int n,ee=0,h[maxn],x,vis[maxn],d[maxn],cnt[maxn];
struct Edge{
int v,tp,w,next;
}e[maxm];
void addedge(int u,int v,int w,int tp){
e[ee]=Edge{v,tp,w,h[u]};
h[u]=ee++;
}
bool spfa(){
deque<int>q;
memset(vis,0,sizeof(int)*(n+1));
memset(d,0x3f,sizeof(int)*(n+1));
memset(cnt,0,sizeof(int)*(n+1));
d[0]=0;
cnt[0]=1;
q.push_front(0);
vis[0]=1;
while(!q.empty()){
int u=q.front();q.pop_front();
vis[u]=0;
for(register int i=h[u];i!=-1;i=e[i].next){
int v=e[i].v,w=e[i].w+e[i].tp*x;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
if(d[v]<0)return 1;//根据建图特点优化:TLE->140ms
if(vis[v])continue;
cnt[v]++;
if(cnt[v]>n)return 1;
if(!q.empty() && d[v]<d[q.front()])q.push_front(v);//SLF优化:140ms->31ms
else q.push_back(v);
vis[v]=1;
}
}
}
return 0;
}
int main(){
int t;rd(&t);
while(t--){
int m1,m2;
rd(&n,&m1,&m2);
ee=0;
memset(h,-1,sizeof(int)*(n+1));
FOR(i,1,n+1)addedge(i-1,i,1,0),addedge(i,i-1,0,0);
while(m1--){
int l,r,k;
rd(&l,&r,&k);
addedge(r,l-1,-k,0);
}
while(m2--){
int l,r,k;
rd(&l,&r,&k);
addedge(l-1,r,-k,1);
}
addedge(0,n,0,1);
addedge(n,0,0,-1);
int l=0,r=n,ans=-1;
while(l<=r){
int mid=l+r>>1;
x=mid;
bool res=spfa();
if(res)l=mid+1;
else{
r=mid-1;
ans=x;
}
}
printf("%d\n",ans);
}
}
(方法二实现代码):
与上述官方题解不相同,由于作者能力有限,未能
实现官方题解中g[i][j]的更新过程,因此在本代码中
将采用队列来实现对边的松弛操作,导致复杂度不优
,无法通过本题的测评,会超时,也希望高手能够
提供更为优秀的解法。本题个人推荐采用方法一实现。
\\运用方法二(公式法实现),实测按照传统更新方式无法通过本题,推荐采用方法一
#include <bits/stdc++.h>
#define FOR(i,a,b) for(register int i=(a);i<(b);++i)
#define ROF(i,a,b) for(register int i=(a);i>=(b);--i)
#define pi pair<int,int>
#define mk(a,b) make_pair(a,b)
#define mygc(c) (c)=getchar()
#define mypc(c) putchar(c)
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef double db;
const int maxn = 3005;
const int maxm = 20000;
const int inf = 2147483647;
typedef long long ll;
const double eps = 1e-9;
const long long INF = 9223372036854775807ll;
ll qpow(ll a,ll b,ll c){ll ans=1;while(b){if(b&1)ans=ans*a%c;a=a*a%c;b>>=1;}return ans;}
inline void rd(int *x){int k,m=0;*x=0;for(;;){mygc(k);if(k=='-'){m=1;break;}if('0'<=k&&k<='9'){*x=k-'0';break;}}for(;;){mygc(k);if(k<'0'||k>'9')break;*x=(*x)*10+k-'0';}if(m)(*x)=-(*x);}
inline void rd(ll *x){int k,m=0;*x=0;for(;;){mygc(k);if(k=='-'){m=1;break;}if('0'<=k&&k<='9'){*x=k-'0';break;}}for(;;){mygc(k);if(k<'0'||k>'9')break;*x=(*x)*10+k-'0';}if(m)(*x)=-(*x);}
inline void rd(db *x){scanf("%lf",x);}
inline int rd(char c[]){int i,s=0;for(;;){mygc(i);if(i!=' '&&i!='\n'&&i!='\r'&&i!='\t'&&i!=EOF) break;}c[s++]=i;for(;;){mygc(i);if(i==' '||i=='\n'||i=='\r'||i=='\t'||i==EOF) break;c[s++]=i;}c[s]='\0';return s;}
inline void rd(int a[],int n){FOR(i,0,n)rd(&a[i]);}
inline void rd(ll a[],int n){FOR(i,0,n)rd(&a[i]);}
template <class T, class S> inline void rd(T *x, S *y){rd(x);rd(y);}
template <class T, class S, class U> inline void rd(T *x, S *y, U *z){rd(x);rd(y);rd(z);}
template <class T, class S, class U, class V> inline void rd(T *x, S *y, U *z, V *w){rd(x);rd(y);rd(z);rd(w);}
inline void wr(int x){if(x < 10) putchar('0' + x); else wr(x / 10), wr(x % 10);}
inline void wr(int x, char c){int s=0,m=0;char f[10];if(x<0)m=1,x=-x;while(x)f[s++]=x%10,x/=10;if(!s)f[s++]=0;if(m)mypc('-');while(s--)mypc(f[s]+'0');mypc(c);}
inline void wr(ll x, char c){int s=0,m=0;char f[20];if(x<0)m=1,x=-x;while(x)f[s++]=x%10,x/=10;if(!s)f[s++]=0;if(m)mypc('-');while(s--)mypc(f[s]+'0');mypc(c);}
inline void wr(db x, char c){printf("%.15f",x);mypc(c);}
inline void wr(const char c[]){int i;for(i=0;c[i]!='\0';i++)mypc(c[i]);}
inline void wr(const char x[], char c){int i;for(i=0;x[i]!='\0';i++)mypc(x[i]);mypc(c);}
template<class T> inline void wrn(T x){wr(x,'\n');}
template<class T, class S> inline void wrn(T x, S y){wr(x,' ');wr(y,'\n');}
template<class T, class S, class U> inline void wrn(T x, S y, U z){wr(x,' ');wr(y,' ');wr(z,'\n');}
template<class T> inline void wra(T x[], int n){int i;if(!n){mypc('\n');return;}FOR(i,0,n-1)wr(x[i],' ');wr(x[n-1],'\n');}
int n,ee,g[maxn][maxn],h[maxn],vis[2][maxn];//vis主要是一点小优化,防止重复入队
struct Edge{
int v,w1,w2,next;
}e[maxm];
void addedge(int u,int v,int w1,int w2){
e[ee]=Edge{v,w1,w2,h[u]};
h[u]=ee++;
}
queue<int>q[2];
db max(db a,db b){
if(a>b)return a;
return b;
}
db min(db a,db b){
if(a>b)return b;
return a;
}
int main(){
int t;rd(&t);
while(t--){
int m1,m2;
rd(&n,&m1,&m2);
memset(h,-1,sizeof(int)*(n+1));
ee=0;
FOR(i,0,n)addedge(i,i+1,0,1),addedge(i+1,i,0,0);
while(m1--){
int l,r,k;
rd(&l,&r,&k);
addedge(r,l-1,0,-k);
}
while(m2--){
int l,r,k;
rd(&l,&r,&k);
addedge(l-1,r,1,-k);
}
addedge(0,n,1,0);
int o=0;
FOR(i,0,n+2)
FOR(j,0,n+1)g[i][j]=inf/4;
g[0][0]=0;q[o].push(0);
FOR(i,0,n+2){
o=i%2;
while(!q[o].empty()){
int u=q[o].front();q[o].pop();
vis[o][u]=0;
for(register int j=h[u];j!=-1;j=e[j].next){
int v=e[j].v,w1=e[j].w1,w2=e[j].w2;
if(i<n+1 && w1 && g[i+1][v]>g[i][u]+w2){//第一种松弛操作,由g[i][u]更新g[i+1][v],满足u->v边为1*x+b类型
g[i+1][v]=g[i][u]+w2;
if(!vis[o^1][v])q[o^1].push(v),vis[o^1][v]=1;
}else if(!w1 && g[i][v]>g[i][u]+w2){//第二种松弛操作,由g[i][u]更新g[i][v],满足u->v边为0*x+b类型
g[i][v]=g[i][u]+w2;
if(!vis[o][v])q[o].push(v),vis[o][v]=1;
}
}
}
wra(g[i],n+1);
}
db ans1=inf,ans2=inf;//由于ax+b中b/a可能是小数,所以采用double类型处理
FOR(i,0,n+1){
db maxx=0;
FOR(j,0,n+1)maxx=max(maxx,1.0*(g[n+1][i]-g[j][i])/(n+1-j));
ans1=min(ans1,maxx);
}
FOR(i,2,n+2)ans2=min(ans2,1.0*g[i][n]/(i-1));
db ans=-min(ans1,ans2);
int as=0;
if(fabs(ans-(int)ans)<eps)as=(int)(ans+0.5);
else as=(int)ans+1;
wrn(as);
}
}