和差方程最少标定
拼多多 8月23号笔试 真题 第四题
拼多多真题目录点击查看: 拼多多 春招&秋招 笔试真题题库目录|笔试题库 + 算法考点详解
题目内容
计量室要对 p p p 个样品的整数量值 x 1 , x 2 , ... , x p x_1,x_2,\ldots,x_p x1,x2,...,xp 做交叉核对。仪器打出 e e e 条记录,每条只可能是下面两种之一:
Du u u v v v d d d:表示差关系 x u − x v = d x_u-x_v=d xu−xv=dSu u u v v v s s s:表示和关系 x u + x v = s x_u+x_v=s xu+xv=s
量值必须是整数,可正可负可为零。请先判断这 e e e 条记录有没有一组整数解;若有,再问至少要实测多少个样品的量值,才能把全部 p p p 个量值都唯一定下来。一条记录里的两个下标可以相同。
输入描述
第一行两个整数 p p p 和 e e e( 1 ≤ p ≤ 100000 1\le p\le 100000 1≤p≤100000, 0 ≤ e ≤ 100000 0\le e\le 100000 0≤e≤100000),表示样品数与记录数。
接下来 e e e 行,每行一个大写字母 D 或 S,随后三个整数 u , v , w u,v,w u,v,w( 1 ≤ u , v ≤ p 1\le u,v\le p 1≤u,v≤p, ∣ w ∣ ≤ 1000000000 |w|\le 1000000000 ∣w∣≤1000000000)。字母为 D 时表示 x u − x v = w x_u-x_v=w xu−xv=w,为 S 时表示 x u + x v = w x_u+x_v=w xu+xv=w。
输出描述
第一行输出 YES 或 NO,表示是否存在整数解。
第二行:若第一行是 YES,输出最少实测个数(可以为 0,表示记录本身已经唯一确定全部量值);若第一行是 NO,输出 -1。
样例1
输入
2 2
S 1 2 8
D 1 2 2
输出
YES
0
说明
x 1 + x 2 = 8 x_1+x_2=8 x1+x2=8 且 x 1 − x 2 = 2 x_1-x_2=2 x1−x2=2,解得 x 1 = 5 x_1=5 x1=5, x 2 = 3 x_2=3 x2=3,已经唯一,不必再测。
样例2
输入
1 1
S 1 1 5
输出
NO
-1
说明
2 x 1 = 5 2x_1=5 2x1=5,不是整数,无解。
样例3
输入
3 2
D 1 2 1
D 2 3 1
输出
YES
1
说明
只有差关系,三个量由一个自由变量决定,实测任意一个即可推出其余。
数据范围
- 1 ≤ p ≤ 100000 1\le p\le 100000 1≤p≤100000
- 0 ≤ e ≤ 100000 0\le e\le 100000 0≤e≤100000
- 1 ≤ u , v ≤ p 1\le u,v\le p 1≤u,v≤p
- ∣ w ∣ ≤ 1000000000 |w|\le 1000000000 ∣w∣≤1000000000
- 线索类型仅为
D或S - 所有编号与关系值均为整数
题解
思路
解题思路: 并查集
- 并查集内不关需要维护属于哪个集合,元素还需要维护
x_u = sign_u * x_r + offset_u和连通块根的关系。x_r为连通块根。 - 正常情况下需要实测个数 == 连通块数量,但存在以下两种例如情况:
- 无解情况,发生在同一连通块中,前后关系矛盾例如
- 存在
x_u=s_u * x_r+d_u和x_v=s_v * x_r +d_v - 此时引入新的约束
x_u = k * x_v + w - 带入得到新的关系
(s_u− k* s_v)x_r=kd_v+w−d_u s_u ∈ {-1,1},s_v ∈ {-1,1},k ∈ {-1,1}- 左边系数只会存在两种情况
- 系数为0,那么
kd_v+w−d_u == 0不成立则说明矛盾 - 系数为±2, 需要满足
(k * d_v + w - d_u) % 2 == 0,不成立则矛盾
- 系数为0,那么
- 存在
- 连通块内只要有一个值被确定,则不需要进行测量。其实也就是上述无解情境下,引入新的约束之后,不发生冲突,那么就能确认一个值。
x1 = x3 + 2, x2 = x3 - 2, 如果此时引入x1 = kx2 + w不发生冲突下,带入就能确认连通块中任意一个值。
- 无解情况,发生在同一连通块中,前后关系矛盾例如
- 在有解情况下,题目的答案就是不能确认值的连通块数量。上述就是这个题目的总体思路,具体实现逻辑可参照下面代码。
- 代码时间复杂度为
O(p+e)
C++
cpp
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
struct DSU {
vector<int> parent, sign;
vector<ll> offset;
vector<bool> fixed;
DSU(int n) {
parent.resize(n + 1);
sign.resize(n + 1, 1);
offset.resize(n + 1, 0);
fixed.resize(n + 1, false);
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
}
// x_u = sign[u] * x_parent[u] + offset[u]
int find(int u) {
if (parent[u] == u) {
return u;
}
int p = parent[u];
int r = find(p);
// x_p = sign[p] * x_r + offset[p]
// x_u = sign[u] * x_p + offset[u]
offset[u] += (ll)sign[u] * offset[p];
sign[u] *= sign[p];
parent[u] = r;
return r;
}
// 添加关系:x_u = k * x_v + w
bool unite(int u, int v, int k, ll w) {
int ru = find(u);
int rv = find(v);
int su = sign[u];
ll du = offset[u];
int sv = sign[v];
ll dv = offset[v];
// 已经在同一个连通块
if (ru == rv) {
// (su - k * sv) * x_root
// = k * dv + w - du
int coef = su - k * sv;
ll rhs = k * dv + w - du;
if (coef == 0) {
// 0 = rhs
return rhs == 0;
}
// coef 一定是 2 或 -2
// 要求 x_root 为整数
if (rhs % 2 != 0) {
return false;
}
// 自由变量被唯一确定
fixed[ru] = true;
return true;
}
// x_ru = A * x_rv + B
int A = su * k * sv;
ll B = (ll)su * (k * dv + w - du);
parent[ru] = rv;
sign[ru] = A;
offset[ru] = B;
// 如果 ru 原本已经被固定,
// 合并后 rv 所在的连通块也被固定
fixed[rv] = fixed[rv] || fixed[ru];
return true;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int p, e;
cin >> p >> e;
DSU dsu(p);
bool ok = true;
for (int i = 0; i < e; i++) {
char type;
int u, v;
ll w;
cin >> type >> u >> v >> w;
// D:x_u - x_v = w
// S:x_u + x_v = w
int k = (type == 'D' ? 1 : -1);
if (ok && !dsu.unite(u, v, k, w)) {
ok = false;
}
}
if (!ok) {
cout << "NO\n";
cout << "-1\n";
return 0;
}
// 没有被方程唯一确定的连通块,每个至少需要实测一个样品
int ans = 0;
for (int i = 1; i <= p; i++) {
if (dsu.find(i) == i && !dsu.fixed[i]) {
ans++;
}
}
cout << "YES\n";
cout << ans << '\n';
return 0;
}
java
java
import java.io.*;
import java.util.*;
public class Main {
static class DSU {
int[] parent, sign;
long[] offset;
boolean[] fixed;
DSU(int n) {
parent = new int[n + 1];
sign = new int[n + 1];
offset = new long[n + 1];
fixed = new boolean[n + 1];
Arrays.fill(sign, 1);
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
}
// x_u = sign[u] * x_parent[u] + offset[u]
int find(int u) {
if (parent[u] == u) {
return u;
}
int p = parent[u];
int r = find(p);
// x_p = sign[p] * x_r + offset[p]
// x_u = sign[u] * x_p + offset[u]
offset[u] += (long) sign[u] * offset[p];
sign[u] *= sign[p];
parent[u] = r;
return r;
}
// 添加关系:x_u = k * x_v + w
boolean unite(int u, int v, int k, long w) {
int ru = find(u);
int rv = find(v);
int su = sign[u];
long du = offset[u];
int sv = sign[v];
long dv = offset[v];
// 已经在同一个连通块
if (ru == rv) {
// (su - k * sv) * x_root
// = k * dv + w - du
int coef = su - k * sv;
long rhs = k * dv + w - du;
if (coef == 0) {
// 0 = rhs
return rhs == 0;
}
// coef 一定是 2 或 -2
// 要求 x_root 为整数
if (rhs % 2 != 0) {
return false;
}
// 自由变量被唯一确定
fixed[ru] = true;
return true;
}
// x_ru = A * x_rv + B
int A = su * k * sv;
long B = (long) su * (k * dv + w - du);
parent[ru] = rv;
sign[ru] = A;
offset[ru] = B;
// 如果 ru 原本已经被固定,
// 合并后 rv 所在的连通块也被固定
fixed[rv] = fixed[rv] || fixed[ru];
return true;
}
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int p = Integer.parseInt(st.nextToken());
int e = Integer.parseInt(st.nextToken());
DSU dsu = new DSU(p);
boolean ok = true;
for (int i = 0; i < e; i++) {
st = new StringTokenizer(br.readLine());
char type = st.nextToken().charAt(0);
int u = Integer.parseInt(st.nextToken());
int v = Integer.parseInt(st.nextToken());
long w = Long.parseLong(st.nextToken());
// D:x_u - x_v = w
// S:x_u + x_v = w
int k = (type == 'D' ? 1 : -1);
if (ok && !dsu.unite(u, v, k, w)) {
ok = false;
}
}
if (!ok) {
System.out.println("NO");
System.out.println("-1");
return;
}
// 没有被方程唯一确定的连通块,每个至少需要实测一个样品
int ans = 0;
for (int i = 1; i <= p; i++) {
if (dsu.find(i) == i && !dsu.fixed[i]) {
ans++;
}
}
System.out.println("YES");
System.out.println(ans);
}
}
python
python
import sys
class DSU:
def __init__(self, n):
self.parent = list(range(n + 1))
self.sign = [1] * (n + 1)
self.offset = [0] * (n + 1)
self.fixed = [False] * (n + 1)
# x_u = sign[u] * x_parent[u] + offset[u]
def find(self, u):
if self.parent[u] == u:
return u
p = self.parent[u]
r = self.find(p)
# x_p = sign[p] * x_r + offset[p]
# x_u = sign[u] * x_p + offset[u]
self.offset[u] += self.sign[u] * self.offset[p]
self.sign[u] *= self.sign[p]
self.parent[u] = r
return r
# 添加关系:x_u = k * x_v + w
def unite(self, u, v, k, w):
ru = self.find(u)
rv = self.find(v)
su = self.sign[u]
du = self.offset[u]
sv = self.sign[v]
dv = self.offset[v]
# 已经在同一个连通块
if ru == rv:
# (su - k * sv) * x_root
# = k * dv + w - du
coef = su - k * sv
rhs = k * dv + w - du
if coef == 0:
# 0 = rhs
return rhs == 0
# coef 一定是 2 或 -2
# 要求 x_root 为整数
if rhs % 2 != 0:
return False
# 自由变量被唯一确定
self.fixed[ru] = True
return True
# x_ru = A * x_rv + B
A = su * k * sv
B = su * (k * dv + w - du)
self.parent[ru] = rv
self.sign[ru] = A
self.offset[ru] = B
# 如果 ru 原本已经被固定,
# 合并后 rv 所在的连通块也被固定
self.fixed[rv] = self.fixed[rv] or self.fixed[ru]
return True
def main():
input = sys.stdin.readline
p, e = map(int, input().split())
dsu = DSU(p)
ok = True
for _ in range(e):
type_, u, v, w = input().split()
u = int(u)
v = int(v)
w = int(w)
# D:x_u - x_v = w
# S:x_u + x_v = w
k = 1 if type_ == 'D' else -1
if ok and not dsu.unite(u, v, k, w):
ok = False
if not ok:
print("NO")
print("-1")
return
# 没有被方程唯一确定的连通块,每个至少需要实测一个样品
ans = 0
for i in range(1, p + 1):
if dsu.find(i) == i and not dsu.fixed[i]:
ans += 1
print("YES")
print(ans)
if __name__ == "__main__":
main()
javascript
js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let lines = [];
rl.on('line', line => {
lines.push(line);
});
rl.on('close', () => {
let idx = 0;
class DSU {
constructor(n) {
this.parent = new Int32Array(n + 1);
this.sign = new Int32Array(n + 1);
this.offset = new Float64Array(n + 1);
this.fixed = new Array(n + 1).fill(false);
for (let i = 1; i <= n; i++) {
this.parent[i] = i;
this.sign[i] = 1;
}
}
// x_u = sign[u] * x_parent[u] + offset[u]
find(u) {
if (this.parent[u] === u) {
return u;
}
const p = this.parent[u];
const r = this.find(p);
// x_p = sign[p] * x_r + offset[p]
// x_u = sign[u] * x_p + offset[u]
this.offset[u] += this.sign[u] * this.offset[p];
this.sign[u] *= this.sign[p];
this.parent[u] = r;
return r;
}
// 添加关系:x_u = k * x_v + w
unite(u, v, k, w) {
const ru = this.find(u);
const rv = this.find(v);
const su = this.sign[u];
const du = this.offset[u];
const sv = this.sign[v];
const dv = this.offset[v];
// 已经在同一个连通块
if (ru === rv) {
// (su - k * sv) * x_root
// = k * dv + w - du
const coef = su - k * sv;
const rhs = k * dv + w - du;
if (coef === 0) {
// 0 = rhs
return rhs === 0;
}
// coef 一定是 2 或 -2
// 要求 x_root 为整数
if (rhs % 2 !== 0) {
return false;
}
// 自由变量被唯一确定
this.fixed[ru] = true;
return true;
}
// x_ru = A * x_rv + B
const A = su * k * sv;
const B = su * (k * dv + w - du);
this.parent[ru] = rv;
this.sign[ru] = A;
this.offset[ru] = B;
// 如果 ru 原本已经被固定,
// 合并后 rv 所在的连通块也被固定
this.fixed[rv] = this.fixed[rv] || this.fixed[ru];
return true;
}
}
const [p, e] = lines[idx++].trim().split(/\s+/).map(Number);
const dsu = new DSU(p);
let ok = true;
for (let i = 0; i < e; i++) {
const [type, uStr, vStr, wStr] = lines[idx++].trim().split(/\s+/);
const u = Number(uStr);
const v = Number(vStr);
const w = Number(wStr);
// D:x_u - x_v = w
// S:x_u + x_v = w
const k = type === 'D' ? 1 : -1;
if (ok && !dsu.unite(u, v, k, w)) {
ok = false;
}
}
if (!ok) {
console.log("NO");
console.log("-1");
return;
}
// 没有被方程唯一确定的连通块,每个至少需要实测一个样品
let ans = 0;
for (let i = 1; i <= p; i++) {
if (dsu.find(i) === i && !dsu.fixed[i]) {
ans++;
}
}
console.log("YES");
console.log(ans);
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
)
type DSU struct {
parent []int
sign []int
offset []int64
fixed []bool
}
func NewDSU(n int) *DSU {
dsu := &DSU{
parent: make([]int, n+1),
sign: make([]int, n+1),
offset: make([]int64, n+1),
fixed: make([]bool, n+1),
}
for i := 1; i <= n; i++ {
dsu.parent[i] = i
dsu.sign[i] = 1
}
return dsu
}
// x_u = sign[u] * x_parent[u] + offset[u]
func (d *DSU) find(u int) int {
if d.parent[u] == u {
return u
}
p := d.parent[u]
r := d.find(p)
// x_p = sign[p] * x_r + offset[p]
// x_u = sign[u] * x_p + offset[u]
d.offset[u] += int64(d.sign[u]) * d.offset[p]
d.sign[u] *= d.sign[p]
d.parent[u] = r
return r
}
// 添加关系:x_u = k * x_v + w
func (d *DSU) unite(u, v, k int, w int64) bool {
ru := d.find(u)
rv := d.find(v)
su := d.sign[u]
du := d.offset[u]
sv := d.sign[v]
dv := d.offset[v]
// 已经在同一个连通块
if ru == rv {
// (su - k * sv) * x_root
// = k * dv + w - du
coef := su - k*sv
rhs := int64(k)*dv + w - du
if coef == 0 {
// 0 = rhs
return rhs == 0
}
// coef 一定是 2 或 -2
// 要求 x_root 为整数
if rhs%2 != 0 {
return false
}
// 自由变量被唯一确定
d.fixed[ru] = true
return true
}
// x_ru = A * x_rv + B
A := su * k * sv
B := int64(su) * (int64(k)*dv + w - du)
d.parent[ru] = rv
d.sign[ru] = A
d.offset[ru] = B
// 如果 ru 原本已经被固定,
// 合并后 rv 所在的连通块也被固定
d.fixed[rv] = d.fixed[rv] || d.fixed[ru]
return true
}
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var p, e int
fmt.Fscan(in, &p, &e)
dsu := NewDSU(p)
ok := true
for i := 0; i < e; i++ {
var typ string
var u, v int
var w int64
fmt.Fscan(in, &typ, &u, &v, &w)
// D:x_u - x_v = w
// S:x_u + x_v = w
k := 1
if typ == "S" {
k = -1
}
if ok && !dsu.unite(u, v, k, w) {
ok = false
}
}
if !ok {
fmt.Fprintln(out, "NO")
fmt.Fprintln(out, "-1")
return
}
// 没有被方程唯一确定的连通块,每个至少需要实测一个样品
ans := 0
for i := 1; i <= p; i++ {
if dsu.find(i) == i && !dsu.fixed[i] {
ans++
}
}
fmt.Fprintln(out, "YES")
fmt.Fprintln(out, ans)
}