网络流杂题选做
最近总是不知不觉地就做到了网络流的题,感觉知识树要点偏。算了,先随便写点东西再说。
[AHOI2009]最小割
显然一条边至少要满流才有可能是被割边。
先考虑存在性问题,若一条边为某个最小割割边集中的边,那么这条边一定是该最小割中无法取代的。对于一条流量为 \(0\) 的边 \((u,v)\),如果在残量网络上存在从 \(u\) 到 \(v\) 的路径,即在残量网络上 \(u,v\) 属于同一连通块(因为 \((v,u)\) 这条边必然在残量网络中),那么可以从这条路径中分点流量到另一条路径,就不存在一个最小代价路径切断方案,其中该道路被切断。
再考虑必要性问题,先给出结论,当且仅当 \(u\) 和 \(S\) 在同一强连通分量中,同时 \(v\) 和 \(T\) 在同一强连通分量中。因为一条边如果没满流,那么残量网络上必然同时存在原边和反边,两端点同属一强连通分量。考虑缩点后的 DAG,从 \(S\) 到 \(T\) 的路径上如果有不止一条边,那么没有一条边是必然被切断的。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e4+10,M=1e5+10,inf=INT_MAX;
int n,m,S,T,tot=1,head[N];
struct edge{
int u,v,nxt,w;
}e[M<<2];
void add(int u,int v,int w){
e[++tot].v=v;
e[tot].u=u;
e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
}
int dep[N],cur[N];
bool bfs(int S,int T){
for(int i=1;i<=n;i++){
dep[i]=0;
}
dep[S]=1;
queue<int>q;
q.push(S);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(!dep[v]&&w){
dep[v]=dep[u]+1;
if(v==T){
return 1;
}
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int flow,int T){
if(u==T){
return flow;
}
int s=0;
for(int i=cur[u];i;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,w=e[i].w;
if(dep[v]==dep[u]+1&&w){
int res=dfs(v,min(flow,w),T);
s+=res;
flow-=res;
e[i].w-=res;
e[i^1].w+=res;
}
if(!flow){
break;
}
}
if(!s){
dep[u]=0;
}
return s;
}
int dfn[N],low[N],col[N],idx,col_num,stk[N],top,in_stack[N];
void tarjan(int u){
low[u]=dfn[u]=++idx;
stk[++top]=u;
in_stack[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(!w){
continue;
}
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in_stack[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
col[u]=++col_num;
in_stack[u]=0;
while(stk[top]!=u){
col[stk[top]]=col_num;
in_stack[stk[top]]=0;
top--;
}
top--;
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
read(n),read(m),read(S),read(T);
for(int i=1;i<=m;i++){
int u,v,w;
read(u),read(v),read(w);
add(u,v,w);
add(v,u,0);
}
while(bfs(S,T)){
for(int i=1;i<=n;i++){
cur[i]=head[i];
}
dfs(S,inf,T);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int i=1;i<=m;i++){
if(!e[i<<1].w){
int u=e[i<<1].u,v=e[i<<1].v;
if(col[u]!=col[v]){
write_space(1);
}
else{
write_space(0);
}
if(col[u]==col[S]&&col[v]==col[T]){
write_endl(1);
}
else{
write_endl(0);
}
}
else{
puts("0 0");
}
}
return 0;
}
[NOI2009] 植物大战僵尸
我们将一个植物前面的植物视作也保护它的植物。因为要击溃一个植物,必须击溃保护它的植物。所以,从保护的植物连边到被保护的植物。但是可能有环,环上的植物是不可能被击溃,所以做一遍拓扑排序,只有被遍历过的植物才有可能是被击溃的植物。我们只保留遍历过的植物,和边上的两个植物都遍历过的边。那么最后题目就变成了求这个图的最大权闭合子图。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=610,M=1e6+10,inf=1e9 ;
int n,m,val[N];
int id(int x,int y){
return (x-1)*m+y;
}
vector<int>G[N],out[N];
int deg[N],vis[N],S,T,tot=1,head[N],ans;
void topo(){
queue<int>q;
for(int i=1;i<=n*m;i++){
if(!deg[i]){
q.push(i);
}
}
while(!q.empty()){
int u=q.front();
vis[u]=1;
q.pop();
for(auto v:G[u]){
deg[v]--;
if(!deg[v]){
q.push(v);
}
}
}
}
struct node{
int v,w,nxt;
}e[M<<1];
void add(int u,int v,int w){
e[++tot].v=v;
e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
}
int dep[N],cur[N];
bool bfs(int S,int T){
for(int i=1;i<=T;i++){
dep[i]=0;
}
queue<int>q;
q.push(S);
dep[S]=1;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(w&&!dep[v]){
dep[v]=dep[u]+1;
if(v==T){
return 1;
}
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int flow,int T){
if(u==T){
return flow;
}
int s=0;
for(int i=cur[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(w&&dep[v]==dep[u]+1){
int res=dfs(v,min(flow,w),T);
e[i].w-=res;
e[i^1].w+=res;
s+=res;
flow-=res;
}
if(!flow){
break;
}
}
if(!s){
dep[u]=0;
}
return s;
}
int dinic(int S,int T){
int sum=0;
while(bfs(S,T)){
for(int i=1;i<=T;i++){
cur[i]=head[i];
}
sum+=dfs(S,inf,T);
}
return sum;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
read(n),read(m);
S=n*m+1,T=n*m+2;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
read(val[id(i,j)]);
int sum;
read(sum);
for(int k=1,x,y;k<=sum;k++){
read(x),read(y);
x++,y++;
out[id(i,j)].pb(id(x,y));
G[id(i,j)].pb(id(x,y));
deg[id(x,y)]++;
}
if(j<m){
out[id(i,j+1)].pb(id(i,j));
G[id(i,j+1)].pb(id(i,j));
deg[id(i,j)]++;
}
}
}
topo();
for(int i=1;i<=n*m;i++){
if(!vis[i]){
continue;
}
if(val[i]>0){
add(S,i,val[i]);
add(i,S,0);
ans+=val[i];
}
else{
add(i,T,-val[i]);
add(T,i,0);
}
for(auto x:out[i]){
if(!vis[x]){
continue;
}
add(x,i,inf);
add(i,x,0);
}
}
write_endl(ans-dinic(S,T));
return 0;
}
[SDOI2013]费用流
别看到题目叫作费用流,就一股脑写费用流了。注意到题目中有句话,Bob在分配单位花费之前,已经知道Alice所给出的最大流方案。那么Bob的赋权方法就是确定的,将所有权值赋给这个最大流方案中流量最大的边,那么题目要求的东西就变成了求最大流并求一个最大流方案,使得流量最大的边流量最小。
求最大值最小,二分最大边流量 \(x\),所有边的流量对 \(x\) 取 \(\min\)。看这样的图上的最大流是否是原图上的最大流,是则说明这个最大流量是合法,反之亦然。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=110,M=1e3+10,inf=1e9;
int head[N],tot=1,n,m,S,T,val;
double max_flow;
struct edge{
int v,nxt;
double W,w;
}e[M<<1];
int dep[N],cur[N];
bool bfs(int S,int T){
for(int i=1;i<=n;i++){
dep[i]=0;
}
dep[S]=1;
queue<int>q;
q.push(S);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
double w=e[i].w;
if(!dep[v]&&w>eps){
dep[v]=dep[u]+1;
if(v==T){
return 1;
}
q.push(v);
}
}
}
return 0;
}
double dfs(int u,double flow,int T){
if(u==T){
return flow;
}
double s=0;
for(int i=cur[u];i;i=e[i].nxt){
cur[u]=i;
int v=e[i].v;
double w=e[i].w;
if(dep[v]==dep[u]+1&&w>eps){
double res=dfs(v,min(flow,w),T);
e[i].w-=res;
e[i^1].w+=res;
flow-=res;
s+=res;
}
if(flow<eps){
break;
}
}
if(!s){
dep[u]=0;
}
return s;
}
double dinic(int S,int T){
double flow=0;
while(bfs(S,T)){
for(int i=1;i<=n;i++){
cur[i]=head[i];
}
flow+=dfs(S,inf,T);
}
return flow;
}
void add(int u,int v,int w){
e[++tot].v=v;
e[tot].W=e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
}
void add_e(int u,int v,int w){
add(u,v,w);
add(v,u,0);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
read(n),read(m),read(val);
for(int i=1,u,v,w;i<=m;i++){
read(u),read(v),read(w);
add_e(u,v,w);
}
S=1,T=n;
max_flow=dinic(S,T);
write_endl((int)max_flow);
ll l=0,r=5e9,ans=0;
while(l<=r){
ll mid=(l+r)>>1;
for(int i=1;i<=tot;i++){
e[i].w=min(e[i].W,1.0*mid/100000.0);
}
double flow=dinic(S,T);
if(fabs(flow-max_flow)<eps){
r=mid-1;
ans=mid;
}
else{
l=mid+1;
}
}
printf("%.6lf\n",ans/100000.0*val);
return 0;
}
Special Edges
可以发现本题有一个很明显的特点,\(k\) 很小,所以我们可以考虑枚举每条关键边的割与否。现在我们的问题变成了,给定一个集合 \(S\) 表示 \(k\) 条边是否属于割集,求除了这 \(k\) 条边的外的边的最小割。
将集合大小变小 \(1\) 相当于在原图中新添一条不可被割的边,再跑一遍网络流就可以求出这条边对残量网络的影响。这条边边权可以改为 \(inf\),也可以为了方便运算,改为边权上限 \(25\)。至于两者等价的原因,因为 \(25\) 是边权上限,所以流量更大的话,肯定不如直接割掉这条边,对最终答案影响是相同的。
选出这条不可被割的边可以在状压后使用 lowbit 得到最低位的 \(1\) 的位置,该位置就是要删去的边。
因为本题出题人卡常,所以只建议第一次网络流用 dinic,后面的全部用 FF。
点击查看代码
#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e4+10,M=2e4+10,MX=(1<<10)+10,inf=1e9;
int n,m,k,q,s,t,cur[N],tot=1,head[N],all;
int que[N],l,r,dep[N];
int pre[N],pre_to[N],f[N],Lg[N],L[N],val[N],g[N];
struct edge{
int v,w,nxt;
}e[M],G[M][MX];
void add(int u,int v,int w){
e[++tot].v=v;
e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
}
void add_e(int u,int v,int w){
add(u,v,w);
add(v,u,0);
}
bool bfs(){
for(int i=1;i<=n;i++){
que[i]=dep[i]=0;
}
l=r=0;
que[++r]=s;
dep[s]=1;
while(l<r){
int u=que[++l];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(!dep[v]&&w){
dep[v]=dep[u]+1;
if(v==t){
return 1;
}
que[++r]=v;
}
}
}
return 0;
}
int dfs(int u,int flow){
if(u==t){
return flow;
}
int ans=0;
for(int i=cur[u];i;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,w=e[i].w;
if(dep[v]==dep[u]+1&&w){
int res=dfs(v,min(flow,w));
e[i].w-=res;
e[i^1].w+=res;
ans+=res;
flow-=res;
}
if(!flow){
break;
}
}
return ans;
}
int dinic(){
int ans=0;
while(bfs()){
for(int i=1;i<=n;i++){
cur[i]=head[i];
}
ans+=dfs(s,inf);
}
return ans;
}
int BFS(){
for(int i=1;i<=n;i++){
dep[i]=que[i]=0;
}
l=r=0;
dep[s]=1;
que[++r]=s;
while(l<r){
int u=que[++l];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(!dep[v]&&w){
dep[v]=dep[u]+1;
pre[v]=u;
pre_to[v]=i;
que[++r]=v;
}
if(dep[t]){
break;
}
}
if(dep[t]){
break;
}
}
if(!dep[t]){
return 0;
}
int ans=25,u;
u=t;
while(u!=s){
ans=min(ans,e[pre_to[u]].w);
u=pre[u];
}
u=t;
while(u!=s){
e[pre_to[u]].w-=ans;
e[pre_to[u]^1].w+=ans;
u=pre[u];
}
return ans;
}
int FF(){
int ans=0,tmp;
while(tmp=BFS()){
ans+=tmp;
}
return ans;
}
int lowbit(int x){
return x&(-x);
}
void solve(){
read(n),read(m),read(k),read(q);
all=(1<<k)-1;
s=1,t=n;
for(int i=1,u,v,w;i<=m;i++){
read(u),read(v),read(w);
add_e(u,v,w);
}
f[0]=dinic();
for(int i=2;i<=tot;i++){
G[i][0]=e[i];
}
Lg[1]=1;
for(int i=2;i<=all;i<<=1){
Lg[i]=Lg[i>>1]+1;
}
for(int i=1;i<=all;i++){
L[i]=lowbit(i);
for(int j=2;j<=tot;j++){
e[j]=G[j][i^L[i]];
}
e[Lg[L[i]]<<1].w=25;
f[i]=FF()+f[i^L[i]];
for(int j=2;j<=tot;j++){
G[j][i]=e[j];
}
}
while(q--){
for(int i=1;i<=k;i++){
read(val[i]);
}
int ans=inf;
for(int i=1;i<=all;i++){
g[i]=g[i^L[i]]+val[Lg[L[i]]];
}
for(int i=0;i<=all;i++){
ans=min(ans,f[i]+g[i^all]);
}
write_endl(ans);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
无限之环
看到水管的形状,我们发现和插头 dp 中插头的样子很像,容易想到插头dp。但是有一个需要注意的是本题中直线型是不能转的,但在插头 dp 中直线型和别的东西是没有区别的,这引导我们去思考其它的做法。
先放弃观察不合法情况,考虑以下合法情况是怎么回事。对于一个合法状态,满足要求则需要水管两两配对,将一个点拆成 \(5\) 个点,分别表示中间和四个方向的水管。两个水管匹配,则在两个水管之间连条边,最后是求最大匹配是否为 \(\frac{sum}{2}\),其中 \(sum\) 表示水管总数。这一般用什么?网络流!!!
于是我们顺着这个思路想下去,根据一个比较经典的 trick,将网格黑白染色,从拆出来的点中代表相邻水管的两边连一条费用为 \(0\),流量为 \(1\) 的边。如果存在一个水管,则从中间连向对应方向,连一条费用为 \(0\),流量为 \(1\) 的边。
对于旋转,分为 \(5\) 种情况,单向,拐角,直线,T形,十字。其中直线和十字不能旋转,不考虑。先只考虑白点,黑点和白点相反建边即可。单向水管旋转,水管会换一个方向,从起始方向向转到的方向连一条费用为旋转次数,流量为 \(1\) 的边。拐角旋转,水管会变到对向的位置,从两个位置各自向对向的点连边,费用为 \(1\),流量为 \(1\),在这里,两条边各走一遍,就是相当于旋转两次。T形旋转会有一个点旋转到空白点,从转来的方向向空白点方向连一条费用为旋转次数,流量为 \(1\) 的边。当最大流等于 \(\frac{sum}{2}\) 时,为有解,解为最小费用。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e4+10,M=2e5+10,inf=1e9;
int head[N],n,m,tot=1,s,t,maxflow,mincost,sum;
struct edge{
int v,flow,cost,nxt;
}e[M<<1];
void add(int u,int v,int flow,int cost){
e[++tot].v=v;
e[tot].flow=flow;
e[tot].cost=cost;
e[tot].nxt=head[u];
head[u]=tot;
}
void add_e(int u,int v,int flow,int cost){
add(u,v,flow,cost);
add(v,u,0,-cost);
}
namespace MCMF{
int d[N],flow[N],b[N],pre[N],pre_to[N];
int spfa(){
for(int i=1;i<=t;i++){
d[i]=flow[i]=inf;
b[i]=pre[i]=pre_to[i]=0;
}
queue<int>q;
q.push(s);
b[s]=1;
d[s]=0;
pre[t]=0;
while(!q.empty()){
int u=q.front();
q.pop();
b[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].flow,c=e[i].cost;
if(w>0&&d[v]>d[u]+c){
d[v]=d[u]+c;
pre[v]=u;
pre_to[v]=i;
flow[v]=min(flow[u],w);
if(!b[v]){
b[v]=1;
q.push(v);
}
}
}
}
return pre[t];
}
void solve(){
while(spfa()){
int u=t;
maxflow+=flow[t];
mincost+=flow[t]*d[t];
while(u!=s){
e[pre_to[u]].flow-=flow[t];
e[pre_to[u]^1].flow+=flow[t];
u=pre[u];
}
}
}
}
int id(int x,int y,int opt){
return (x-1)*m+y+opt*n*m;
}
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
void buildA(int x,int y,int opt,int type){
if(type){
add_e(id(x,y,opt),id(x,y,(opt+3)%4),1,1);
add_e(id(x,y,opt),id(x,y,(opt+1)%4),1,1);
add_e(id(x,y,opt),id(x,y,(opt+2)%4),1,2);
}
else{
add_e(id(x,y,(opt+3)%4),id(x,y,opt),1,1);
add_e(id(x,y,(opt+1)%4),id(x,y,opt),1,1);
add_e(id(x,y,(opt+2)%4),id(x,y,opt),1,2);
}
}
void buildB(int x,int y,int opt,int type){
return;
}
void buildC(int x,int y,int opt,int type){
if(type){
add_e(id(x,y,opt),id(x,y,(opt+2)%4),1,1);
add_e(id(x,y,(opt+1)%4),id(x,y,(opt+3)%4),1,1);
}
else{
add_e(id(x,y,(opt+2)%4),id(x,y,opt),1,1);
add_e(id(x,y,(opt+3)%4),id(x,y,(opt+1)%4),1,1);
}
}
void buildD(int x,int y,int opt,int type){
if(type){
add_e(id(x,y,(opt+3)%4),id(x,y,opt),1,1);
add_e(id(x,y,(opt+1)%4),id(x,y,opt),1,1);
add_e(id(x,y,(opt+2)%4),id(x,y,opt),1,2);
}
else{
add_e(id(x,y,opt),id(x,y,(opt+3)%4),1,1);
add_e(id(x,y,opt),id(x,y,(opt+1)%4),1,1);
add_e(id(x,y,opt),id(x,y,(opt+2)%4),1,2);
}
}
void buildE(int x,int y,int opt,int type){
return;
}
void solve(){
read(n),read(m);
s=n*m*5+1,t=n*m*5+2;
int s1=0,s2=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int opt;
read(opt);
vector<int>pos;
if((i+j)%2){
add_e(s,id(i,j,4),inf,0);
for(int k=0;k<4;k++){
if(opt>>k&1){
add_e(id(i,j,4),id(i,j,k),1,0);
pos.pb(k);
sum++;
s1++;
}
int x=i+dx[k],y=j+dy[k];
if(x<=0||y<=0||x>n||y>m){
continue;
}
add_e(id(i,j,k),id(x,y,(k+2)%4),1,0);
}
}
else{
add_e(id(i,j,4),t,inf,0);
for(int k=0;k<4;k++){
if(opt>>k&1){
add_e(id(i,j,k),id(i,j,4),1,0);
pos.pb(k);
s2++;
}
}
}
if(pos.size()==1){
buildA(i,j,pos[0],(i+j)%2);
}
else if(pos.size()==2&&abs(pos[1]-pos[0])==2){
buildB(i,j,pos[0],(i+j)%2);
}
else if(pos.size()==2){
if(opt==3){
buildC(i,j,0,(i+j)%2);
}
else if(opt==6){
buildC(i,j,1,(i+j)%2);
}
else if(opt==12){
buildC(i,j,2,(i+j)%2);
}
else{
buildC(i,j,3,(i+j)%2);
}
}
else if(pos.size()==3){
if(pos[0]!=0){
buildD(i,j,0,(i+j)%2);
}
else if(pos[1]!=1){
buildD(i,j,1,(i+j)%2);
}
else if(pos[2]!=2){
buildD(i,j,2,(i+j)%2);
}
else if(pos[3]!=3){
buildD(i,j,3,(i+j)%2);
}
}
else{
buildE(i,j,0,(i+j)%2);
}
}
}
if(s1!=s2){
puts("-1");
return;
}
MCMF::solve();
if(maxflow!=sum){
puts("-1");
}
else{
write_endl(mincost);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
Incorrect Flow
分类讨论,分为 \(f\le c\) 和 \(f>c\) 两类。
下文中所有的流量上限全部为减少或增大流量的上限。
对于 \(f\le c\) 的边,存在三种可行的操作:
- \(f\) 减小,即退流,从 \(v\) 向 \(u\) 连一条流量为 \(f\),费用为 \(1\) 的边。
- \(f\) 增大且不超过 \(c\),即增大流,从 \(u\) 向 \(v\) 连一条流量为 \(c-f\),费用为 \(1\) 的边。
- \(f\) 增大且超过 \(c\),此时要同时增大流和容量,从 \(u\) 到 \(v\) 连一条流量为 \(+\infty\),费用为 \(2\) 的边。
对于 \(f>c\) 的边,同样存在 \(3\) 种可行操作:
- \(f\) 减小且小于 \(c\),退流操作,从 \(v\) 向 \(u\) 连一条流量为 \(c\) ,费用为 \(1\) 的边,然后还要额外提供 \(f-c\) 的费用。
- \(f\) 减小且大于 \(c\),此时它不仅退了流,还增大了容量上限,显然无论如何,费用都为 \(f-c\),但这不好统计,于是我们将 \(f-c\) 提出来,再从 \(v\) 向 \(u\) 连一条流量为 \(f-c\),费用为 \(0\) 的边。
- \(f\) 增大,除了 \(f-c\) 的费用,流每增大 \(1\),费用都要增加 \(2\),因此从 \(u\) 到 \(v\) 连一条流量为 \(+\infty\) 费用为 \(2\) 的边。
现在问题来了,原图中的 \(f\) 怎么办呢,考虑将图转化为上下界网络,对于原来的每条边,连一条从 \(u\) 到 \(v\) 上下界均为 \(f\),费用为 \(0\) 的边,于是我们先将这 \(f\) 流完,假定如果一个点流入比流出多 \(d_i\),从超级源点向该点连一条流量为 \(d_i\),费用为 \(0\) 的边,反之则从该点向超级汇点连一条流量为 \(d_i\),费用为 \(0\) 的边。
最后跑一遍费用流即可。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=110,M=2e3+10,inf=1e9;
int n,m,head[N],tot=1,Flow[N],s,t,maxflow,mincost;
struct edge{
int v,flow,cost,nxt;
}e[M<<1];
void add(int u,int v,int flow,int cost){
e[++tot].v=v;
e[tot].flow=flow;
e[tot].cost=cost;
e[tot].nxt=head[u];
head[u]=tot;
}
void add_e(int u,int v,int flow,int cost){
add(u,v,flow,cost);
add(v,u,0,-cost);
}
namespace MCMF{
int d[N],flow[N],b[N],pre[N],pre_to[N];
int spfa(){
for(int i=1;i<=t;i++){
d[i]=flow[i]=inf;
b[i]=pre[i]=pre_to[i]=0;
}
queue<int>q;
q.push(s);
b[s]=1;
d[s]=0;
pre[t]=0;
while(!q.empty()){
int u=q.front();
q.pop();
b[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].flow,c=e[i].cost;
if(w>0&&d[v]>d[u]+c){
d[v]=d[u]+c;
pre[v]=u;
pre_to[v]=i;
flow[v]=min(flow[u],w);
if(!b[v]){
b[v]=1;
q.push(v);
}
}
}
}
return pre[t];
}
void solve(){
while(spfa()){
int u=t;
maxflow+=flow[t];
mincost+=flow[t]*d[t];
while(u!=s){
e[pre_to[u]].flow-=flow[t];
e[pre_to[u]^1].flow+=flow[t];
u=pre[u];
}
}
}
}
void solve(){
read(n),read(m);
s=n+1,t=n+2;
int S=0;
for(int i=1;i<=m;i++){
int u,v,flow,sum;
read(u),read(v),read(flow),read(sum);
if(flow>=sum){
add_e(v,u,sum,1);
add_e(u,v,flow-sum,1);
}
else{
S+=sum-flow;
add_e(v,u,flow,1);
add_e(v,u,sum-flow,0);
}
Flow[u]+=sum;
Flow[v]-=sum;
add_e(u,v,inf,2);
}
add_e(n,1,inf,0);
for(int i=1;i<=n;i++){
if(Flow[i]>0){
add_e(i,t,Flow[i],0);
}
else{
add_e(s,i,-Flow[i],0);
}
}
MCMF::solve();
write_endl(S+mincost);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
Showing Off
从一个点向它到达的点连边,可以发现这是一个基环树森林。
先考虑可以不在环上的点要满足什么条件,因为权值图均为正整数,所以它必然是从一个小于它的点移过来的,所以它的周围四格必然要存在一个小于它的数。换过来说就是如果一个点周围四格的数均大于等于它,那么它必然在环上。
将原图黑白染色,得到所有的环均为偶环。既然所有环均为偶环,所以我们可以将所有的环全部拆成大小为 \(2\) 的环,这是等效的。所以我们把一个环转化为了一个匹配。对于所有的黑点,连一条从 \(s\) 到它,流量为 \([x_{i,j},1]\) 的边,其中 \(x_{i,j}\) 表示 \((i,j)\) 是否一定在环上,白点同样连一条从它到 \(t\) 的边,流量为 \([x_{i,j},1]\) 的边,对于相邻的点 \((i,j),(x,y)\),如果 \(a_{i,j}=a_{x,y}\) 从黑点向白点连一条流量为 \([0,1]\) 的边,最后跑可行流,如果可行,则将匹配的两个点 \((i,j),(x,y)\) 权值变为 \(1,a_{i,j}-1\)。对于不在环上的点,权值为 \(a_{i,j}-a_{x,y}\) 且 \(a_{x,y}<a_{i,j}\)。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e5+10,inf=1e9;
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
char opt[4]={'D','R','U','L'};
int n,m,head[N],tot=1,s,t,S,T;
int a[N],b[N],c[N],vis[N];
struct edge{
int v,flow,nxt;
}e[N<<4];
int id(int x,int y){
return (x-1)*m+y;
}
void add(int u,int v,int w){
e[++tot].v=v;
e[tot].flow=w;
e[tot].nxt=head[u];
head[u]=tot;
}
void add_e(int u,int v,int w){
add(u,v,w);
add(v,u,0);
}
void Add(int u,int v,int opt,int type){
if(!opt){
add_e(u,v,type);
}
else{
add_e(S,v,1);
add_e(u,T,1);
}
}
namespace Max_Flow{
int dep[N],cur[N];
bool bfs(){
for(int i=1;i<=T;i++){
dep[i]=0;
cur[i]=head[i];
}
queue<int>q;
q.push(S);
dep[S]=1;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].flow;
if(w&&!dep[v]){
dep[v]=dep[u]+1;
q.push(v);
if(v==T){
return 1;
}
}
}
}
return 0;
}
int dfs(int u,int flow){
if(u==T){
return flow;
}
int s=0;
for(int i=cur[u];i;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,w=e[i].flow;
if(w&&dep[v]==dep[u]+1){
int res=dfs(v,min(flow,w));
e[i].flow-=res;
e[i^1].flow+=res;
flow-=res;
s+=res;
}
if(!flow){
break;
}
}
if(!s){
dep[u]=0;
}
return s;
}
int dinic(){
int ans=0;
while(bfs()){
for(int i=1;i<=T;i++){
cur[i]=head[i];
}
ans+=dfs(S,inf);
}
return ans;
}
}
void solve(){
read(n),read(m);
s=n*m+1;
t=n*m+2;
S=n*m+3;
T=n*m+4;
for(int i=1;i<=T;i++){
head[i]=0;
b[i]=c[i]=vis[i]=0;
}
tot=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
read(a[id(i,j)]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x<=0||y<=0||x>n||y>m){
continue;
}
vis[id(i,j)]|=(a[id(i,j)]>a[id(x,y)]);
}
vis[id(i,j)]^=1;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if((i+j)&1){
Add(s,id(i,j),vis[id(i,j)],1);
for(int way=0;way<4;way++){
if(i+dx[way]<1||i+dx[way]>n||j+dy[way]<1||j+dy[way]>m){
continue;
}
if(a[id(i,j)]==a[id(i+dx[way],j+dy[way])]){
add_e(id(i,j),id(i+dx[way],j+dy[way]),1);
}
}
}
else{
Add(id(i,j),t,vis[id(i,j)],1);
}
}
}
add(t,s,inf);
int sum=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
sum+=vis[id(i,j)];
}
}
if(Max_Flow::dinic()!=sum){
puts("NO");
return;
}
puts("YES");
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if((i+j)&1){
for(int k=head[id(i,j)];k;k=e[k].nxt){
int v=e[k].v;
if(v!=s&&e[k^1].flow){
for(int way=0;way<4;way++){
int x=i+dx[way],y=j+dy[way];
if(x<1||y<1||x>n||y>m){
continue;
}
if(id(x,y)==v){
b[id(x,y)]=a[id(i,j)]-1;
b[id(i,j)]=1;
c[id(x,y)]=(way+2)%4;
c[id(i,j)]=way;
}
}
}
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(!b[id(i,j)]){
for(int way=0;way<4;way++){
int x=i+dx[way],y=j+dy[way];
if(x<1||y<1||x>n||y>m){
continue;
}
if(a[id(x,y)]<a[id(i,j)]){
c[id(i,j)]=way;
b[id(i,j)]=a[id(i,j)]-a[id(x,y)];
}
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
write_space(b[id(i,j)]);
}
putchar('\n');
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
putchar(opt[c[id(i,j)]]);
putchar(' ');
}
putchar('\n');
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t;
read(t);
while(t--){
solve();
}
return 0;
}

浙公网安备 33010602011771号