Matrix 矩阵 NKOJ P6277、51Nod 2853、HYSBZ 4500、黑暗爆炸 4500 题解 令人耳目一新的差分约束做法
题目描述
这里以校内 OJ 上的题面为准,对 Markdown 不合规之处进行了调整。
何老板给你一个 \(n \times m\) 的数字矩阵,开始时矩阵中每个格子里的数字都为 \(0\),何老板可以对矩阵进行如下两种操作:
- 任选一行,让该行所有格子里的数字都增加 \(1\) 或减少 \(1\)。
- 任选一列,让该列所有格子里的数字都增加 \(1\) 或减少 \(1\)。
何老板给你有 \(K\) 个限制条件,每个限制条件形为 \(x,y,z\) 三个整数,表示要让格子 \((x,y)\) 里的数字为 \(z\)。
何老板想知道经过若干次上述操作后,能否使得该矩阵满足所有 \(K\) 个限制条件?如果能输出 Yes
,否则输出 No
。
多测,\(T <= 5,1 \le n,m,K \le 1000\)。
\(|z| \le 10^4\)。
思路 差分约束
似乎跟网上大部分题解的做法不一样。至少网上的大部分题解太简短,一句话了事,看不懂。
考虑这样一件事:对于处在同一行的两个格子 \((x_1,y),(x_2,y)\),\(x_1,x_2\) 两列的操作次数之差(规定加法为加一次操作次数,减法为减一次操作次数,所以如果操作次数为负,表示执行绝对值次减法操作)显然为 \(z_1-z_2\)。
因为第 \(y\) 行的操作次数是固定的,所以让 \(z_1 \not = z_2\) 的唯一办法就是让这两列的操作次数不同。(这个应该不难想吧?)
所以令 \(d_{x_i}\) 表示对第 \(x_i\) 列进行 \(d_{x_i}\) 次操作,那么通过上面的分析,我们就得出了一组等量关系:\(d_{x_1}-d_{x_2} = z_1-z_2\)。把这个式子转化为差分约束的不等式:
第一式可以直接建边,从 \(x_1\) 向 \(x_2\) 连一条权值为 \(z_1-z_2\) 的边。第二式化简得:
这表示从 \(x_2\) 向 \(x_1\) 连一条权值为 \(-(z_1-z_2)\) 的边。这样等量关系就表达完成。
同一列的格子同理。\(O(k^2)\) 枚举每一对关系,暴力建边。
注意图不联通,但是我们一对关系建的相当于是双向边,所以每次从没有跑过差分约束的点开始跑一遍 SPFA 即可。
无解即出现负环。
时间复杂度:\(O(k^2+(n+m)k^2)\),不过由于 SPFA 本身在一般图上跑的不错,所以 \(O((n+m)k^2)\) 的部分跑不满。
AC 代码
#include<bits/stdc++.h>
using namespace std;
int t;
int n,m,q;
vector<pair<int,int>> hang[1050],lie[1050];
int etot = 0,Last[2050],Next[2000050],End[2000050],Len[2000050];
void addedge(int u,int v,int c){
etot++;
Next[etot] = Last[u];
Last[u] = etot;
End[etot] = v;
Len[etot] = c;
return;
}
int dis[2050],tot[2050];
bitset<2050> inque;
queue<int> que;
bool spfa(int Start){
inque.reset();
while(que.size()){
que.pop();
}
dis[Start] = 0;
que.push(Start);
inque[Start] = 1;
int now;
while(que.size()){
now = que.front();
que.pop();
inque[now] = 0;
tot[now]++;
if(tot[now] > n+m){
return 1;
}
for(int i = Last[now]; i; i = Next[i]){
if(dis[End[i]] > dis[now]+Len[i]){
dis[End[i]] = dis[now]+Len[i];
if(inque[End[i]]){
continue;
}
inque[End[i]] = 1;
que.push(End[i]);
}
}
}
return 0;
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d%d",&n,&m,&q);
for(int i = 0; i <= n+10; i++){
hang[i].clear();
}
for(int i = 0; i <= m+10; i++){
lie[i].clear();
}
memset(Last,0,sizeof(int)*(n+m+10));
memset(Next,0,sizeof(int)*(etot+10));
memset(End,0,sizeof(int)*(etot+10));
memset(Len,0,sizeof(int)*(etot+10));
etot = 0;
while(q--){
static int tpa,tpb,tpc;
scanf("%d%d%d",&tpa,&tpb,&tpc);
hang[tpa].push_back({tpb,tpc});
lie[tpb].push_back({tpa,tpc});
}
for(int i = 1; i <= n; i++){
static int tp;
sort(hang[i].begin(),hang[i].end());
for(int j = 0; j < (int)hang[i].size(); j++){
for(int k = j+1; k < (int)hang[i].size(); k++){
tp = hang[i][j].second-hang[i][k].second;
addedge(hang[i][j].first+n,hang[i][k].first+n,-tp);
addedge(hang[i][k].first+n,hang[i][j].first+n,tp);
}
}
}
for(int i = 1; i <= m; i++){
static int tp;
sort(lie[i].begin(),lie[i].end());
for(int j = 0; j < (int)lie[i].size(); j++){
for(int k = j+1; k < (int)lie[i].size(); k++){
tp = lie[i][j].second-lie[i][k].second;
addedge(lie[i][j].first,lie[i][k].first,-tp);
addedge(lie[i][k].first,lie[i][j].first,tp);
}
}
}
memset(dis,0x3f,sizeof(int)*(n+m+10));
memset(tot,0,sizeof(int)*(n+m+10));
for(int i = 1; i <= n+m; i++){
if(dis[i] == dis[0]){
if(spfa(i)){
puts("No");
goto gt;
}
}
}
puts("Yes");
gt:;
}
return 0;
}