最大流最小割费用流线性规划模拟费用流保序回归

题目:
tower
HNOI2013 切糕
CQOI2017 老C的方块
TJOI2015 线性代数
FJWC2019 最短路
文理分科
Goods transportation
Sum of Abs
九月的咖啡店
清华集训2017 无限之环
志愿者招募加强版
六省联考2017 寿司餐厅
最大K段和
Orz the MST
联合省选2020 魔法商店

tower

对每个塔,将它攻击的范围用有向边串起来,割掉一条边表示选择对应的点。将流量赋为最大可能值减去该位置的值即可。对于炮弹轨迹不能相交的限制,在一条链上对应位置连一条有向边流量为正无穷到另一条链上对应的点即可,并将另一条链翻转。
空间复杂度 O ( n m ) O(nm) O(nm)

#include<stdio.h>
#include<queue>
using namespace std;
#define R register int
#define I inline
#define N 5005
#define M 40000
I int Min(const int x,const int y){
	return x<y?x:y;
}
int Last[N],Ecur[N],b[51][51],Next[M],id[51][51],lat[51][51],fir[51][51],a[51][51],End[M],Cap[M],gap[N],dis[N],ct=1;
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void BFS(int&T){
	queue<int>Q;
	Q.push(T);
	for(R i=1;i!=T;i++){
		dis[i]=T;
	}
	while(Q.empty()==false){
		int u=Q.front();
		Q.pop();
		for(R i=Last[u];i!=0;i=Next[i]){
			int v=End[i];
			if(dis[v]>dis[u]+1){
				dis[v]=dis[u]+1;
				Q.push(v);
			}
		}
	}
	for(R i=1;i<=T;i++){
		Ecur[i]=Last[i];
		gap[dis[i]]++;
	}
}
I int ISAP(int u,int flow,int&S,int&T){
	if(u==T){
		return flow;
	}
	int f,v,tem=0;
	for(R&i=Ecur[u];i!=0;i=Next[i]){
		v=End[i];
		if(Cap[i]!=0&&dis[v]==dis[u]-1){
			f=ISAP(v,Min(Cap[i],flow-tem),S,T);
			Cap[i]-=f;
			Cap[i^1]+=f;
			tem+=f;
			if(flow==tem||dis[S]==T){
				return tem;
			}
		}
	}
	gap[dis[u]]--;
	if(gap[dis[u]]==0){
		dis[S]=T;
	}
	dis[u]++;
	gap[dis[u]]++;
	Ecur[u]=Last[u];
	return tem;
}
I int MaxFlow(int S,int T){
	int res=0;
	BFS(T);
	while(dis[S]<T){
		res+=ISAP(S,999,S,T);
	}
	return res;
}
int main(){
	int n,m,sum,tot=0;
	scanf("%d%d",&n,&m);
	for(R i=1;i<=n;i++){
		for(R j=1;j<=m;j++){
			scanf("%d",a[i]+j);
			if(a[i][j]<0){
				sum+=999;
			}
			tot+=2;
			id[i][j]=tot;
		}
	}
	tot+=2;
	for(R i=1;i<=n;i++){
		for(R j=1;j<=m;j++){
			if(a[i][j]==-1){
				Link(tot-1,id[i][j]-1,999);
				Link(id[1][j]-1,tot,999);
				fir[i][j]=tot-1;
				for(R k=1;k!=i;k++){
					Link(id[k+1][j]-1,id[k][j]-1,999-a[k][j]);
					b[k][j]|=1;
					fir[k][j]=id[k+1][j]-1;
				}
			}else if(a[i][j]==-2){
				Link(tot-1,id[i][j]-1,999);
				Link(id[n][j]-1,tot,999);
				fir[i][j]=tot-1;
				for(R k=i;k!=n;k++){
					Link(id[k][j]-1,id[k+1][j]-1,999-a[k+1][j]);
					b[k+1][j]|=1;
					fir[k+1][j]=id[k][j]-1;
				}
			}else if(a[i][j]==-3){
				Link(tot-1,id[i][1],999);
				Link(id[i][j],tot,999);
				lat[i][j]=tot;
				for(R k=1;k!=j;k++){
					Link(id[i][k],id[i][k+1],999-a[i][k]);
					b[i][k]|=2;
					lat[i][k]=id[i][k+1];
				}
			}else if(a[i][j]==-4){
				Link(tot-1,id[i][m],999);
				Link(id[i][j],tot,999);
				lat[i][j]=tot;
				for(R k=j;k!=m;k++){
					Link(id[i][k+1],id[i][k],999-a[i][k+1]);
					b[i][k+1]|=2;
					lat[i][k+1]=id[i][k];
				}
			}
		}
	}
	for(R i=1;i<=n;i++){
		for(R j=1;j<=m;j++){
			if(b[i][j]==3){
				Link(fir[i][j],lat[i][j],999);
			}
		}
	}
	printf("%d",sum-MaxFlow(tot-1,tot));
	return 0;
}
HNOI2013 切糕

跟前一道题类似,把纵行上的点串起来,通过连边的方式解决相邻差小于等于 D D D 的限制即可。
空间复杂度 O ( P Q R ) O(PQR) O(PQR)

#include<iostream>
#include<queue>
using namespace std;
#define R register int
#define I inline
#define N 65603
#define M 633602
#define INF 999999999
I int Min(const int x,const int y){
	return x<y?x:y;
}
int v[41][41][41],id[42][41][41],dis[N],gap[N],Last[N],End[M],Ecur[N],Cap[M],Next[M],ct=1;
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void BFS(int&T){
	for(R i=1;i!=T;i++){
		dis[i]=T;
	}
	queue<int>Q;
	Q.push(T);
	while(Q.empty()==false){
		int v,x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(dis[v]>dis[x]+1){
				dis[v]=dis[x]+1;
				Q.push(v);
			}
		}
	}
	for(R i=1;i<=T;i++){
		Ecur[i]=Last[i];
		gap[dis[i]]++;
	}
}
I int ISAP(int u,int flow,int&S,int&T){
	if(u==T){
		return flow;
	}
	int f,v,tem=0;
	for(R&i=Ecur[u];i!=0;i=Next[i]){
		v=End[i];
		if(Cap[i]!=0&&dis[v]==dis[u]-1){
			f=ISAP(v,Min(Cap[i],flow-tem),S,T);
			Cap[i]-=f;
			Cap[i^1]+=f;
			tem+=f;
			if(flow==tem||dis[S]==T){
				return tem;
			}
		}
	}
	gap[dis[u]]--;
	if(gap[dis[u]]==0){
		dis[S]=T;
	}
	dis[u]++;
	gap[dis[u]]++;
	Ecur[u]=Last[u];
	return tem;
}
I int MaxFlow(int S,int T){
	BFS(T);
	int res=0;
	while(dis[S]<T){
		res+=ISAP(S,INF,S,T);
	}
	return res;
}
int main(){
	int A,B,C,D,T=0;
	cin>>A>>B>>C>>D;
	for(R i=1;i!=C+2;i++){
		for(R j=1;j<=A;j++){
			for(R k=1;k<=B;k++){
				if(i<=C){
					cin>>v[i][j][k];
				}
				T++;
				id[i][j][k]=T;
			}
		}
	}
	T+=2;
	for(R i=1;i<=A;i++){
		for(R j=1;j<=B;j++){
			Link(T-1,id[1][i][j],INF);
			Link(id[C+1][i][j],T,INF);
		}
	}
	for(R i=1;i<=C;i++){
		for(R j=1;j<=A;j++){
			for(R k=1;k<=B;k++){
				Link(id[i][j][k],id[i+1][j][k],v[i][j][k]);
			}
		}
	}
	for(R i=1;i<=C-D;i++){
		for(R j=1;j<=A;j++){
			for(R k=1;k<=B;k++){
				if(k!=B){
					Link(id[i+D+1][j][k+1],id[i+1][j][k],INF);
				}
				if(k!=1){
					Link(id[i+D+1][j][k-1],id[i+1][j][k],INF);
				}
				if(j!=A){
					Link(id[i+D+1][j+1][k],id[i+1][j][k],INF);
				}
				if(j!=1){
					Link(id[i+D+1][j-1][k],id[i+1][j][k],INF);
				}
			}
		}
	}
	cout<<MaxFlow(T-1,T);
	return 0;
}
CQOI2017 老C的方块

根据所给的图形可以发现不能存在的图形一定由蓝色竖边左边一个格子以及它上左下的格子加上蓝色竖边右边一个格子以及它上右下的格子组成。因此考虑对网格染色,对于行数为奇数的格子,按列数模 4 4 4染色,分别染为颜色 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4;对于行数为偶数的格子,还是按列数模 4 4 4染色,分别染为颜色 2 , 1 , 4 , 3 2,1,4,3 2,1,4,3。这样一个图形一定由依次 4 4 4种颜色的格子各一个组成,因此从源点连向颜色为 1 1 1的点,容量为费用;从颜色为 1 1 1的点连向颜色为 2 2 2的点,容量为正无穷的边;颜色为 2 2 2的点连向颜色为 3 3 3的点,容量为两者费用较小值;从颜色为 3 3 3的点连向颜色为 4 4 4的点,容量为正无穷的边;从颜色为 4 4 4的点连向汇点,容量为费用。这样求出的最小割即为答案。
时间复杂度 O ( n n ) O(n \sqrt{n}) O(nn ),空间复杂度 O ( n ) O(n) O(n)

#include<stdio.h>
#include<map>
#include<queue>
using namespace std;
#define R register int
#define I inline
#define C 100001
#define N 200003
#define M 1000009
#define INF 2000000007
const int v[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
I int Min(const int x,const int y){
	return x<y?x:y;
}
int w[C],id[C],dis[N],gap[N],Last[N],End[M],Ecur[N],Cap[M],Next[M],ct=1;
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void BFS(int&T){
	for(R i=1;i!=T;i++){
		dis[i]=T;
	}
	queue<int>Q;
	Q.push(T);
	while(Q.empty()==false){
		int v,x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(dis[v]>dis[x]+1){
				dis[v]=dis[x]+1;
				Q.push(v);
			}
		}
	}
	for(R i=1;i<=T;i++){
		Ecur[i]=Last[i];
		gap[dis[i]]++;
	}
}
I int ISAP(int u,int flow,int&S,int&T){
	if(u==T){
		return flow;
	}
	int f,v,tem=0;
	for(R&i=Ecur[u];i!=0;i=Next[i]){
		v=End[i];
		if(Cap[i]!=0&&dis[v]==dis[u]-1){
			f=ISAP(v,Min(Cap[i],flow-tem),S,T);
			Cap[i]-=f;
			Cap[i^1]+=f;
			tem+=f;
			if(flow==tem||dis[S]==T){
				return tem;
			}
		}
	}
	gap[dis[u]]--;
	if(gap[dis[u]]==0){
		dis[S]=T;
	}
	dis[u]++;
	gap[dis[u]]++;
	Ecur[u]=Last[u];
	return tem;
}
I int MaxFlow(int S,int T){
	BFS(T);
	int res=0;
	while(dis[S]<T){
		res+=ISAP(S,INF,S,T);
	}
	return res;
}
struct Point{
	int PosX,PosY;
	I friend bool operator<(Point A,Point B){
		if(A.PosX==B.PosX){
			return A.PosY<B.PosY;
		}
		return A.PosX<B.PosX;
	}
}p[C];
I Point Pair(int x,int y){
	Point res;
	res.PosX=x;
	res.PosY=y;
	return res;
}
I int GetType(int x,int y){
	x&=3;
	if((y&1)==1){
		return x+1;
	}
	if(x<2){
		return 2-x;
	}
	return 6-x;
}
int main(){
	int n,type,vx,vy;
	scanf("%d%d%d",&n,&n,&n);
	map<Point,int>Q;
	for(R i=1;i<=n;i++){
		scanf("%d%d%d",&p[i].PosX,&p[i].PosY,w+i);
		Q[p[i]]=i;
	}
	for(R i=1;i<=n;i++){
		type=GetType(p[i].PosX,p[i].PosY);
		if(type==4){
			Link(i,n+2,w[i]);
		}else{
			if(type==1){
				Link(n+1,i,w[i]);
			}
			for(R j=0;j!=4;j++){
				vx=p[i].PosX+v[j][0];
				vy=p[i].PosY+v[j][1];
				if(GetType(vx,vy)==type+1&&Q.count(Pair(vx,vy))!=0){
					vx=Q[Pair(vx,vy)];
					if(type==2){
						Link(i,vx,Min(w[i],w[vx]));
					}else{
						Link(i,vx,INF);
					}
				}
			}
		}
	}
	printf("%d",MaxFlow(n+1,n+2));
	return 0;
}
TJOI2015 线性代数

A ′ = A B − C A'=AB-C A=ABC,则:
D = A ′ A T = ∑ i = 0 n − 1 A i ′ A i D=A'A^T=\sum_{i=0}^{n-1}A'_i A_i D=AAT=i=0n1AiAi
带入定义式:
D = ∑ i = 0 n − 1 A i ( ∑ j = 0 n − 1 A j B j , i − C i ) = ∑ i = 0 n − 1 ∑ j = 0 n − 1 A i A j B i , j − ∑ i = 0 n − 1 A i C i D=\sum_{i=0}^{n-1} A_i(\sum_{j=0}^{n-1} A_j B_{j,i} -C_i)=\sum_{i=0}^{n-1} \sum_{j=0}^{n-1} A_i A_j B_{i,j} -\sum_{i=0}^{n-1} A_i C_i D=i=0n1Ai(j=0n1AjBj,iCi)=i=0n1j=0n1AiAjBi,ji=0n1AiCi
到此为止,代数方法就用到了尽头,之后的问题不好处理。由于 A A A 为01矩阵,因此可以用最小割。先在答案上加上 B B B 中数的总和,然后再考虑损失的数值。从点 i i i 连一条容量为 C i C_i Ci 的边到汇点,表示割掉这条边即 A i = 1 A_i=1 Ai=1 会损失 C i C_i Ci。从点 ( i , j ) (i,j) (i,j) 分别连边倒点 i , j i,j i,j,容量为正无穷。从源点出发连向点 ( i , j ) (i,j) (i,j) 的边,容量为 B i , j B_{i,j} Bi,j。答案即位 B B B 的和减去最小割。
由于只有当 A i = 1 , A j = 1 A_i=1,A_j=1 Ai=1,Aj=1 同时成立时才不会割去源点到 ( i , j ) (i,j) (i,j) 的边,否则就会。因此这样建图是正确的。
时间复杂度 O ( n 3 ) O(n^3) O(n3),空间复杂度 O ( n 2 ) O(n^2) O(n2)

#include<iostream>
#include<queue>
using namespace std;
#define R register int
#define I inline
#define N 250503
#define M 1501004
#define INF 999999999
I int Min(const int x,const int y){
	return x<y?x:y;
}
int dis[N],gap[N],Last[N],End[M],Ecur[N],Cap[M],Next[M],ct=1;
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void BFS(int&T){
	for(R i=1;i!=T;i++){
		dis[i]=T;
	}
	queue<int>Q;
	Q.push(T);
	while(Q.empty()==false){
		int v,x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(dis[v]>dis[x]+1){
				dis[v]=dis[x]+1;
				Q.push(v);
			}
		}
	}
	for(R i=1;i<=T;i++){
		Ecur[i]=Last[i];
		gap[dis[i]]++;
	}
}
I int ISAP(int u,int flow,int&S,int&T){
	if(u==T){
		return flow;
	}
	int f,v,tem=0;
	for(R&i=Ecur[u];i!=0;i=Next[i]){
		v=End[i];
		if(Cap[i]!=0&&dis[v]==dis[u]-1){
			f=ISAP(v,Min(Cap[i],flow-tem),S,T);
			Cap[i]-=f;
			Cap[i^1]+=f;
			tem+=f;
			if(flow==tem||dis[S]==T){
				return tem;
			}
		}
	}
	gap[dis[u]]--;
	if(gap[dis[u]]==0){
		dis[S]=T;
	}
	dis[u]++;
	gap[dis[u]]++;
	Ecur[u]=Last[u];
	return tem;
}
I int MaxFlow(int S,int T){
	BFS(T);
	int res=0;
	while(dis[S]<T){
		res+=ISAP(S,INF,S,T);
	}
	return res;
}
int main(){
	int n,T,sum=0,b,p;
	cin>>n;
	T=n*(n+1)+2;
	for(R i=0;i!=n;i++){
		for(R j=0;j!=n;j++){
			cin>>b;
			sum+=b;
			p=i*n+j+1;
			Link(T-1,p,b);
			Link(p,n*n+i+1,INF);
			Link(p,n*n+j+1,INF);
		}
	}
	for(R i=1;i<=n;i++){
		cin>>b;
		Link(n*n+i,T,b);
	}
	cout<<sum-MaxFlow(T-1,T);
	return 0;
}
FJWC2019 最短路

给定一个 n n n 个点, m m m 条边的简单无向图,令 0 0 0号点为起点, n − 1 n-1 n1号点为终点。给定数 k k k,选图上除起点和终点外的至多 k k k 个点,令经过这些点的时间为 1 1 1,经过每条边的时间为 1 1 1,求起点到终点的最大时间。
2 ⩽ n ⩽ 100 , 0 ⩽ k ⩽ n , m ⩾ 1 2 \leqslant n \leqslant 100,0 \leqslant k \leqslant n,m \geqslant 1 2n100,0kn,m1
时间限制1s,空间限制256MB。

每次跑一次最短路,将最短路上的边和点提出来建网络流,将点拆为两个点,若该点已被选,则中间的容量为正无穷,否则容量为 1 1 1,将最小割上的点选中即可。每一次答案加 1 1 1
时间复杂度 O ( n 4 ) O(n^4) O(n4),空间复杂度 O ( n 2 ) O(n^2) O(n2)

#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
#define R register int
#define I inline
#define N 203
#define M 30000
#define INF 9999999
I int Min(const int x,const int y){
	return x<y?x:y;
}
int val[N],dis[N],gap[N],Last[N],End[M],Ecur[N],Cap[M],Next[M],ct;
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Cap[ct]=0;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void BFS(int&T){
	for(R i=1;i!=T;i++){
		dis[i]=T;
	}
	queue<int>Q;
	Q.push(T);
	while(Q.empty()==false){
		int v,x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(dis[v]>dis[x]+1){
				dis[v]=dis[x]+1;
				Q.push(v);
			}
		}
	}
	for(R i=1;i<=T;i++){
		Ecur[i]=Last[i];
		gap[dis[i]]++;
	}
}
I int ISAP(int u,int flow,int&S,int&T){
	if(u==T){
		return flow;
	}
	int f,v,tem=0;
	for(R&i=Ecur[u];i!=0;i=Next[i]){
		v=End[i];
		if(Cap[i]!=0&&dis[v]==dis[u]-1){
			f=ISAP(v,Min(Cap[i],flow-tem),S,T);
			Cap[i]-=f;
			Cap[i^1]+=f;
			tem+=f;
			if(flow==tem||dis[S]==T){
				return tem;
			}
		}
	}
	gap[dis[u]]--;
	if(gap[dis[u]]==0){
		dis[S]=T;
	}
	dis[u]++;
	gap[dis[u]]++;
	Ecur[u]=Last[u];
	return tem;
}
I int MaxFlow(int S,int T){
	BFS(T);
	int res=0;
	while(dis[S]<T){
		res+=ISAP(S,INF,S,T);
	}
	return res;
}
vector<int>G[N];
I int SPFA(int&n){
	static int in[N];
	for(R i=1;i!=n;i++){
		dis[i]=INF;
		in[i]=false;
	}
	queue<int>Q;
	Q.push(0);
	while(Q.empty()==false){
		int x=Q.front();
		in[x]=false;
		Q.pop();
		for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
			if(dis[*T]>dis[x]+1+val[*T]){
				dis[*T]=dis[x]+1+val[*T];
				if(in[*T]==false){
					Q.push(*T);
					in[*T]=true;
				}
			}
		}
	}
	return dis[n-1];
}
I int Solve(int&n){
	int T=n-1<<1;
	ct=1;
	for(R i=1;i<=T;i++){
		Last[i]=0;
	}
	for(R i=1;i!=n-1;i++){
		Link(i<<1,i<<1|1,val[i]==1?INF:1);
	}
	for(R i=0;i!=n;i++){
		for(vector<int>::iterator T=G[i].begin();T!=G[i].end();T++){
			if(dis[*T]-val[*T]==dis[i]+1){
				Link(i<<1|1,*T<<1,INF);
			}
		}
	}
	return MaxFlow(1,T);
}
bool vis[N];
I void ReBFS(int n){
	for(R i=1;i<=n;i++){
		vis[i]=false;
	}
	queue<int>Q;
	Q.push(1);
	while(Q.empty()==false){
		int x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			if(Cap[i]!=0&&vis[End[i]]==false){
				vis[End[i]]=true;
				Q.push(End[i]);
			}
		}
	}
}
int main(){
	int n,x,m,y,k,ans;
	scanf("%d%d%d",&n,&m,&k);
	for(R i=m;i!=0;i--){
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	ans=SPFA(n);
	while(true){
		x=Solve(n);
		if(k<x){
			break;
		}
		k-=x;
		ans++;
		bool tag=true;
		ReBFS(n-1<<1);
		for(R i=1;i!=n-1;i++){
			if(vis[i<<1]==vis[i<<1|1]){
				tag=false;
			}else{
				val[i]=1;
			}
		}
		if(tag==true){
			break;
		}
		SPFA(n);
	}
	printf("%d",ans);
	return 0;
}
文理分科

类似于之前TJOI的线性代数,先将所有权值加起来,再减掉最少需要舍弃的即可。从源点向每个点连边,容量为 a r t art art;向汇点连边,容量为 s c i e n c e science science;给每个增加一个点连向周围的点,容量为正无穷,源点连向这些点,容量为 s a m e a r t sameart sameart s a m e s c i e n c e samescience samescience 的部分同理。
这样做的正确性是因为最小割会将所有点划分为两个不相交的集合,且两个集合的并集为全集。
时间复杂度 O ( n m n m ) O(nm \sqrt{nm}) O(nmnm ),空间复杂度 O ( n m ) O(nm) O(nm)

#include<iostream>
#include<queue>
using namespace std;
#define R register int
#define I inline
#define N 102000
#define M 280000
#define INF 999999999
const int v[5][2]={{0,0},{1,0},{0,1},{-1,0},{0,-1}};
I int Min(const int x,const int y){
	return x<y?x:y;
}
int val[4][101][101],dis[N],Cap[M],gap[N],Last[N],End[M],Ecur[N],Next[M],ct=1;
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void BFS(int&T){
	for(R i=1;i!=T;i++){
		dis[i]=T;
	}
	queue<int>Q;
	Q.push(T);
	while(Q.empty()==false){
		int v,x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(dis[v]>dis[x]+1){
				dis[v]=dis[x]+1;
				Q.push(v);
			}
		}
	}
	for(R i=1;i<=T;i++){
		Ecur[i]=Last[i];
		gap[dis[i]]++;
	}
}
I int ISAP(int u,int flow,int&S,int&T){
	if(u==T){
		return flow;
	}
	int f,tem=0;
	for(R&i=Ecur[u];i!=0;i=Next[i]){
		int v=End[i];
		if(Cap[i]!=0&&dis[v]==dis[u]-1){
			f=ISAP(v,Min(Cap[i],flow-tem),S,T);
			Cap[i]-=f;
			Cap[i^1]+=f;
			tem+=f;
			if(flow==tem||dis[S]==T){
				return tem;
			}
		}
	}
	gap[dis[u]]--;
	if(gap[dis[u]]==0){
		dis[S]=T;
	}
	dis[u]++;
	gap[dis[u]]++;
	Ecur[u]=Last[u];
	return tem;
}
I int MaxFlow(int S,int T){
	BFS(T);
	int res=0;
	while(dis[S]<T){
		res+=ISAP(S,INF,S,T);
	}
	return res;
}
int main(){
	int n,m,sum=0,T,d,x,y;
	cin>>n>>m;
	T=n*m*3+2;
	for(R i=0;i!=4;i++){
		for(R j=1;j<=n;j++){
			for(R k=1;k<=m;k++){
				cin>>val[i][j][k];
				sum+=val[i][j][k];
			}
		}
	}
	for(R i=1;i<=n;i++){
		for(R j=1;j<=m;j++){
			d=((i-1)*m+j)*3;
			Link(T-1,d,val[0][i][j]);
			Link(d,T,val[1][i][j]);
			Link(T-1,d-2,val[2][i][j]);
			Link(d-1,T,val[3][i][j]);
			for(R k=0;k!=5;k++){
				x=i+v[k][0];
				y=j+v[k][1];
				if(x!=0&&y!=0&&x<=n&&y<=m){
					x=((x-1)*m+y)*3;
					Link(d-2,x,INF);
					Link(x,d-1,INF);
				}
			}
		}
	}
	cout<<sum-MaxFlow(T-1,T);
	return 0;
}
Goods transportation

有一个显然的最大流做法,源点向点 i i i 连容量为 P i P_i Pi 的边,点 i i i 向汇点连容量为 S i S_i Si 的边,每个点向编号更大的边连边。
这样的图规模相当大,由于图的特殊性,可以通过DP来求最小割。设 f i , j f_{i,j} fi,j 表示从 1 1 1讨论到 i i i 号点时,有 j j j 个点在源点所在集合中的最小割。讨论当前的点是属于哪个集合即可:
f i , j = min ⁡ ( f i − 1 , j − 1 + S i , f i − 1 , j + j ∗ c + P i ) f_{i,j}=\min(f_{i-1,j-1}+S_i,f_{i-1,j}+j*c+P_i) fi,j=min(fi1,j1+Si,fi1,j+jc+Pi)
滚动数组即可通过。
时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n ) O(n) O(n)

#include<stdio.h>
#define R register int
#define L long long
L f[10001];
int p[10001];
int main(){
	int n,s,c;
	scanf("%d%d",&n,&c);
	for(R i=1;i<=n;i++){
		scanf("%d",p+i);
	}
	for(R i=1;i<=n;i++){
		scanf("%d",&s);
		register L v=(L)i*c+p[i];
		f[i]=f[i-1]+s;
		for(R j=i-1;j>0;j--){
			v-=c;
			f[j]+=v;
			if(f[j-1]+s<f[j]){
				f[j]=f[j-1]+s;
			}
		}
		f[0]+=p[i];
	}
	long long ans=f[0];
	for(R i=1;i<=n;i++){
		if(f[i]<ans){
			ans=f[i];
		}
	}
	printf("%lld",ans);
	return 0;
}
Sum of Abs

答案计算时为取绝对值,因此一个连通块中对答案的贡献一定都为原数或都为相反数。因此建立最小割模型。从源点连向点 ( i , 1 ) (i,1) (i,1) 边权为 B i B_i Bi;从点 ( i , 2 ) (i,2) (i,2) 连向汇点,边权为 − B i -B_i Bi;从点 ( i , 1 ) (i,1) (i,1) 连向点 ( i , 2 ) (i,2) (i,2) 边权为 A i A_i Ai;若原图中有边 ( x , y ) (x,y) (x,y),则从点 ( x , 2 ) (x,2) (x,2) 连向点 ( y , 1 ) (y,1) (y,1)、从点 ( y , 2 ) (y,2) (y,2) 连向点 ( x , 1 ) (x,1) (x,1) 边权为正无穷表示不可割断即可。
由于边权有负数,所以先减去所有的 − ∣ B i ∣ -|B_i| Bi,所有 A i A_i Ai 加上 ∣ B i ∣ |B_i| Bi 处理一下即可。
时间复杂度 O ( n m 2 ) O(nm^2) O(nm2),空间复杂度 O ( n + m ) O(n+m) O(n+m)

#include<iostream>
#include<queue>
using namespace std;
#define R register int
#define I inline
#define N 603
#define M 2405
#define INF 999999999
I int Min(const int x,const int y){
	return x<y?x:y;
}
int dis[N],a[301],gap[N],Last[N],End[M],Ecur[N],Cap[M],Next[M],ct=1;
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Cap[ct]=0;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void BFS(int&T){
	for(R i=1;i!=T;i++){
		dis[i]=T;
	}
	queue<int>Q;
	Q.push(T);
	while(Q.empty()==false){
		int v,x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(dis[v]>dis[x]+1){
				dis[v]=dis[x]+1;
				Q.push(v);
			}
		}
	}
	for(R i=1;i<=T;i++){
		Ecur[i]=Last[i];
		gap[dis[i]]++;
	}
}
I int ISAP(int u,int flow,int&S,int&T){
	if(u==T){
		return flow;
	}
	int f,v,tem=0;
	for(R&i=Ecur[u];i!=0;i=Next[i]){
		v=End[i];
		if(Cap[i]!=0&&dis[v]==dis[u]-1){
			f=ISAP(v,Min(Cap[i],flow-tem),S,T);
			Cap[i]-=f;
			Cap[i^1]+=f;
			tem+=f;
			if(flow==tem||dis[S]==T){
				return tem;
			}
		}
	}
	gap[dis[u]]--;
	if(gap[dis[u]]==0){
		dis[S]=T;
	}
	dis[u]++;
	gap[dis[u]]++;
	Ecur[u]=Last[u];
	return tem;
}
I int MaxFlow(int S,int T){
	BFS(T);
	int res=0;
	while(dis[S]<T){
		res+=ISAP(S,INF,S,T);
	}
	return res;
}
int main(){
	int n,m,x,y,T,sum=0;
	cin>>n>>m;
	T=n+1<<1;
	for(R i=1;i<=n;i++){
		cin>>a[i];
	}
	for(R i=1;i<=n;i++){
		cin>>x;
		if(x<0){
			x=-x;
			Link(i+n,T,x<<1);
		}else if(x>0){
			Link(T-1,i,x<<1);
		}
		sum+=x;
		Link(i,i+n,a[i]+x);
	}
	for(R i=0;i!=m;i++){
		cin>>x>>y;
		Link(x+n,y,INF);
		Link(y+n,x,INF);
	}
	cout<<sum-MaxFlow(T-1,T);
	return 0;
}
九月的咖啡店

对于最终选入的数,一定有 1 1 1,剩下的数要么为一个素数的极大幂,要么为一个大于 n \sqrt n n 的素数和一个小于等于 n \sqrt n n 素数极大幂之积,因为选入其它数不优。
p i p_i pi 为第 i i i 大的素数。有一种很简单的建图方法:对于 p i ⩽ n p_i \leqslant \sqrt n pin i i i,源点向 i i i 连边,容量为 1 1 1,费用为 0 0 0 i i i 向汇点连边,容量为 1 1 1,费用为 p i p_i pi 的极大幂;对于 p j > n p_j > \sqrt n pj>n j j j 类似;对于这样的 i , j i,j i,j i i i j j j 连边,容量为 1 1 1,费用为 p j p_j pj 乘以 p i p_i pi 的极大幂。
这样的建图显然正确,但边数太多跑不过。所以先将所有的素数极大幂求和,连边时 i i i j j j 的费用变为原来的费用减去 p j p_j pj p i p_i pi 的极大幂,最后求出最大费用流即可。
时间复杂度 O ( n n ) O(n \sqrt n) O(nn ),空间复杂度 n n ln ⁡ 2 n \frac{n \sqrt n}{\ln^2n} ln2nnn

#include<stdio.h>
#include<queue>
#include<math.h>
#include<algorithm>
using namespace std;
#define R register int
#define I inline
#define N 17987
#define M 75392
int prime[N],End[M],pw[87],dis[N],Last[N],Next[M],Cost[M],pre[N],ct=1;
bool vis[200001],in[N],Cap[M];
I int GetPrime(int&n){
	int res=0;
	for(R i=2;i<=n;i++){
		if(vis[i]==false){
			res++;
			prime[res]=i;
		}
		for(R j=1;i*prime[j]<=n;j++){
			vis[i*prime[j]]=true;
			if(i%prime[j]==0){
				break;
			}
		}
	}
	return res;
}
I int GetPower(int n,int p){
	int res=1;
	while(res*p<=n){
		res*=p;
	}
	return res;
}
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=true;
	Cost[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Cost[ct]=-c;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I bool SPFA(int S,int T){
	for(R i=1;i<=T;i++){
		dis[i]=-99999999;
	}
	dis[S]=0;
	queue<int>Q;
	Q.push(S);
	while(Q.empty()==false){
		int x=Q.front(),v;
		Q.pop();
		in[x]=false;
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(Cap[i]==true&&dis[v]<dis[x]+Cost[i]){
				dis[v]=dis[x]+Cost[i];
				pre[v]=i;
				if(in[v]==false){
					in[v]=true;
					Q.push(v);
				}
			}
		}
	}
	return dis[T]>0;
}
I int MaxCost(int S,int T){
	int res=0;
	while(SPFA(S,T)==true){
		res+=dis[T];
		for(R i=T;i!=S;i=End[pre[i]^1]){
			Cap[pre[i]]=false;
			Cap[pre[i]^1]=true;
		}
	}
	return res;
}
int main(){
	int n,t,m,d,cur=0,b;
	scanf("%d",&n);
	if(n<4){
		printf("%d",n*(n+1)>>1);
		return 0;
	}
	m=GetPrime(n);
	t=sqrt(n);
	t=upper_bound(prime+1,prime+m+1,t)-prime-1;
	for(R i=1;i<=t;i++){
		Link(m+1,i,0);
		pw[i]+=GetPower(n,prime[i]);
		cur+=pw[i];
	}
	for(R i=m;i!=t;i--){
		cur+=prime[i];
		Link(i,m+2,0);
		d=n/prime[i];
		for(R j=1;j<=t&&prime[j]<=d;j++){
			b=prime[i]*GetPower(d,prime[j]);
			if(b>prime[i]+pw[j]){
				Link(j,i,b-prime[i]-pw[j]);
			}
		}
	}
	printf("%d",MaxCost(m+1,m+2)+cur+1);
	return 0;
}
清华集训2017 无限之环

题面很复杂,图形也很复杂。由于是网格图,显然可以二分染色。总的来说,非空图形分五类,进行分别处理:

  1. 断头水管,只与周围一个点相连。从源点连向该点容量为 1 1 1,费用为 0 0 0。再拆四个点表示连出的方向,从该点连向这四个点,容量为 1 1 1,费用为旋转次数。
  2. 直水管,不能旋转。从源点连向该点容量为 2 2 2,费用为 0 0 0。再从该点连向两个对应拆出来的点,容量为 1 1 1,费用为 0 0 0
  3. T型水管,有三个出度。从源点连向该点容量为 3 3 3,费用为 0 0 0。再从该点连向四个拆出来的点连边,容量为 1 1 1,费用为分别为 a , b , c , d a,b,c,d a,b,c,d。不妨设原始状态对应的三条边费用分别为 a , b , c a,b,c a,b,c,则有:
    { a + b + c = 0 b + c + d = 1 c + d + a = 1 d + a + b = 2 \begin{cases} a+b+c=0\\ b+c+d=1\\ c+d+a=1\\ d+a+b=2 \end{cases} a+b+c=0b+c+d=1c+d+a=1d+a+b=2
    解得:
    { a = 1 3 b = − 2 3 c = 1 3 d = 4 3 \begin{cases} a=\frac{1}3\\ b=-\frac{2}3\\ c=\frac{1}3\\ d=\frac{4}3 \end{cases} a=31b=32c=31d=34
  4. 十字形水管,不需要旋转。从源点分别连向对应的四个点,容量为 1 1 1,费用为 0 0 0即可。
  5. 弧形水管,有两个口。从源点连向该点,容量为 2 2 2,费用为 0 0 0,从该点连向拆出的两个点分别表示连向同一行或同一列的点,容量为 1 1 1,费用为 0 0 0。再从这两个点连向四个点中对应的点,容量为 1 1 1,费用取决于初始状态。

这是源点部分的连边,汇点部分类似。相邻格子对应点连起来即可。
最后可以通过是否满流判断有无解。
空间复杂度 O ( n m ) O(nm) O(nm)

#include<stdio.h>
#include<queue>
using namespace std;
#define R register int
#define I inline
#define N 12009
#define M 99000
const int v[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
I int Popcount(int x){
	return(x&1)+(x>>1&1)+(x>>2&1)+(x>>3);
}
int a[2002][2002],id[2001][2001],Last[N],End[M],Next[M],Cost[M],Cap[M],dis[N],pre[N],ct=1;
I void Link(int x,int y,int cap,int cost){
	ct++;
	End[ct]=y;
	Cap[ct]=cap;
	Cost[ct]=cost;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Cost[ct]=-cost;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void Link2(const int type,int x,int y,int a,int b){
	if(type==0){
		Link(x,y,a,b);
	}else{
		Link(y,x,a,b);
	}
}
bool in[N];
I bool SPFA(int S,int T){
	for(R i=1;i<=T;i++){
		dis[i]=M;
	}
	dis[S]=0;
	queue<int>Q;
	Q.push(S);
	while(Q.empty()==false){
		int x=Q.front(),v;
		Q.pop();
		in[x]=false;
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(Cap[i]!=0&&dis[v]>dis[x]+Cost[i]){
				dis[v]=dis[x]+Cost[i];
				pre[v]=i;
				if(in[v]==false){
					in[v]=true;
					Q.push(v);
				}
			}
		}
	}
	return dis[T]!=M;
}
int main(){
	int n,m,t,T=0,x,y,f=0,f2=0;
	scanf("%d%d",&n,&m);
	for(R i=1;i<=n;i++){
		for(R j=1;j<=m;j++){
			scanf("%d",a[i]+j);
			t=Popcount(a[i][j]);
			if(a[i][j]==5||a[i][j]==10){
				T+=4;
			}else if(t==2){
				T+=6;
			}else{
				T+=5;
			}
			id[i][j]=T;
		}
	}
	T+=2;
	for(R i=1;i<=n;i++){
		for(R j=1;j<=m;j++){
			t=Popcount(a[i][j]);
			x=(i^j)&1;
			if(a[i][j]==5){
				if(x==0){
					Link(T-1,id[i][j],1,0);
					Link(T-1,id[i][j]-2,1,0);
				}else{
					Link(id[i][j],T,1,0);
					Link(id[i][j]-2,T,1,0);
				}
			}else if(a[i][j]==10){
				if(x==0){
					Link(T-1,id[i][j]-1,1,0);
					Link(T-1,id[i][j]-3,1,0);
				}else{
					Link(id[i][j]-1,T,1,0);
					Link(id[i][j]-3,T,1,0);
				}
			}else if(t==2){
				if(x==0){
					Link(T-1,id[i][j]-4,1,0);
					Link(T-1,id[i][j]-5,1,0);
				}else{
					Link(id[i][j]-4,T,1,0);
					Link(id[i][j]-5,T,1,0);
				}
				Link2(x,id[i][j]-4,id[i][j],1,(a[i][j]&1)==1?0:3);
				Link2(x,id[i][j]-5,id[i][j]-1,1,(a[i][j]>>1&1)==1?0:3);
				Link2(x,id[i][j]-4,id[i][j]-2,1,(a[i][j]>>2&1)==1?0:3);
				Link2(x,id[i][j]-5,id[i][j]-3,1,a[i][j]>>3==1?0:3);
			}else if(a[i][j]!=0){
				if(x==0){
					Link(T-1,id[i][j]-4,t,0);
				}else{
					Link(id[i][j]-4,T,t,0);
				}
				if(t==1){
					for(R k=0;k!=4;k++){
						if((a[i][j]>>k&1)==1){
							Link2(x,id[i][j]-4,id[i][j]-k,1,0);
						}else if((a[i][j]>>(k^2)&1)==1){
							Link2(x,id[i][j]-4,id[i][j]-k,1,6);
						}else{
							Link2(x,id[i][j]-4,id[i][j]-k,1,3);
						}
					}
				}else if(t==4){
					for(R k=0;k!=4;k++){
						Link2(x,id[i][j]-4,id[i][j]-k,1,0);
					}
				}else{
					int p;
					for(R k=0;k!=4;k++){
						if((a[i][j]>>k&1)==0){
							p=k;
							break;
						}
					}
					Link2(x,id[i][j]-4,id[i][j]-p,1,4);
					p=p+1&3;
					Link2(x,id[i][j]-4,id[i][j]-p,1,1);
					p=p+1&3;
					Link2(x,id[i][j]-4,id[i][j]-p,1,-2);
					p=p+1&3;
					Link2(x,id[i][j]-4,id[i][j]-p,1,1);
				}
			}
			if(x==0){
				f+=t;
				for(R k=0;k!=4;k++){
					x=i+v[k][0];
					y=j+v[k][1];
					if(a[x][y]!=0){
						Link(id[i][j]-k,id[x][y]-(k^2),1,0);
					}
				}
			}else{
				f2+=t;
			}
		}
	}
	if(f2>f){
		f=f2;
	}
	t=0;
	while(SPFA(T-1,T)==true){
		f2=M;
		for(R i=T;i!=T-1;i=End[pre[i]^1]){
			if(Cap[pre[i]]<f2){
				f2=Cap[pre[i]];
			}
		}
		t+=dis[T]*f2;
		f-=f2;
		for(R i=T;i!=T-1;i=End[pre[i]^1]){
			Cap[pre[i]]-=f2;
			Cap[pre[i]^1]+=f2;
		}
	}
	if(f==0){
		printf("%d",t/3);
	}else{
		printf("-1");
	}
	return 0;
}
志愿者招募加强版

可以将一种志愿者很多段等效为多种志愿者,然后就与非加强版类似了。
x i x_i xi 为第 i i i 种志愿者招募人数,向量数列 B B B,则有:
∀ i , x ⋅ B i ⩾ A i \forall i,x \cdot B_i \geqslant A_i i,xBiAi
要求最小化 x ⋅ C x \cdot C xC
这是一个线性规划问题,引入向量 y , y i ∈ N y,y_i \in \N y,yiN,将条件转化为:
∀ i , x i ⋅ B i − y i − A i = 0 \forall i,x_i \cdot B_i -y_i-A_i=0 i,xiBiyiAi=0
对相邻的式子差分后,得到等价的方程组,但每个未知数 x i x_i xi 只会出现 2 2 2次,且系数为 − 1 -1 1 1 1 1各一次。把等式看做点,数看做边,正数为入流量,负数为出流量。 i i i 号点向 i + 1 i+1 i+1 号点连边表示 y i y_i yi t i + 1 t_i+1 ti+1 s i s_i si 连边表示 x i x_i xi,费用为 c i c_i ci;源点向常数项为正的等式连边,常数项为负的等式向汇点连边。
由于边数较多,跑多路增广费用流即可。
空间复杂度 O ( n m ) O(nm) O(nm)

#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 1009
#define M 999999
#define INF 2147483647
I int Min(const int x,const int y){
	return x<y?x:y;
}
int Last[N],Ecur[N],lev[N],Cap[M],Cost[M],Next[M],End[M],ct=1;
I void Link(int x,int y,int cap,int cost){
	ct++;
	End[ct]=y;
	Cap[ct]=cap;
	Cost[ct]=cost;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Cost[ct]=-cost;
	Next[ct]=Last[y];
	Last[y]=ct;
}
L dis[N];
bool in[N];
I bool SPFA(int S,int T){
	for(R i=1;i<=T;i++){
		dis[i]=9999999999999;
	}
	dis[S]=0;
	queue<int>Q;
	Q.push(S);
	while(Q.empty()==false){
		int x=Q.front(),v;
		in[x]=false;
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(Cap[i]!=0&&dis[v]>dis[x]+Cost[i]){
				dis[v]=dis[x]+Cost[i];
				lev[v]=lev[x]+1;
				if(in[v]==false){
					in[v]=true;
					Q.push(v);
				}
			}
		}
	}
	return dis[T]!=9999999999999;
}
I int Dinic(int u,int flow,int&S,int&T){
	if(u==T){
		return flow;
	}
	in[u]=true;
	int tem=0,f,v;
	for(R i=Ecur[u];i!=0;i=Next[i]){
		Ecur[u]=i;
		v=End[i];
		if(in[v]==false&&Cap[i]!=0&&lev[v]==lev[u]+1&&dis[v]==dis[u]+Cost[i]){
			f=Dinic(v,Min(flow,Cap[i]),S,T);
			tem+=f;
			flow-=f;
			Cap[i]-=f;
			Cap[i^1]+=f;
			if(flow==0){
				return tem;
			}
		}
	}
	return tem;
}
I L MinCost(int S,int T){
	L res=0;
	while(SPFA(S,T)==true){
		for(R i=1;i<=T;i++){
			Ecur[i]=Last[i];
		}
		res+=dis[T]*Dinic(S,INF,S,T);
		for(R i=1;i<=T;i++){
			in[i]=false;
		}
	}
	return res;
}
int main(){
	int n,m,x,y,z,a,b=0;
	scanf("%d%d",&n,&m);
	for(R i=1;i<=n;i++){
		Link(i,i+1,INF,0);
		scanf("%d",&a);
		if(a>b){
			Link(i,n+3,a-b,0);
		}else{
			Link(n+2,i,b-a,0);
		}
		b=a;
	}
	Link(n+2,n+1,b,0);
	for(R i=m;i!=0;i--){
		scanf("%d",&z);
		vector<pair<int,int> >S;
		for(R j=z;j!=0;j--){
			scanf("%d%d",&x,&y);
			S.push_back(make_pair(x,y));
		}
		scanf("%d",&z);
		for(vector<pair<int,int> >::iterator T=S.begin();T!=S.end();T++){
			Link(T->second+1,T->first,INF,z);
		}
	}
	printf("%lld",MinCost(n+2,n+3));
	return 0;
}
六省联考2017 寿司餐厅

题面很复杂,最小割模型并不难建立,但涉及负权边。由于选择了 d i , j d_{i,j} di,j 则一定会选择 d i + 1 , j d_{i+1,j} di+1,j d i , j − 1 d_{i,j-1} di,j1。于是可以建立最大权闭合子图模型。对于花费 c x cx cx 部分,对每个 d i , i d_{i,i} di,i 减去 a i a_i ai 即可。对于花费 x 2 x^2 x2,可以视为选了 a i a_i ai 就要选 − a i 2 -a_i^2 ai2 即可。综上所述,所有的情况都可以在模型中体现。
空间复杂度 O ( n 2 ) O(n^2) O(n2)

#include<stdio.h>
#include<queue>
#include<map>
#include<vector>
using namespace std;
#define R register int
#define I inline
#define N 45000
#define M 190000
#define INF 2100000000
I int Min(const int x,const int y){
	return x<y?x:y;
}
int a[101],id[101][101],d[101][101],dis[N],Cap[M],gap[N],Last[N],End[M],Ecur[N],Next[M],ct=1;
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void BFS(int&T){
	for(R i=1;i!=T;i++){
		dis[i]=T;
	}
	queue<int>Q;
	Q.push(T);
	while(Q.empty()==false){
		int v,x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			v=End[i];
			if(dis[v]>dis[x]+1){
				dis[v]=dis[x]+1;
				Q.push(v);
			}
		}
	}
	for(R i=1;i<=T;i++){
		Ecur[i]=Last[i];
		gap[dis[i]]++;
	}
}
I int ISAP(int u,int flow,int&S,int&T){
	if(u==T){
		return flow;
	}
	int f,tem=0;
	for(R&i=Ecur[u];i!=0;i=Next[i]){
		int v=End[i];
		if(Cap[i]!=0&&dis[v]==dis[u]-1){
			f=ISAP(v,Min(Cap[i],flow-tem),S,T);
			Cap[i]-=f;
			Cap[i^1]+=f;
			tem+=f;
			if(flow==tem||dis[S]==T){
				return tem;
			}
		}
	}
	gap[dis[u]]--;
	if(gap[dis[u]]==0){
		dis[S]=T;
	}
	dis[u]++;
	gap[dis[u]]++;
	Ecur[u]=Last[u];
	return tem;
}
I int MaxFlow(int S,int T){
	BFS(T);
	int res=0;
	while(dis[S]<T){
		res+=ISAP(S,INF,S,T);
	}
	return res;
}
vector<int>A[1001];
int main(){
	int n,m,T=0,b,sum=0;
	scanf("%d%d",&n,&m);
	b=(n+1)*n>>1;
	map<int,int>S;
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
		A[a[i]].push_back(i);
		if(S.count(a[i])==0){
			b++;
			S[a[i]]=b;
		}
	}
	for(R i=1;i<=n;i++){
		for(R j=i;j<=n;j++){
			scanf("%d",d[i]+j);
			T++;
			id[i][j]=T;
		}
		d[i][i]-=a[i];
	}
	T=b+2;
	for(R i=1;i<=n;i++){
		for(R j=i;j<=n;j++){
			if(d[i][j]>0){
				sum+=d[i][j];
				Link(T-1,id[i][j],d[i][j]);
			}else if(d[i][j]<0){
				Link(id[i][j],T,-d[i][j]);
			}
		}
		for(R j=i+1;j<=n;j++){
			Link(id[i][j],id[i][j-1],INF);
			Link(id[i][j],id[i+1][j],INF);
		}
		if(m==1){
			Link(id[i][i],S[a[i]],INF);
		}
	}
	if(m==1){
		for(map<int,int>::iterator T1=S.begin();T1!=S.end();T1++){
			Link(T1->second,T,T1->first*T1->first);
		}
	}
	printf("%d",sum-MaxFlow(T-1,T));
	return 0;
}
最大K段和

给出一个长度为 n n n 的整数数列 a a a m m m 次操作,每次操作会修改其中的一个值或询问一个子区间中取出至多 k k k 个子区间的最大和。
1 ⩽ n , m ⩽ 1 0 5 , 1 ⩽ k ⩽ 20 , − 500 ⩽ a i ⩽ 500 1 \leqslant n,m \leqslant 10^5,1 \leqslant k \leqslant 20,-500 \leqslant a_i \leqslant 500 1n,m105,1k20,500ai500
时间限制1s,空间限制256MB

类似于小白逛公园,用线段树记录二十段的各种状态的答案然后合并。这样做的复杂度为 O ( ( n + m ) k 2 log ⁡ 2 n ) O((n+m)k^2 \log_2 n) O((n+m)k2log2n)
这里用线段树模拟费用流,每次查询时查不超过 k k k 次子区间最大子段和,查出最大和和位置后将改区间取反继续查,如果最大和不超过 0 0 0时停止。可以采用持久化的方法还原线段树。
时间复杂度 O ( ( n + m k ) log ⁡ 2 n ) O((n+mk) \log_2 n) O((n+mk)log2n),空间复杂度 O ( n + k log ⁡ 2 n ) O(n+k \log_2 n) O(n+klog2n)

#include<stdio.h>
#define R register int
#define I inline
int a[100001],ct=1;
struct Segment{
	int Sum,AnsL,AnsR,Llen,Rlen,Ans,Lans,Rans;
	I void InitNode(int x,int y){
		Sum=Ans=Lans=Rans=y;
		Llen=Rlen=1;
		AnsL=AnsR=x;
	}
};
I void MergeSegment(Segment&A,Segment&B,Segment&C,int lf,int mid,int rt){
	C.Sum=A.Sum+B.Sum;
	if(A.Lans>A.Sum+B.Lans){
		C.Lans=A.Lans;
		C.Llen=A.Llen;
	}else{
		C.Lans=A.Sum+B.Lans;
		C.Llen=mid-lf+1+B.Llen;
	}
	if(A.Rans+B.Sum>B.Rans){
		C.Rans=A.Rans+B.Sum;
		C.Rlen=A.Rlen+rt-mid;
	}else{
		C.Rans=B.Rans;
		C.Rlen=B.Rlen;
	}
	int l=A.Ans,r=B.Ans,m=A.Rans+B.Lans,c;
	c=l>r?l:r;
	c=c>m?c:m;
	C.Ans=c;
	if(l==c){
		C.AnsL=A.AnsL;
		C.AnsR=A.AnsR;
	}else if(r==c){
		C.AnsL=B.AnsL;
		C.AnsR=B.AnsR;
	}else{
		C.AnsL=mid-A.Rlen+1;
		C.AnsR=mid+B.Llen;
	}
}
struct SegmentNode{
	Segment S[2];
	int Ls,Rs;
	bool Lazy;
}T[300000];
I void GetNode(int&x){
	ct++;
	x=ct;
	T[x].Lazy=false;
	T[x].Ls=T[x].Rs=0;
}
I void Update(int x,int lf,int rt){
	int mid=lf+rt>>1;
	MergeSegment(T[T[x].Ls].S[0],T[T[x].Rs].S[0],T[x].S[0],lf,mid,rt);
	MergeSegment(T[T[x].Ls].S[1],T[T[x].Rs].S[1],T[x].S[1],lf,mid,rt);
}
I void Init(int p,int lf,int rt){
	if(lf==rt){
		T[p].S[0].InitNode(lf,a[rt]);
		T[p].S[1].InitNode(lf,-a[rt]);
	}else{
		GetNode(T[p].Ls);
		GetNode(T[p].Rs);
		Init(T[p].Ls,lf,lf+rt>>1);
		Init(T[p].Rs,lf+rt+2>>1,rt);
		Update(p,lf,rt);
	}
}
I void Modify(int p,int lf,int rt,const int x){
	if(lf==rt){
		T[p].S[0].InitNode(x,a[x]);
		T[p].S[1].InitNode(x,-a[x]);
	}else{
		int mid=lf+rt>>1;
		if(x>mid){
			Modify(T[p].Rs,mid+1,rt,x);
		}else{
			Modify(T[p].Ls,lf,mid,x);
		}
		Update(p,lf,rt);
	}
}
I void ReverseNode(int p){
	Segment A=T[p].S[0];
	T[p].S[0]=T[p].S[1];
	T[p].S[1]=A;
}
I void PutDown(int p,bool tag){
	if(tag==true){
		int x,y;
		GetNode(x);
		GetNode(y);
		T[x]=T[T[p].Ls];
		T[y]=T[T[p].Rs];
		T[p].Ls=x;
		T[p].Rs=y;
		if(T[p].Lazy==true){
			T[T[p].Ls].Lazy^=true;
			ReverseNode(T[p].Ls);
			T[T[p].Rs].Lazy^=true;
			ReverseNode(T[p].Rs);
			T[p].Lazy=false;
		}
	}
}
I void GetAns(int p,int lf,int rt,const int l,const int r,Segment&A){
	if(l<=lf&&rt<=r){
		Segment B;
		MergeSegment(A,T[p].S[0],B,l-1,lf-1,rt);
		A=B;
	}else{
		if(T[p].Lazy==true){
			PutDown(p,lf!=rt);
		}
		int mid=lf+rt>>1;
		if(l<=mid){
			GetAns(T[p].Ls,lf,mid,l,r,A);
		}
		if(r>mid){
			GetAns(T[p].Rs,mid+1,rt,l,r,A);
		}
	}
}
I void Reverse(int p1,int p2,int lf,int rt,const int l,const int r){
	if(l<=lf&&rt<=r){
		T[p2].Lazy^=true;
		ReverseNode(p2);
	}else{
		PutDown(p2,lf!=rt);
		int mid=lf+rt>>1;
		if(l<=mid){
			Reverse(T[p1].Ls,T[p2].Ls,lf,mid,l,r);
		}
		if(r>mid){
			Reverse(T[p1].Rs,T[p2].Rs,mid+1,rt,l,r);
		}
		Update(p2,lf,rt);
	}
}
int main(){
	int n,opt,l,r,k;
	scanf("%d",&n);
	for(R i=1;i<=n;i++){
		scanf("%d",a+i);
	}
	Init(1,1,n);
	const int CURRENT=ct;
	scanf("%d",&k);
	for(R i=k;i!=0;i--){
		scanf("%d%d%d",&opt,&l,&r);
		if(opt==0){
			a[l]=r;
			Modify(1,1,n,l);
		}else{
			scanf("%d",&k);
			int cur=1,ans=0;
			for(R j=0;j!=k;j++){
				Segment S;
				S.InitNode(l-1,0);
				GetAns(cur,1,n,l,r,S);
				if(S.Ans<=0){
					break;
				}
				ans+=S.Ans;
				int root;
				GetNode(root);
				T[root]=T[cur];
				Reverse(cur,root,1,n,S.AnsL,S.AnsR);
				cur=root;
			}
			printf("%d\n",ans);
			ct=CURRENT;
		}
	}
	return 0;
}
Orz the MST

对于一条树边边权不会增加,一条非树边不会减少,这样代价只有一种,统一为 c i c_i ci,设 x i x_i xi 为第 i i i 条边的变化量,得到:

{ w i − w j ⩽ x i + x j x i ∈ N min ⁡ ∑ i c i x i \begin{cases} w_i-w_j \leqslant x_i+x_j\\ x_i \in N\\ \min \sum_i c_i x_i \end{cases} wiwjxi+xjxiNminicixi

其中第 j j j 号边为非树边,连接的两个端点在树上的简单路径覆盖了第 i i i 号边。将其转为对偶问题,同时引入变量 y i , j y_{i,j} yi,j

{ ∑ j y i , j ⩽ a i − ∑ i y i , j ⩾ − b i y i , j ∈ N max ⁡ ∑ i , j ( w i − w j ) y i , j \begin{cases} \sum_j y_{i,j} \leqslant a_i\\ -\sum_i y_{i,j} \geqslant -b_i\\ y_{i,j} \in N\\ \max \sum_{i,j} (w_i-w_j)y_{i,j} \end{cases} jyi,jaiiyi,jbiyi,jNmaxi,j(wiwj)yi,j

每个 y y y 变量出现了两次,一次系数为正,一次系数为负,把正值当做出流,负值当做入流,建立费用流模型。
空间复杂度 O ( n m ) O(nm) O(nm)

#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
#define R register int
#define I inline
#define N 440000
#define INF 999999999
struct Edge{
	int End,Id;
};
I Edge Pair(int y,int d){
	Edge res;
	res.End=y;
	res.Id=d;
	return res;
}
vector<Edge>G[301];
int Last[1005],w[1001],f[301],id[301],Cap[N],dis[1005],pre[1005],Cost[N],Next[N],End[N],ct=1;
I void Link(int x,int y,int c,int w){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Cost[ct]=w;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Cost[ct]=-w;
	Next[ct]=Last[y];
	Last[y]=ct;
}
I void DFS(int x,int F){
	for(Edge T:G[x]){
		int v=T.End;
		if(v!=F){
			f[v]=x;
			id[v]=T.Id;
			DFS(v,x);
		}
	}
}
bool in[1005];
I bool SPFA(int S,int T,const int n){
	for(R i=1;i<=n;i++){
		dis[i]=-INF;
	}
	dis[S]=0;
	queue<int>Q;
	Q.push(S);
	in[S]=true;
	while(Q.empty()==false){
		int x=Q.front();
		in[x]=false;
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			int y=End[i];
			if(Cap[i]!=0&&dis[y]<dis[x]+Cost[i]){
				dis[y]=dis[x]+Cost[i];
				pre[y]=i;
				if(in[y]==false){
					in[y]=true;
					Q.push(y);
				}
			}
		}
	}
	return dis[T]>0;
}
I int MaxCost(int S,int T,const int n){
	int res=0;
	while(SPFA(S,T,n)==true){
		int flow=INF;
		for(R i=T;i!=S;i=End[pre[i]^1]){
			if(flow>Cap[pre[i]]){
				flow=Cap[pre[i]];
			}
		}
		res+=flow*dis[T];
		for(R i=T;i!=S;i=End[pre[i]^1]){
			Cap[pre[i]]-=flow;
			Cap[pre[i]^1]+=flow;
		}
	}
	return res;
}
int main(){
	int n,m,x,y,type,a,b,S,T;
	scanf("%d%d",&n,&m);
	vector<pair<int,Edge>>E;
	S=m+1;
	T=m+2;
	for(R i=1;i<=m;i++){
		scanf("%d%d%d%d%d%d",&x,&y,w+i,&type,&a,&b);
		if(type==1){
			G[x].push_back(Pair(y,i));
			G[y].push_back(Pair(x,i));
			Link(S,i,b,0);
			Link(i,T,INF,0);
		}else{
			E.push_back(make_pair(x,Pair(y,i)));
			Link(S,i,INF,0);
			Link(i,T,a,0);
		}
	}
	for(auto T:E){
		x=T.first;
		y=T.second.Id;
		DFS(x,0);
		for(R i=T.second.End;i!=x;i=f[i]){
			Link(id[i],y,INF,w[id[i]]-w[y]);
		}
	}
	printf("%d",MaxCost(S,T,T));
	return 0;
}
联合省选2020 魔法商店

对于异或和的限制,若将一个 A A A 中的 i i i 删去后可以加入 j j j,则最终的 v i ⩽ v j v_i \leqslant v_j vivj,对于集合 B B B 同理。这里用线性基判断。
之后问题转化为了 L 2 L_2 L2保序回归问题,整体二分最大权闭合子图判断即可。
空间复杂度 O ( n m ) O(nm) O(nm)

#include<stdio.h>
#include<vector>
#include<map>
#include<queue>
using namespace std;
#define R register int
#define L unsigned long long
#define I inline
#define N 265000
#define INF 1070000000
I int Min(const int x,const int y){
	return x<y?x:y;
}
L c[1001];
int v[1001],w[1001],Ecur[1003],Last[1003],dis[1003],gap[1003],Next[N],End[N],Cap[N],a[64],b[64],ct;
struct Basis{
	L A[63];
	int Len;
	I void Clear(){
		Len=0;
	}
	I bool Insert(L x,bool tag){
		for(R i=0;i!=Len;i++){
			if((x^A[i])<x){
				x^=A[i];
			}
		}
		if(x!=0&&tag==true){
			A[Len]=x;
			Len++;
		}
		return x!=0;
	}
};
I void Link(int x,int y,int c){
	ct++;
	End[ct]=y;
	Cap[ct]=c;
	Next[ct]=Last[x];
	Last[x]=ct;
	ct++;
	End[ct]=x;
	Cap[ct]=0;
	Next[ct]=Last[y];
	Last[y]=ct;
}
vector<int>G[1001];
I void SPFA(int T,const int n){
	for(R i=1;i<=n;i++){
		dis[i]=n;
	}
	dis[T]=0;
	queue<int>Q;
	Q.push(T);
	while(Q.empty()==false){
		int x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			int y=End[i];
			if(dis[y]>dis[x]+1){
				dis[y]=dis[x]+1;
				Q.push(y);
			}
		}
	}
	for(R i=1;i<=n;i++){
		Ecur[i]=Last[i];
		gap[dis[i]]++;
	}
}
bool tag[1003];
I int ISAP(int u,int flow,const int S,const int T,const int n){
	if(u==T){
		return flow;
	}
	int f,v,tem=0;
	for(R&i=Ecur[u];i!=0;i=Next[i]){
		v=End[i];
		if(Cap[i]!=0&&dis[v]==dis[u]-1){
			f=ISAP(v,Min(Cap[i],flow-tem),S,T,n);
			Cap[i]-=f;
			Cap[i^1]+=f;
			tem+=f;
			if(tem==flow||dis[S]==n){
				return tem;
			}
		}
	}
	gap[dis[u]]--;
	if(gap[dis[u]]==0){
		dis[S]=n;
	}
	dis[u]++;
	gap[dis[u]]++;
	Ecur[u]=Last[u];
	return tem;
}
I void MaxFlow(int S,int T,const int n){
	SPFA(T,n);
	while(dis[S]<n){
		ISAP(S,INF,S,T,n);
	}
	for(R i=1;i<=n;i++){
		tag[i]=false;
	}
	queue<int>Q;
	Q.push(S);
	tag[S]=true;
	while(Q.empty()==false){
		int x=Q.front();
		Q.pop();
		for(R i=Last[x];i!=0;i=Next[i]){
			if(Cap[i]!=0&&tag[End[i]]==false){
				tag[End[i]]=true;
				Q.push(End[i]);
			}
		}
	}
}
I void Find(vector<int>&A,int l,int r){
	map<int,int>Q;
	int S=A.size();
	for(R i=0;i!=S;i++){
		Q[A[i]]=i+1;
	}
	S++;
	for(R i=1;i!=S+2;i++){
		Last[i]=0;
	}
	ct=1;
	for(R i=0;i!=S-1;i++){
		for(int T:G[A[i]]){
			if(Q.count(T)!=0){
				Link(i+1,Q[T],INF);
			}
		}
	}
	int mid=l+r>>1;
	for(R i=0;i!=S-1;i++){
		int x=l==r-1?(v[A[i]]-l<<1)-1:v[A[i]]-mid;
		if(x>0){
			Link(S,i+1,x);
		}else{
			Link(i+1,S+1,-x);
		}
	}
	MaxFlow(S,S+1,S+1);
	vector<int>C,D;
	for(R i=1;i!=S;i++){
		if(tag[i]==false){
			if(l==r-1){
				w[A[i-1]]=l;
			}else{
				C.push_back(A[i-1]);
			}
		}else if(l==r-1){
			w[A[i-1]]=r;
		}else{
			D.push_back(A[i-1]);
		}
	}
	if(l<r-1){
		if(C.empty()==false){
			Find(C,l,mid);
		}
		if(D.empty()==false){
			Find(D,mid,r);
		}
	}
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	vector<int>A;
	for(int i=1;i<=n;i++){
		scanf("%llu",c+i);
		A.push_back(i);
	}
	for(R i=1;i<=n;i++){
		scanf("%d",v+i);
	}
	for(R i=0;i!=m;i++){
		scanf("%d",a+i);
	}
	for(R i=0;i!=m;i++){
		Basis B;
		B.Clear();
		for(R j=0;j!=m;j++){
			if(i!=j){
				B.Insert(c[a[j]],true);
			}
		}
		for(int j=1;j<=n;j++){
			if(j!=a[i]&&B.Insert(c[j],false)==true){
				G[a[i]].push_back(j);
			}
		}
	}
	for(R i=0;i!=m;i++){
		scanf("%d",b+i);
	}
	for(R i=0;i!=m;i++){
		Basis B;
		B.Clear();
		for(R j=0;j!=m;j++){
			if(i!=j){
				B.Insert(c[b[j]],true);
			}
		}
		for(R j=1;j<=n;j++){
			if(j!=b[i]&&B.Insert(c[j],false)==true){
				G[j].push_back(b[i]);
			}
		}
	}
	Find(A,0,1000000);
	L ans=0;
	for(R i=1;i<=n;i++){
		ans+=(L)(v[i]-w[i])*(v[i]-w[i]);
	}
	printf("%llu",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值