离散变量取值问题

有若干个变量,每个变量取值为 v1,v2,,vnv_1,v_2,\ldots,v_n,现在要求所有变量的和最小(大),且不同变量的取值有某种限制。(详细见下文例题)

先不考虑限制,考虑用最小割刻画这个问题。

如图所示,对每一个变量都建立 n+1n+1 个点 X1,X2,,Xn+1X_1, X_2, \ldots, X_{n+1}

  • SSX1X_1 建立一条边权为 \infty 的边;
  • Xn+1X_{n+1}TT 建立一条边权为 \infty 的边;
  • XiX_{i}Xi+1X_{i+1} 建立一条边权为 viv_{i} 的边;
  • Xi+1X_{i+1}XiX_{i} 建立一条边权为 \infty 的边。

这样我们保证了,每个变量所构造出的一条链上,在最小割中有且仅有一条边被割掉,被割掉的边的边权就代表了我们为这个变量的取值。

证明

  • 存在性:
    如果没有边被割,那么显然存在 STS\to T 的路径,矛盾。
    如果不加反向边存在一个割一条边的割,加上反向边依然存在这个割。

  • 唯一性:
    假设存在两条边被割,即 XiXi+1X_{i}\to X_{i+1}XjXj+1 (i<j)X_{j}\to X_{j+1}\ (i<j)
    我们知道,对于一条最小割中的割边 (u,v)(u,v),在被割完的图中一定存在 Su,vTS\to u, v\to T 的路径(否则这条边不割也行)。
    那么我们可以得出存在路径 SXj+1,Xi+1TS\to X_{j+1}, X_{i+1}\to T。因为无穷的边不会被割掉,所以还存在路径 Xj+1Xi+1X_{j+1}\to X_{i+1},所以存在 STS\to T 的路径,矛盾!

这样我们就证明了有且仅有一条边被割掉。

知道了这样的基本模型,我们来看一道例题。

[RC-02] 开门大吉

洛谷P6054 [RC-02] 开门大吉

我们转化到刚刚的模型上,这题的变量就是 nn 个选手每个人做的题目编号。

  • nn 个变量,每个变量 XXmm 个取值,设变量 XX 选取的取值编号为 f[X][1,m]f[X]\in [1,m]
  • yy 条限制,形如对于两个变量 X,YX,Y,有 f[X]f[Y]+kf[X]\ge f[Y]+k。(kk 不一定是正数)
  • 所有变量和最小。

我们考虑如何处理这个限制,事实上,如果要让 f[X]f[Y]+kf[X]\ge f[Y]+k,只需要对于所有 i=1,2,,m+1i=1,2,\ldots,m+1,从 YiY_iXi+kX_{i+k} 连边权为 \infty 的边即可。(如果 i+k<0i+k<0 或者 i+k>m+1i+k>m+1 就不连)。

为什么
还是从最小割的角度出发考虑:

如果变量 YY 选择了第 pp 个,那么我们知道 Y1YpY_1\sim Y_{p} 被划分给了 SSYp+1Ym+1Y_{p+1}\sim Y_{m+1} 被划分给了 TT

记得我们从 YiY_i 连出的边权为 \infty 的边吗?
我们知道,如果一个被划分给 SS 的点连出了一条边权为 \infty 的边,那么这条边的终点一定也划分给 SS(否则就割掉了一条边权为无穷的边)。

因此,X1Xp+kX_{1}\sim X_{p+k} 一定划分给了 SS,换句话说 f[X]p+kf[X]\ge p+k(想一想,为什么?),我们就完成了这个限制。(如果 p+k<1p+k<1,那么 XX 任取,我们也没有限制到任何 XX 的取值,也成立)


来看一个例子:若 m=3,k=1m=3,k=-1,最后的图就是这样的。

f[Y]=3f[Y]=3,那么我们知道 f[X]2f[X]\ge 2,反应到割上就是 X1,X2X_1,X_2 被强制划分给了 SS 集合。

因此 f[X]f[X] 只能等于 2233

参考代码(注意dinic里面如果流量大于 INF 就直接返回,要不然会超时):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include<bits/stdc++.h>
using namespace std;

using ll=long long;using ull=unsigned long long;using pii=pair<int,int>;
#define all(x) (x).begin(),(x).end()
#define rall(x) (x).rbegin(),(x).rend()
#define unq(x) (x).erase(unique(all((x))),(x).end())
#define YES cout << "YES\n"
#define NO cout << "NO\n"
#define Yes cout << "Yes\n"
#define No cout << "No\n"
#define mem0(x) memset((x),0,sizeof(x))

const double INF = 1e15;
template<typename Cap> struct Dinic{
struct Edge{int u,v;Cap c,f;};
int n;
vector<Edge> edges;
vector<vector<int>> G;
vector<int> dis,cur;
explicit Dinic(int _n):n(_n+1),G(_n+1),dis(_n+1),cur(_n+1){}

int s,t;
void add_edge(int u,int v,Cap w){
edges.push_back(Edge{u,v,w,0}),edges.push_back(Edge{v,u,0,0});
int m = edges.size();
G[u].push_back(m-2),G[v].push_back(m-1);
}

bool bfs(){
vector<bool> vis(dis.size());
dis[s]=0;vis[s]=1;
queue<int> q;
q.push(s);
while(q.size()){
int u=q.front();q.pop();
for(auto o:G[u]){
const auto& e = edges[o];
if(!vis[e.v] && (e.c-e.f)>0){
vis[e.v]=1;
dis[e.v]=dis[u]+1;
q.push(e.v);
}
}
}
return vis[t];
}

Cap dfs(int u,Cap maxflow){
if(u==t || maxflow<1e-10)return maxflow;
Cap flow = 0;
for(int& i=cur[u];i<G[u].size();i++){
auto& e=edges[G[u][i]];
if(dis[e.v] != dis[e.u]+1)continue;
Cap f = dfs(e.v,min(maxflow,e.c-e.f));
flow+=f;
maxflow-=f;
e.f+=f;
edges[G[u][i]^1].f-=f;
if(maxflow<1e-10)return flow;
}
return flow;
}
Cap flow(int _s,int _t){
s=_s,t=_t;
Cap flow = 0;
while(bfs() && flow < INF){
fill(cur.begin(), cur.end(), 0);
flow += dfs(s,numeric_limits<Cap>::max());
}
return flow;
}
};

void solve(){
int n,m,p,y;
cin>>n>>m>>p>>y;

auto id = [&](int i,int j){
return (i-1)*(m+1)+j+1;
};

int s = 0, t = (m+1)*n+1;
Dinic<double> g((m+1)*n+1);
for(int i=1;i<=n;i++){
g.add_edge(s,id(i,0),INF);
g.add_edge(id(i,m),t,INF);
}

vector<int> c(n+1);
for(int i=1;i<=p;i++)cin>>c[i];

for(int j=1;j<=m;j++){
for(int i=1;i<=n;i++){
double v = 0;
double prod_p = 1;
for(int k=1;k<=p;k++){
double prob; cin>>prob;
prod_p *= prob;
v += c[k] * prod_p;
}
g.add_edge(id(i,j-1),id(i,j),v);
g.add_edge(id(i,j),id(i,j-1),INF);
}
}


while(y--){
int i,j,k;cin>>i>>j>>k;
for(int x=0;x<=m;x++){
if(x+k<0 || x+k>m)continue;
g.add_edge(id(j,x), id(i,x+k), INF);
}
}


auto res = g.flow(s,t);
if(res>=INF)cout<<-1<<'\n';
else cout<<res<<'\n';
}

int main(){
cout<<fixed<<setprecision(4);

ios::sync_with_stdio(0);cin.tie(0);

int T;cin>>T;
while(T--)solve();

return 0;
}

[HNOI2013] 切糕

洛谷P3227 [HNOI2013] 切糕
是上题的弱化版,一样处理即可。

Max Vector

[ARC176E] Max Vector

P=maxXi,Yi,Ai,j=500P = \max{X_i,Y_i,A_{i,j}} = 500
考虑 X1XNX_1\sim X_NY1YNY_1\sim Y_N2N2N 个变量,他们取值范围都是 1P1\sim P

因此我们根据套路,先建立 2N(P+1)2N(P+1) 个点,具体的说,对于 XiX_i 建立 Ui,1Ui,P+1U_{i,1}\sim U_{i,P+1},对于 YiY_i 建立 Vi,1Vi,P+1V_{i,1}\sim V_{i,P+1}

但是,我们对 VV 要反向建边,也就是 SVi,P+1Vi,PVi,1TS\to V_{i,P+1} \to V_{i,P} \to \ldots \to V_{i,1} \to T。(这是为了我们表现出这个限制服务的,详见下文)

然后考虑这题给我们的所有限制:

  • XiXi,初始X_i \ge X_{i,\text{初始}}YiYi,初始Y_i \ge Y_{i,\text{初始}}
  • i=1,2,,M, (j=1NXjAi,j)(j=1NYjAi,j)\forall i = 1,2,\ldots,M,\ \left(\bigwedge_{j=1}^N X_j\ge A_{i,j}\right)\vee\left(\bigwedge_{j=1}^N Y_j\ge A_{i,j}\right)

对于第一个限制,我们只需要建立边权都为无穷的 SUi,Xi,初始S\to U_{i,X_{i,\text{初始}}}Vi,Yi,初始TV_{i,Y_{i,\text{初始}}} \to T 即可。

这保证了 Ui,Xi,初始U_{i,X_{i,\text{初始}}} 划分给 SS,也就是 XiXi,初始X_i \ge X_{i,\text{初始}}。(想一想,为什么?)
同理,另一条边保证了 Vi,Yi,初始V_{i,Y_{i,\text{初始}}} 划分给 TT,也就是 YiYi,初始Y_i \ge Y_{i,\text{初始}}(理由同上)

对于第二个限制,我们先转化为如果 Xj<Ai,j,k,YjAi,k\exist X_j < A_{i,j},\forall k, Y_j\ge A_{i,k},不难发现这个是一个充要条件。

那么我们就从 Vj,Ai,jV_{j,A_{i,j}}Uk,Ai,kU_{k,A_{i,k}} 建立边权为 \infty 的边。

此时如果 Xj<Ai,jX_j < A_{i,j},那么 Uj,Ai,jU_{j,A_{i,j}} 就会被划分给 TT,也就保证了所有 Vk,Ai,kV_{k,A_{i,k}} 被划分给 TT,也就是 k,YjAi,k\forall k, Y_j\ge A_{i,k}

问题就解决了。

回到为什么要反向建边。
我们深入思考一下无穷边 (u,v)(u,v) 能带来的限制:

  • 如果 uu 被划分给 SS,那么 vv 被划分给 SS
  • 如果 vv 被划分给 TT,那么 uu 被划分给 TT

而在本题中,我们需要的是如果 Xj<Ai,j    YkAi,kX_j < A_{i,j} \implies Y_{k}\ge A_{i,k},此时不等号方向是反的,而无穷边只能强制让两边属于同一个集合,因此我们要挑选一个变量反向建边,这样属于同一个集合所对应的不等号就是相反的,也就解决了本题。

时间复杂度:
最大流量为 O(NP)O(NP) ,建了 O(N2M)O(N^2M) 条边,复杂度就是 O(N3P2M)O(N^3P^2M),足够通过本题。

(官方题解给出了一个 O(N2P2M)O(N^2P^2M) 的题解,可以自行阅读)

参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include<bits/stdc++.h>
using namespace std;

using ll=long long;using ull=unsigned long long;using pii=pair<int,int>;
#define all(x) (x).begin(),(x).end()
#define rall(x) (x).rbegin(),(x).rend()
#define unq(x) (x).erase(unique(all((x))),(x).end())
#define YES cout << "YES\n"
#define NO cout << "NO\n"
#define Yes cout << "Yes\n"
#define No cout << "No\n"
#define mem0(x) memset((x),0,sizeof(x))


const int INF = 1e9;
template<typename Cap> struct Dinic{
struct Edge{int u,v;Cap c,f;};
int n;
vector<Edge> edges;
vector<vector<int>> G;
vector<int> dis,cur;
explicit Dinic(int _n):n(_n+1),G(_n+1),dis(_n+1),cur(_n+1){}

int s,t;
void add_edge(int u,int v,Cap w){
edges.push_back(Edge{u,v,w,0}),edges.push_back(Edge{v,u,0,0});
int m = edges.size();
G[u].push_back(m-2),G[v].push_back(m-1);
}

bool bfs(){
vector<bool> vis(dis.size());
dis[s]=0;vis[s]=1;
queue<int> q;
q.push(s);
while(q.size()){
int u=q.front();q.pop();
for(auto o:G[u]){
const auto& e = edges[o];
if(!vis[e.v] && e.c>e.f){
vis[e.v]=1;
dis[e.v]=dis[u]+1;
q.push(e.v);
}
}
}
return vis[t];
}

Cap dfs(int u,Cap maxflow){
if(u==t || !maxflow)return maxflow;
Cap flow = 0;
for(int& i=cur[u];i<G[u].size();i++){
auto& e=edges[G[u][i]];
if(dis[e.v] != dis[e.u]+1)continue;
Cap f = dfs(e.v,min(maxflow,e.c-e.f));
flow+=f;
maxflow-=f;
e.f+=f;
edges[G[u][i]^1].f-=f;
if(!maxflow)return flow;
}
return flow;
}
Cap flow(int _s,int _t){
s=_s,t=_t;
Cap flow = 0;
while(bfs() && flow<INF){
fill(cur.begin(), cur.end(), 0);
flow += dfs(s,numeric_limits<Cap>::max());
}
return flow;
}
};

const int P = 500;

int main(){
ios::sync_with_stdio(0);cin.tie(0);

int n,m;cin>>n>>m;
Dinic<int> g(n*2*(P+1)+2);

auto idX = [&](int i,int j){return (i-1)*(P+1)+j;};
auto idY = [&](int i,int j){return (i-1+n)*(P+1)+j;};
int S=0,T=idY(n,P+1)+1;

for(int i=1;i<=n;i++){
int xi;cin>>xi;
g.add_edge(S,idX(i,1),INF);g.add_edge(idX(i,P+1),T,INF);
for(int j=1;j<=P;j++){
g.add_edge(idX(i,j),idX(i,j+1),j);
g.add_edge(idX(i,j+1),idX(i,j),INF);
}
g.add_edge(S, idX(i,xi), INF);
}

for(int i=1;i<=n;i++){
int yi;cin>>yi;
g.add_edge(S,idY(i,P+1),INF);g.add_edge(idY(i,1),T,INF);
for(int j=1;j<=P;j++){
g.add_edge(idY(i,j+1),idY(i,j),j);
g.add_edge(idY(i,j),idY(i,j+1),INF);
}
g.add_edge(idY(i,yi), T, INF);
}

for(int i=1;i<=m;i++){
vector<int> a(n+1);
for(int j=1;j<=n;j++)cin>>a[j];
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
g.add_edge(idY(k,a[k]),idX(j,a[j]),INF);
}
}
}

cout << g.flow(S,T) << '\n';
return 0;
}