Parker大神说,addition这种类型的题目在ACM里面算是水题,学弱心里不禁深深颤抖了一下,果然ACM太难。突然想起了高中竞赛时代从来没有碰过的《难题集萃》。HAHA。
=======================================================================
Anyway,这里继续讨论addition这道题目。
#1
上一篇blog给出来的解法是有bug的,Parker大神指出,我的问题出现在visted上面。他给出了以下test case:
a+b=1
b+c=1
a+d=1
d+e=1
e+c=1
求解a+c,如果是以下链路a->b->c,偶数条,不能求解,继续找a->b->c->e->d->a这个时候,所有的edge都visited过了,但是依然没有解。那么就返回无解麽?事实上不是,因为这样一条链路是有解的:a->b->c->e->d->a->b->c,奇数条。Parker大神指出,应该看点的奇偶性,有可能经过同一个点,比如说b,第一次出现的时候,是在链路的第2个位置,认为它是偶数点,第二次出现的时候是在链路的第7个位置,因此认为是奇数点,这两个点虽然都是同一个string,但是奇偶性不一样,故认为是不同点。故visited里面应该用来存的是点,而且点是有奇偶性的点。
另外一个值得注意的地方是,visited set在每次 dfs 失败之后不应该撤销搜索点。因为证明改点无法链接到目标点。之前因为撤销了失败搜索的点,而large test case里面的每条搜索路径又特别长,使得每次搜索耗时特别长。
=======================================================================
#2 Parker大神还给出来一个良好的数据结构:
每次Parse完(e.g. banana + apple = 4)的时候应该把string hash到int,比如banana->0, apple->1,这样每次判断端点的时候不需要处理字符串而是直接处理int,效率高很多!
存所有edge的时候使用list<list<int>>的结构去存,list.get(i)就可以得到一个list,这个list里面存着所有以 “ i ” 开始的边。
但是我觉得写起来特别费劲就没修改代码了。
=======================================================================
#3 数据结构
增加了一个point类,有parity和string两个成员;
使用map<string, map<string, integer>>去存储边,第一个string是起点,第二个string是端点,这样在 dfs 扩展当前状态的时候,效率高很多。
使用set<Point>去存访问过的点。
=======================================================================
#4 代码
import java.util.*;
import java.io.*;
public class Solution {
// global variable
private static Map<String, HashMap<String, Integer>> edge_map = new HashMap<String, HashMap<String, Integer>>();
public static void main(String[] args) {
File inFile = new File ("C-large-practice.in");
File outFile = new File ("C-large-practice.out");
try {
BufferedReader br = new BufferedReader(new FileReader(inFile));
BufferedWriter bw = new BufferedWriter(new FileWriter(outFile));
int T = Integer.parseInt(br.readLine());
for (int i = 1; i <= T; i++) {
// reset data
edge_map.clear();
int N = Integer.parseInt(br.readLine());
for (int j = 0; j < N; j++) {
String s = br.readLine();
// parse line
String[] parts = s.split("[\\+\\=]");
String p1 = parts[0];
String p2 = parts[1];
int wt = Integer.parseInt(parts[2]);
// update data
if (!edge_map.containsKey(p1)) edge_map.put(p1, new HashMap<String, Integer>());
if (!edge_map.containsKey(p2)) edge_map.put(p2, new HashMap<String, Integer>());
edge_map.get(p1).put(p2, wt);
edge_map.get(p2).put(p1, wt);
}
int Q = Integer.parseInt(br.readLine());
bw.write("Case #" + i + ":\n");
for (int j = 0; j < Q; j++) {
String s = br.readLine();
String[] parts = s.split("[\\+]");
String src = parts[0];
String dst = parts[1];
// begin searching the path
ArrayList<Integer> path = new ArrayList<Integer>();
HashSet<Point> visited = new HashSet<Point>();
if (dfs(src, dst, path, visited)) {
int v = val(path);
bw.write(src + "+" + dst + "=" + v + "\n");
// update edge_map to avoid duplicate calculation
edge_map.get(src).put(dst, v);
edge_map.get(dst).put(src, v);
}
else {
ArrayList<Integer> path1 = new ArrayList<Integer>();
ArrayList<Integer> path2 = new ArrayList<Integer>();
HashSet<Point> visited1 = new HashSet<Point>();
HashSet<Point> visited2 = new HashSet<Point>();
boolean b1 = dfs(src, src, path1, visited1);
boolean b2 = dfs(dst, dst, path2, visited2);
if (b1 && b2) {
int v1 = val(path1);
int v2 = val(path2);
bw.write(src + "+" + dst + "=" + (v1+v2)/2 + "\n");
// update edge_map to avoid duplicate calculation
edge_map.get(src).put(src, v1);
edge_map.get(dst).put(dst, v2);
edge_map.get(src).put(dst, (v1+v2)/2);
edge_map.get(dst).put(src, (v1+v2)/2);
}
}
}
}
bw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/* find a path from source(src) to destination(dst) */
/* edge case: e+e, src==dst */
public static boolean dfs (String src, String dst, ArrayList<Integer> path, HashSet<Point> visited) {
if (!edge_map.containsKey(src) || !edge_map.containsKey(dst)) return false;
if (edge_map.get(src).containsKey(dst) && path.size() % 2 == 0) {
int wt = edge_map.get(src).get(dst);
path.add(wt);
return true;
}
if (visited.size() == 0) visited.add(new Point(src, 1)); // include the very first point in the visited set
for (String s : edge_map.get(src).keySet()) {
Point p = new Point(s, path.size() % 2);
if (visited.contains(p)) continue;
visited.add(p);
int wt = edge_map.get(src).get(s);
path.add(wt);
if (dfs(s, dst, path, visited)) return true;
// undo the path and visited set
path.remove(path.size()-1);
}
return false;
}
/* calculate the value from source to destination */
public static int val (ArrayList<Integer> path) {
int value = 0;
for (int i = 0; i < path.size(); i++) {
int wt = path.get(i);
if (i % 2 == 0) value += wt;
else value -= wt;
}
return value;
}
}
/* Point class */
class Point {
String s;
int par; // 0 means even, 1 means odd
// constructor
public Point(String s, int par) {
this.s = s;
this.par = par;
}
// equals method
public boolean equals(Object obj) {
if (obj == null) return false;
if (this == obj) return true;
if (this.getClass() != obj.getClass()) return false;
final Point p = (Point) obj;
return (this.s.equals(p.s)) && (this.par == p.par);
}
// hashCode
public int hashCode() {
int hash = 1;
hash = 31 * hash + s.hashCode();
hash = 31 * hash + par;
return hash;
}
}