2022.02.23 网络流复习


2022.02.23 网络流复习

1. 费用流

1.1 关于费用流的理解

1.1.1 费用流

费用流分为两种:

  1. 最小费用最大流
  2. 最大费用最大流

可以看出,费用流是建立在最大流的基础上进行的骚操作。

2.1 练习题

2.1.1 P2153 [SDOI2009]晨跑(经典标志:在周期最长的情况下,总路程最短)(拆点为边限制经过次数)

https://www.luogu.com.cn/problem/P2153

根据:在周期最长的情况下,总路程最短,可以判断出这是道费用流的题,而不是最大流。

周期就是流量,路程就是费用,正好满足最小费用最大流。

对于一个点只能用一遍,所以要把一个点拆成两个点i与i+n,从i到i+n连一条容量为1费用为0的边,保证从它自己到它自己费用为0。对于从x到y有一条路,从x+n到y建一条容量为1费用为距离的边。因为寝室与学校不算十字路口,不用满足只经过一次的要求,所以S是1+你,T是n。

#include
#include
#include
#include
#include
using namespace std;

#define Ri register
const int N=410;
const int M=6e4+10;
const int inf=1<<30;
int n,m,cnt=1,head[N],dis[N],flow[N],vis[N],pre[N];
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	queueq;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=0x3f3f3f3f;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=flow[t]*dis[t];
}
inline void  Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	n=read();m=read();
	for(Ri int i=1;i<=n;i++)add(i,i+n,1,0);
	for(Ri int i=1;i<=m;i++){
		int u,v,w;
		u=read();v=read();w=read();
		add(u+n,v,1,w);
	}
	S=1+n,T=n;
	Ek(S,T);
	cout<

2.1.2 P2604 [ZJOI2010]网络扩容(费用流与最大流之间的转换)

https://www.luogu.com.cn/problem/P2604

简而言之就是:如果费用流的费用是0,跑出来的manflow就是最大流。——来自某天我的想法

今天我才第一次肯定。——参照1.1.1

——2022.02.23 8:49 eleveni

而且这道题在残量网络上不一定对,因为最大流不止一种跑法,很有可能第一遍算出来的最大流的方案并不是增加k之后费用流的最优解方案。

#include
#include
#include
#include
using namespace std;

#define Ri register
const int N=5e3+10;
const int M=4e4+10;
const int inf=0x3f3f3f3f;
int n,m,k,cnt=1,head[N],dis[N],flow[N],pre[N],vis[N],val[N],from[N],toi[N],cap[N];
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].next=head[u];
	a[cnt].val=w;
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,inf,sizeof(dis));
	memset(vis,0,sizeof(vis));
	queueq;
	dis[s]=0;flow[s]=inf;vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];maxncost+=flow[t]*dis[t];
}
inline void Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	n=read();m=read();k=read();
	for(Ri int i=1;i<=m;i++){
		int u,v,w,x;
		from[i]=u=read();toi[i]=v=read();cap[i]=w=read();val[i]=read();
		add(u,v,w,0);
	}
	maxnflow=0;maxncost=0;
	Ek(1,n);
	cout<

2.1.3 P2050 [NOI2012] 美食节(动态加边+按阶段拆点)

https://www.luogu.com.cn/problem/P2050

如果像修车那道题一样一次性建完边的话,严重怀疑会TLE,但是我想试一试。

所以先介绍一下一次性建完边的写法:

从S到每种菜建一条容量为\(P_i\) 费用为0的边,表示一共要做\(P_i\)个这种菜。把每个厨师拆分成\(Ptot\)个点,厨师i在阶段j表示这个厨师在做倒数第j道菜。从菜品i到第j个厨师的阶段k连一条容量为1费用为\(t_{i,j}*k\)的边,做这道菜对后面总共等待时间的贡献是\(k*t_{i,j}\)。从每个厨师每个阶段向T连一条容量为1费用为0的边。

试出来,60pts,还可以。

60pts(没有动态加边)
#include
using namespace std;

#define Ri register
const int N=5e4+10;
const int M=1e7+10;
const int inf=0x3f3f3f3f;
int n,m,cnt=1,head[N],dis[N],vis[N],pre[N],flow[N],tot,num[N];
int timei[50][110];
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].cost=x;
	a[cnt].next=head[u];
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,inf,sizeof(dis));
	//cout<q;
	dis[s]=0;vis[s]=1;flow[s]=inf;
	q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		//cout<<"x "<dis[x]+a[i].cost&&a[i].val){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=flow[t]*dis[t];
}
inline void Ek(int s,int t){
	while(spfa(s,t))update(s,t);//,cout<<"Case 1"<

说明还是必须动态加边的。当前厨师只有第j个阶段被占用了才会进入下一个j+1阶段,可以根据这个在update的时候把被占用的阶段全部连上下一个阶段。

预计得分100pts,但是边数我就留着了,懒得修改/斜眼笑。

100pts(动态加边)
#include
using namespace std;
//#define int long long
#define Ri register
const int N=7e5+10;
const int M=1e7+6e5+10;
const int inf=0x3f3f3f3f;
int n,m,cnt=1,head[N],dis[N],vis[N],pre[N],flow[N],tot,num[N];
int timei[50][110],flag[110];
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].cost=x;
	a[cnt].next=head[u];
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,inf,sizeof(dis));
	//cout<q;
	dis[s]=0;vis[s]=1;flow[s]=inf;
	q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		//cout<<"x "<dis[x]+a[i].cost&&a[i].val){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	//cout<<"Case 2"<n&&xtot){
				x=a[xi^1].to;
				continue;
			}
			flag[cooker]=id;
			for(Ri int i=1;i<=n;i++)
			add(i,(cooker-1)*tot+id+n,1,timei[i][cooker]*id);
			//cout<

2.1.4 P3980 [NOI2008] 志愿者招募(一面对多面、线性规划)

https://www.luogu.com.cn/problem/P3980

在这道题里人数是流量,雇佣员工的费用是费用……废话!

网络流的流量增加1时不能再分叉之后所有路径上的流量都增加1,这就难以解决每天都有的人数限制的问题。所以对于一个人,我们可以把他工作的天数全部连在一起。然后 S是第0天,T是第n+1天。反正也去不掉多余的员工,那就让他一直跑到结束为止,但是跑过自己负责时间的员工不能占新花钱买来的员工,所以对于每类员工都从他们开始工作那天到他们工作结束的后一天连一条容量inf费用是雇佣他们价钱的边;对于每一天需要多少人可以让i到i+1来确定,从i到i+1建一条容量为现在还不确定费用为0的边,毕竟现在这些多余的员工只是陪跑。我们想让员工的缺口为\(a_i\),但是网络流流量不能是负数,所以就整体增加一个maxn值,这样一张真正的图就可以建出来了:

从S到1建一条容量为maxn费用为0的边,从n到T建一条容量为inf费用为0的边,表示假设有这么多虚拟小人儿在跑图;从i到i+1建一条容量为\(-a_i+maxn\)费用为\(c_i\)的边,表示第i天只能有\(-a_i+maxn\)个虚拟小人,其他的跑图小人和将要收费小人的个数必须是\(a_i\),其他的正在工作的跑图小人不需要收费,收费小人必须要收费,所以从\(s_i\)\(t_i+1\)建一条容量为inf费用为\(c_i\)的边来收费。

#include
#include
#include
#include
#include
using namespace std;

#define Ri register
const int N=1010;
const int M=6e4+10;
const int maxn=1e7;
typedef long long ll;
const ll inf=1e18;
int n,m,cnt=1,head[N],vis[N],pre[N];
int S,T;
ll dis[N],flow[N];
ll maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	for(Ri int i=0;i<=n+8;i++)dis[i]=inf;
	memset(vis,0,sizeof(vis));
	queueq;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+1ll*a[i].cost;
				flow[v]=min(flow[x],1ll*a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=(int)flow[t];
		a[xi^1].val+=(int)flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=1ll*flow[t];
	maxncost+=1ll*flow[t]*dis[t];
}
inline void  Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	n=read();m=read();
	for(Ri int i=1;i<=n;i++){
		int x=read();
		add(i,i+1,maxn-x,0);
	}
	for(Ri int i=1;i<=m;i++){
		int u,v,w;
		u=read();v=read();w=read();
		add(u,v+1,maxn,w);
	}
	S=0,T=n+2;add(S,1,maxn,0);add(n+1,T,maxn,0);
	Ek(S,T);
	cout<

2.1.5 P3705 [SDOI2017]新生舞会(神奇的算分子分母极值的题、分数规划、二分+费用流)

https://www.luogu.com.cn/problem/P3705

对于式子\(C=\frac{\sum_a}{\sum_b}\),可以继续推出\(\sum_a-C*\sum_b=0\) 。考虑二分出C的值,如果\(\sum_a-C*\sum_b>=0\),说明C满足条件。

每次建图:
从S到每个男生连一条容量为1费用为0的边;从每个女生到T连一条容量为1费用为0的边;从i到j连一条容量为1费用为\(a_{i,j}-C*b_{i,j}\)的边。

如果最大费用最大流结果大于等于0,则C满足。

不知道为什么求最长路挂了,于是加了个负号,忽然想到spfa不就是求有负权的最短路的吗?

#include
#include
#include
#include
#include
using namespace std;

#define Ri register
const int N=220;
const int M=3e4+10;
const double eps=1e-8;
const int inf=1<<30;
int n,m,cnt=1,head[N],vis[N],pre[N],flow[N];
int S,T;
double dis[N],a1[110][110],b1[110][110];
int maxnflow;
double maxncost;
struct node{
	int to,next,val;
	double cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,double x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,double x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	//cout<<"there is spfa "<q;
	dis[s]=0.0;flow[s]=1<<30;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=(double)inf;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=(double)flow[t]*dis[t];
}
inline bool Ek(int s,int t,double C){
	maxnflow=0;
	maxncost=0.0;
	cnt=1;
	memset(head,0,sizeof(head));
	memset(a,0,sizeof(a));
	for(Ri int i=1;i<=n;i++)add(S,i,1,0),add(i+n,T,1,0);
	//cout<=0.0;
}
inline void find(){
	double L=0,R=10010,mid,ans=0;
	while(R-L>eps){
		mid=(L+R)/2.0;
		//cout<<"L "<

2.1.6 P2488 [SDOI2011]工作安排

https://www.luogu.com.cn/problem/P2488

这道题看起来很妙啊!别人都是对后面的有积累,这道题反而是从前面开始算。

50pts
#include
#include
#include
#include
#include
using namespace std;

#define int long long
#define Ri register
const int N=201000;
const int M=6e7+10;
const int inf=1e18;
const int maxn=1e7;
int n,m,cnt=1,head[N],dis[N],flow[N],vis[N],pre[N];
int S,T,maxnflow,maxncost,tot;
int A[360][360],sep[360][50],W[360][50],flag[360][10],C[360];
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	//memset(dis,0x3f3f3f3f,sizeof(dis));
	for(Ri int i=0;i<=T;i++)dis[i]=inf;
	memset(vis,0,sizeof(vis));
	queueq;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	//cout<<"flow[t] "<n&&x

我去,忽然发现我为毛要一直动态加边?我直接建好多边连在物体和人上不就行了?

从S到每个人按照他给的分段函数连一条容量为\(seg_{i,j}-seg_{i,j-1}\)费用为\(W_{i,j}\)的边;从每个物体到T连一条从容量为\(C_{i}\)费用为0的边;从人i到物体j根据\(A_{i,j}\),连一条容量为inf费用为0的边。

100pts
#include
#include
#include
#include
#include
using namespace std;

#define int long long
#define Ri register
const int N=500010;
const int M=2e7+10;
const int inf=1e18;
const int maxn=1e8;
int n,m,cnt=1,head[N],dis[N],flow[N],vis[N],pre[N];
int S,T,maxnflow,maxncost,tot;
int A[560][560],sep[560][10],W[560][10],flag[560][10],C[560];
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	//memset(dis,0x3f3f3f3f,sizeof(dis));
	for(Ri int i=0;i<=T;i++)dis[i]=inf;
	memset(vis,0,sizeof(vis));
	queueq;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	//cout<<"flow[t] "<

2.1.7 P7730 [JDWOI-1] 蜀道难(本题涉及差分、正负边权分别处理)

https://www.luogu.com.cn/problem/P7730

#include
#include
#include
#include
#include
using namespace std;

#define Ri register
const int N=5e4+10;
const int M=1e5+10;
const int inf=1<<30;
int n,m,cnt=1,head[N],dis[N],flow[N],vis[N],pre[N];
int op[2][N],edge[N],ai[N],b[N],top;
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	queueq;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=0x3f3f3f3f;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=flow[t]*dis[t];
}
inline void  Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	n=read();m=read();
	S=n+2,T=n+3;
	for(Ri int i=1;i<=n;i++)ai[i]=read(),b[i]=ai[i]-ai[i-1];
	for(Ri int i=0;i<=n+1;i++)op[0][i]=op[1][i]=inf;
	for(Ri int i=1;i<=m;i++){
		int v,w;
		char x;cin>>x;
		v=read();w=read();
		if(x=='+')op[0][v]=min(op[0][v],w);
		else op[1][v]=min(op[1][v],w);
	}
	add(S,n+1,inf,0);
	for(Ri int i=1;i<=n;i++)
	if(b[i]>0)add(S,i,b[i],0);
	else add(i,T,-b[i],0),edge[++top]=cnt-1;
	for(Ri int i=1;i<=n;i++)for(Ri int l=1,r=l+i;r<=n+1;l++,r=l+i){
		if(op[0][i]