GYM 100722F 解题思路分析
题目描述:
给你一个目标数独和一个未完成的且只有一种解法的标准数独,允许你对数独进行一下五种操作:
- 顺时针或逆时针旋转这个数独;
- 交换一个3*9矩阵内的任意两行;
- 交换一个9*3矩阵内的任意两列;
- 交换任意两个row segment或col segment;
- 对数独内的数字进行置换。
每种操作都可以使用多次,要求我们判断那个未完成数独在完成之后是否可以变成上面所给出的目标数独。
思路分析:
其实一开始的那份代码我码了足足有380行,净用时115分钟左右。而那份380行的代码思路不得不说——就是那种读懂题就马上开始写的代码,没有分析题目的性质(尽管我对数独本来就不怎么熟悉...),在我还为自己的代码解数独的速度沾沾自喜的时候,我发现在两个数独的相互转化的时候才是我的大麻烦!我又把这样一道题想得太简单了,只是用结构体和几个结构体内的交换行和列的函数放在bfs里面搞一搞就想过掉这道题。没成想巨大的常数让这个程序和我之后写的另一个程序现出云泥之别。
超时之后,我再次分析题目的性质,发现这道题其实并不需要把那个未完成的数独解出来啊!因为那个数独的解是唯一的,所以说只要那个未完成的数独能够与给出的目标数独匹配,完成的数独就一定能与目标数独匹配。那么下一个问题就是,如何实现两个数独之间的匹配呢?
一看到这几种变化性质,首先要处理的一定是旋转变化了,因为它只有4个方向,而且旋转之后对整个数独是没有影响的。
然后接下来要处理的是两个相似的变化,交换列与交换行。一开始的第一份代码,我把交换列和交换行放在一起去考虑,结果整个程序陷入了一个巨大的常数之中,出现巨大常数的原因其实就是当交换列的交换行在一起考虑的时候,每一次转移状态的常数都大于20,所以说冗余操作太多、STL也太多,那么就要换一种搜索的思路了。
由于单列之间的交换仅限于相邻的三行,所以每个segment的交换都有3!既6种,共有3个segment,就是有(3!)^3种,三个segment的顺序可以不同,所以共有(3!)^3*3!种行的排列方式,同时考虑行和列也只有1679616种可能,所以简单枚举行和列的排列情况就可以。
对于行和列的枚举排列也是用了一点点小技巧的:那就是从a数独(既未完成的数独)中的每一行,一行一行地对应到b数独(既已完成的数独)中,利用该行所在的组的其他行的位置来确定这一行的可行范围。并且将a数独中每一行所在b数独中对应的行数保存好作为映射。
在处理列的时候就稍有区别了,同样都是枚举每列的位置,把位置存在数组中,然后每搜索出一片区域,都要处理置换的关系,如果置换不是合法的,则此次搜索不是解,如果置换全部是合法的,则一直搜下去。这个时候注意一个点,就是用过的置换在回溯的时候记得要清空在这行出现的新的数字之间的置换关系,但是清空这部分就有讲究了:既不能清除掉不是在这一行做出的标记。所以要标记好每个置换关系是在那一行出现或者使用的。
这里要注意的是,a和b数组在比较是否可以在转移之后相互吻合的时候,一定要用变化后的a数独(既刚处理过的映射关系)与b数独进行比较。
这里还要着重的说一下关于数独的置换出现矛盾的情况,只要应置换的数之前已经被处理过置换关系了而且置换的目标并不是这里枚举的被置换数,就可以成为置换是矛盾的。
代码:(2016.12.27版)
#define fs first
#define sc second
#define F first
#define S second
#define pb push_back
#define mp make_pair
#define forn(i, n) for(int i = 0 ; (i) < (n) ; ++i)
#define forit(it,v) for(typeof((v).begin()) it = v.begin() ; it != (v).end() ; ++it)
#define eprintf(...) fprintf(stderr, __VA_ARGS__),fflush(stderr)
#define sz(a) ((int)(a).size())
#define all(a) (a).begin(),a.end()
static inline unsigned long long rdtsc() { unsigned long long d; __asm__ __volatile__ ("rdtsc" : "=A" (d) ); return d; }
using namespace std;
typedef long long ll;
typedef double dbl;
typedef long double ld;
typedef vector vi;
typedef pair pi;
const int inf = (int)1.01e9;
const dbl eps = 1e-9;
const int INF = (int)1.01e9;
const ld EPS = 1e-9;
/* --- main part --- */
#define TASK "a"
const int k = 5;
int Ps[k];
struct Hash {
const static int n = k;
int a[n];
Hash() {}
Hash(long long x) {
for (int i = 0; i < n; ++i) {
a[i] = x % Ps[i];
if (a[i] < 0) {
a[i] += Ps[i];
}
}
}
inline Hash operator - (const Hash &h) const {
Hash res;
for (int i = 0; i < n; ++i) {
res.a[i] = (a[i] - h.a[i]);
if (res.a[i] < 0) {
res.a[i] += Ps[i];
}
}
return res;
}
inline Hash operator + (const Hash &h) const {
Hash res;
for (int i = 0; i < n; ++i) {
res.a[i] = (a[i] + h.a[i]);
if (res.a[i] >= Ps[i]) {
res.a[i] -= Ps[i];
}
}
return res;
}
inline Hash operator * (const Hash &h) const {
Hash res;
for (int i = 0; i < n; ++i) {
res.a[i] = (long long) a[i] * h.a[i] % Ps[i];
}
return res;
}
inline bool operator < (const Hash &h) const {
for (int i = 0; i < n; ++i) {
if (a[i] != h.a[i]) {
return a[i] < h.a[i];
}
}
return 0;
}
};
Hash Q, Q1, Q2, Q3;
const int maxn = (int) 1e5 + 10;
const int maxs = maxn * 7;
Hash Qs[maxs];
void precalc() {
for (int i = 0, cur = (int) 1e9 + 7; i < k; ++i) {
cur += rand() % 1000;
Ps[i] = cur;
cur += 2;
}
//eprintf("%d %d\n", Ps[0], Ps[1]);
Q = 239017;
Q1 = 424243;
Q2 = 1234123;
Q3 = 3453453;
Qs[0] = 1;
for (int i = 1; i < maxs; ++i) {
Qs[i] = Qs[i - 1] * Q;
}
}
char str[maxs];
int read() {
if (scanf("%s", str) < 1) {
return 0;
}
assert((int) strlen(str) < maxs);
return 1;
}
int cnth;
int cntv;
map getHashId;
Hash hashes[maxn];
char names[maxn][5];
int lens[maxn];
int hashId[maxn];
int go[maxn][2];
int buildNode(Hash h, int len, char *name, int left, int right) {
assert(cntv < maxn);
{
auto iter = getHashId.find(h);
if (iter != getHashId.end()) {
hashId[cntv] = iter->second;
} else {
assert(cnth < maxn);
hashId[cntv] = cnth;
getHashId[h] = cnth;
hashes[cnth] = h;
++cnth;
}
}
lens[cntv] = len;
memcpy(names[cntv], name, sizeof(char) * (len + 1));
go[cntv][0] = left, go[cntv][1] = right;
++cntv;
return cntv - 1;
}
int parse(char *&s) {
//eprintf("parse %s\n", s);
char tmp[5];
int len = 0;
Hash name = 0;
for (; islower(s[0]); ++s) {
name = name * Q;
name = name + (int) s[0];
tmp[len++] = s[0];
}
assert(len <= 4);
tmp[len] = 0;
if (!s[0] || s[0] == ')' || s[0] == ',') {
return buildNode(name, len, tmp, -1, -1);
}
name = name * Q;
name = name + Q1;
assert(s[0] == '(');
++s;
char *s0 = s;
int vl = parse(s);
assert(s[0] == ',');
++s;
name = name * Qs[s - s0] + hashes[hashId[vl]];
name = name * Q + Q2;
s0 = s;
int vr = parse(s);
assert(s[0] == ')');
++s;
name = name * Qs[s - s0] + hashes[hashId[vr]];
name = name * Q + Q3;
return buildNode(name, len, tmp, vl, vr);
}
int res;
char ans[maxs];
int used[maxn];
int superId[maxn];
int superMaxId;
void print(int v) {
used[hashId[v]] = 1;
superId[hashId[v]] = superMaxId++;
//eprintf("v = %d, hash = %d\n", v, hashes[hashId[v]].a[0]);
memcpy(ans + res, names[v], sizeof(char) * lens[v]);
res += lens[v];
if (go[v][0] == -1) {
return;
}
ans[res++] = '(';
for (int it = 0; it < 2; ++it) {
int u = go[v][it];
//eprintf("%d->%d (%d)\n", v, u, hashId[u]);
if (used[hashId[u]]) {
int x = superId[hashId[u]] + 1;
int cnt = 0;
for (; x; x /= 10, ++cnt) {
ans[res + cnt] = x % 10 + '0';
}
reverse(ans + res, ans + res + cnt);
res += cnt;
} else {
print(u);
}
if (!it) {
ans[res++] = ',';
}
}
ans[res++] = ')';
}
void solve() {
char *q = str;
cntv = 0;
cnth = 0;
getHashId.clear();
int root = parse(q);
for (int i = 0; i < cnth; ++i) {
used[i] = 0;
}
res = 0;
superMaxId = 0;
print(root);
assert(res < maxs);
ans[res] = 0;
printf("%s\n", ans);
}
int main()
{
srand(23);
#ifdef home
assert(freopen(TASK".in", "r", stdin));
assert(freopen(TASK".out", "w", stdout));
#endif
precalc();
int maxt;
while (scanf("%d", &maxt) == 1) {
for (int t = 0; t < maxt; ++t) {
assert(read());
solve();
#ifdef home
eprintf("Time: %d ms\n", (int)(clock() * 1000. / CLOCKS_PER_SEC));
#endif
}
}
return 0;
}