描述
该做法解决了一类“广义差分约束”问题(当然名字是我自己取的),除了可以解决常见的求解 \(A_1+c_1\geq A_2,A_2+c_2\geq A_3\dots\) 问题外,还可以求解形如“如果 \(A_1\geq c_1\),那么 \(A_2\geq c_2\)”这样涉及条件逻辑运算的问题。另外,变量的取值还可以带权,即 \(A_i\) 取值 \(k\) 时的代价可以不为 \(k\) 而其他指定的数,求最小代价,当然如果代价取值就为 \(k\) 则会求出满足约束的和最小的一组解。
但这个做法也有很大的弊端,它的节点规模是约为变量数 \(\times\) 值域的,复杂度增长非常快,且它要求值域是离散的。
下文记值域为大写 \(C\),而小写 \(c\) 意为常数,请勿混淆。
基本形式
我们将一个变量的取值从小到大连成一条链,一直连到最大取值 \(+1\) 所代表的节点,即 \(1\rightarrow 2\rightarrow 3\rightarrow\cdots\rightarrow (C_{max}-1)\rightarrow C_{max}\rightarrow (C_{max}+1)\) 这样的一条链。而对于一条 \(u\) 连到 \(u+1\) 的边,边权设为该变量取 \(u\) 值时的代价,注意这个代价可以不是随着高度而递增的;同时还需要建一条 \(u+1\) 到 \(u\) 的反边,边权为 \(INF\) (下文图片省略了反边)。
问题的基本形式是“如果 \(A_1\geq c_1\),那么 \(A_2\geq c_2\)”,我们把代表 \(A_1\) 的链上表示 \(c_1\) 的节点向代表 \(A_2\) 链上的表示 \(c_2\) 的节点连一条边权为 \(INF\) 的边。
于是当我们再将所有链头与一个超级源点 \(S\),所有链尾与一个超级汇点 \(T\) 连上边权为 \(INF\) 的边后,跑最小割,我们发现:
将 \(INF\) 边视为不能被割的边,我们发现,如果 \(A_1\) 的链割了 \((u,u+1),u\geq 4\) 的边,那么 \(A_2\) 就必须割 \(u\geq 2\) 的边,否则该网络仍会流通。假设将 \((u,u+1)\) 这条边被割视为这个变量取值为 \(u\),这样就可以找到满足如果 \(A_1\geq 4\),那么 \(A_2\geq 2\) 的最小代价取值方案!
链上割边唯一性定理
可以证明,一条变量所表示的链上只有可能有一个割,否则不优。
考虑最小割中,当一个割边是最优的时候,它一定是一侧能连通至 \(S\),一侧能连通至 \(T\),否则完全没必要在此处设割。
我们假设一条链上存在不止一个割时最优,我们关注距离 \(S\) 最近的与距离 \(T\) 最近的这两个割,前者的边起点能连通 \(S\),终点能连通 \(T\),后者反之。由于反向 \(INF\) 边的存在,这种情况下 \(S\) 就能到达 \(T\),因此不可能存在多于一个割。
推广
-
\(A_2\geq A_1+c\)
我们可以将 \(A_1\geq A_2+c\) 的条件完全等价转化为 \(\forall x\in C\),如果 \(A_1\geq x\),那么 \(A_2\geq x+c\)。因此我们对于 \(x\) 的每一种取值,都仿照基本形式建立一条横跨两个变量的链之间的边即可。
如 \(A_2\geq A_1+2,C=\{1,2,3,4\}\) 的情况:
注意到最后一条连的边是到 \(C_{max}+1\) 的,这么做是为了处理边界情况。
-
\(A_1>c\)
如果我们需要某个变量恒大于某个常数,可以从 \(S\) 连一条 \(INF\) 边到该变量链对应值处,这样保证了割无论如何都不会小于该值。
如 \(A_1\geq 3\) 的情况:
-
如果 \(A_1\geq c_1\),那么 \(A_2< c_2\)
此时可以将 \(A_2\) 链上的值逆序,但链本身的指向不变,此时如果 \((u,u+1)\) 这条边被割表示该变量取值为 \(u+1\)。由于被翻转,因此这种做法是无法处理“如果 \(A_1\geq c_1\),那么 \(A_2\geq c_2\)”和“如果 \(A_1\geq c_1\),那么 \(A_2< c_2\)”同时存在的情况的。
如 \(A_1\geq 4\) 那么 \(A_2< 3\) 的情况:
例题
[HNOI2013] 切糕
题意
题目给定一个 \(P\times Q\) 矩形的每个点取每个值的代价,让我们找出满足某个点的值和其上下左右点之差均小于等于 \(D\) 的情况下的最小代价。
转化
将“两点 \(A_1\) 与 \(A_2\) 之差小于等于 \(D\)”的条件转化为 \(A_1-D\leq A_2\leq A_1+D\),再转化为 \(A_2\geq A_1-D\) 与 \(A_1\geq A_2-D\),即是说对于任意相邻两点,它们之间的链是这样互相交叉连边的:
另外,本题不需要连上文所述的反向边以保证每条链只有一个割,因为边都是由大的值连向小的值且所有的点均有连边,一条链上多个割一定是不优的。[1]
代码
#include<bits/stdc++.h>
#define getid(x,y,z) (((x)-1)*q*(r+1)+((y)-1)*(r+1)+(z))
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar __getchar
inline char __getchar(){
static char ch[1<<20],*l,*r;
return (l==r&&(r=(l=ch)+fread(ch,1,1<<20,stdin),l==r))?EOF:*l++;
}
#endif
template<class T>inline void rd(T &x){
T res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
while('0'<=ch && ch<='9'){res=res*10+ch-'0';ch=getchar();}
x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
static char wtbuff[20];
static int wtptr;
if(x==0){
putchar('0');
}
else{
if(x<0){x=-x;putchar('-');}
wtptr=0;
while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
while(wtptr--) putchar(wtbuff[wtptr]);
}
if(endch!='\0') putchar(endch);
}
const int MAXN=40,MAXV=MAXN*MAXN*(MAXN+1)+5,MAXE=MAXV*2+MAXN*MAXN*2+5,INF=0x3f3f3f3f,dx[]={-1,1,0,0},dy[]={0,0,-1,1};
int p,q,r,d,S,T;
int head[MAXV],ecnt=1;
struct EDGE{
int v,w,nxt;
}e[MAXE<<1];
inline void add(const int& u,const int& v,const int& w){
e[++ecnt].v=v;
e[ecnt].w=w;
e[ecnt].nxt=head[u];
head[u]=ecnt;
}
inline void add_dual(const int& u,const int& v,const int& w){
add(u,v,w);add(v,u,0);
}
int cur[MAXV],dep[MAXV];
inline bool bfs(){
memset(dep,-1,sizeof(dep));
queue<int>q;
q.push(S);
dep[S]=0;cur[S]=head[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;
if(e[i].w>0 && dep[v]==-1){
q.push(v);
cur[v]=head[v];
dep[v]=dep[u]+1;
if(v==T) return true;
}
}
}
return false;
}
int dfs(int u,int flow){
if(u==T) return flow;
int tmp,res=0;
for(int i=cur[u];i && flow>0;i=e[i].nxt){
cur[u]=i;
int v=e[i].v;
if(e[i].w>0 && dep[v]==dep[u]+1){
tmp=dfs(v,min(flow,e[i].w));
if(tmp==0) dep[v]=-1;
e[i].w-=tmp;
e[i^1].w+=tmp;
res+=tmp;
flow-=tmp;
}
}
return res;
}
inline int dinic(){
int res=0;
while(bfs()) res+=dfs(S,INF);
return res;
}
int main(){
rd(p);rd(q);rd(r);rd(d);
S=0;T=getid(p,q,r+1)+1;
for(int k=1,val;k<=r;k++)
for(int i=1;i<=p;i++)
for(int j=1;j<=q;j++){
rd(val);
add_dual(getid(i,j,k),getid(i,j,k+1),val);
}
for(int i=1;i<=p;i++)
for(int j=1;j<=q;j++){
add_dual(S,getid(i,j,1),INF);
add_dual(getid(i,j,r+1),T,INF);
}
for(int k=d+1;k<=r;k++)
for(int i=1;i<=p;i++)
for(int j=1;j<=q;j++)
for(int dir=0;dir<4;dir++){
if(1<=i+dx[dir] && i+dx[dir]<=p && 1<=j+dy[dir] && j+dy[dir]<=q)
add_dual(getid(i,j,k),getid(i+dx[dir],j+dy[dir],k-d),INF);
}
wt(dinic(),'\n');
return 0;
}
[ARC176E] Max Vector
题意
给定两个长为 \(N\) 的序列 \(X,Y\),还有 \(M\) 个长为 \(N\) 的序列 \(A_i\)。操作 \(M\) 次,每次要么让 \(X\) 和 \(A_i\) 分别每位取 \(\max\),要么让 \(Y\) 和 \(A_i\) 分别每位取 \(\max\),求 \(X,Y\) 最后所有元素之和最小值。
转化
正着去选择每个操作和谁取 \(\max\) 很难,可以反过来,找到一个最小的合法的 \(X,Y\),满足 \(X\) 大于等于某些 \(A_i\),而 \(Y\) 大于等于剩下的 \(A_i\),此处的大于等于指的是对于两个等长序列,其中一个序列的元素每位都大于另一个序列的对应元素。
而合法的 \(X,Y\) 的判定标准为,对于每个 \(i\in[1,M]\),如果 \(\exists j\) 使得 \(Y_j<A_{ij}\),那么必须 \(\forall j\) 使得 \(X_j\geq A_{ij}\)。发现了吗,否则那么说明 \(\forall j\) 满足 \(Y_j\geq A_{ij}\),也满足条件。发现了吗?这就类似我们之前推广的“如果 \(A_1\geq c_1\),那么 \(A_2< c_2\)”的情况,于是我们对于 \(Y_j\) 得逆序取值。
但是我们怎么处理任意 \(Y_j\) 不大于等于就得全体 \(X_j\) 大于等于呢?我们增设一个虚点,将 \(Y_j\) 对应的 \(A_{ij}\) 全部连到这个虚点上,再由这个虚点连到全部 \(X_j\) 对应的 \(A_{ij}\) 上就可以了,如下图:
另外,由于本题的割代价是单调的(割的代价为割所代表取值本身),可以证明一条链上多于一个割是不优的,不需要连上文所述的反向边以保证每条链只有一个割。
代码
#include<bits/stdc++.h>
#define getid(x,y) (((x)-1)*501+(y))
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar __getchar
inline char __getchar(){
static char ch[1<<20],*l,*r;
return (l==r&&(r=(l=ch)+fread(ch,1,1<<20,stdin),l==r))?EOF:*l++;
}
#endif
template<class T>inline void rd(T &x){
T res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
while('0'<=ch && ch<='9'){res=res*10+ch-'0';ch=getchar();}
x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
static char wtbuff[20];
static int wtptr;
if(x==0){
putchar('0');
}
else{
if(x<0){x=-x;putchar('-');}
wtptr=0;
while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
while(wtptr--) putchar(wtbuff[wtptr]);
}
if(endch!='\0') putchar(endch);
}
const int MAXN=10,MAXM=500,MAXV=MAXN*2*(MAXM+1)+MAXM+100,MAXE=MAXN*2*(MAXM+2)+MAXM*MAXN*2+100,INF=0x3f3f3f3f;
int n,m,S,T,X[MAXN+2],Y[MAXN+2];
int head[MAXV],ecnt=1;
struct EDGE{
int v,w,nxt;
}e[MAXE<<1];
inline void add(const int& u,const int& v,const int& w){
e[++ecnt].v=v;
e[ecnt].w=w;
e[ecnt].nxt=head[u];
head[u]=ecnt;
}
inline void add_dual(const int& u,const int& v,const int& w){
add(u,v,w);add(v,u,0);
}
int cur[MAXV],dep[MAXV];
inline bool bfs(){
memset(dep,-1,sizeof(dep));
queue<int>q;
q.push(S);
dep[S]=0;cur[S]=head[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;
if(e[i].w>0 && dep[v]==-1){
q.push(v);
cur[v]=head[v];
dep[v]=dep[u]+1;
if(v==T) return true;
}
}
}
return false;
}
int dfs(int u,int flow){
if(u==T) return flow;
int tmp,res=0;
for(int i=cur[u];i && flow>0;i=e[i].nxt){
cur[u]=i;
int v=e[i].v;
if(e[i].w>0 && dep[v]==dep[u]+1){
tmp=dfs(v,min(flow,e[i].w));
if(tmp==0) dep[v]=-1;
e[i].w-=tmp;
e[i^1].w+=tmp;
res+=tmp;
flow-=tmp;
}
}
return res;
}
inline int dinic(){
int res=0;
while(bfs()) res+=dfs(S,INF);
return res;
}
int main(){
rd(n);rd(m);
S=0;T=n*2*501+m+1;
for(int i=1;i<=n;i++) rd(X[i]);
for(int i=1;i<=n;i++) rd(Y[i]);
for(int i=1;i<=n;i++){
add_dual(S,getid(i,X[i]),INF);
for(int j=X[i];j<=500;j++) add_dual(getid(i,j),getid(i,j+1),j);
add_dual(getid(i,501),T,INF);
}
for(int i=1;i<=n;i++){
add_dual(S,getid(i+n,501),INF);
for(int j=500;j>=Y[i];j--) add_dual(getid(i+n,j+1),getid(i+n,j),j);
add_dual(getid(i+n,Y[i]),T,INF);
}
for(int k=n*2*501+1;k<=n*2*501+m;k++){
for(int i=1,x;i<=n;i++){
rd(x);
add_dual(getid(i+n,x),k,INF);
add_dual(k,getid(i,x),INF);
}
}
wt(dinic(),'\n');
return 0;
}
严格证明见https://www.luogu.com.cn/article/azy0pjrl的“本题的特殊性”部分。 ↩︎