0%

29609102 NOIP模拟题

GardenP3160 [CQOI2012]局部极小值

\(n\times m\) 的矩阵进行不重复的 \([1,\ n\times m]\) 整数填数,定义局部最小值为比其八连通块都小的位置,给定局部最小值,求合法方案数

如果罗兹瓦尔说的像这样清晰该有多好

还是我语文太差了

先不讨论非局部最小值的合法性,显然这是一个拓扑排序计数问题,对于一般图的拓扑排序计数问题,通常使用状压\(DP\),把已排序好的集合作为状态,在转移时判断加入集合的点的前驱是否都已排序,而顺序性体现已排序集合的扩大的过程中,这也是排序问题的核心思想 在这道题里还有一个比较明显的性质,局部最小值的数量不超过 \(8\)

所以考场上直接写了一个裸的拓扑排序计数和一个小小的剪枝

然后,果断便会白给

没有生成一组极限数据测状态数

正解首先是对 \(DP\) 的优化

设局部最小值的填数状态为 \(S\) 已填 \(i\) 个数的方案数 \(f_{i,S}\) 和在状态 \(S\) 下不受局部最小值限制的位置个数为 \(g_S\) 则有转移

\[ f_{i,S}\ =\ f_{i-1,S}\times (g_S-(i-1))+\sum_{p\in S}f_{i-1,S-p} \]

前一部是当前的数不给局部最小值,后一部分是给局部最小值

时间复杂度 \(O(nm2^k)\) 其中 \(k\le8\)

接下来讨论非局部最小值的合法性,考虑直接按上述式子 \(DP\) 求出的是至少\(0\)个非局部最小值成为局部最小值,而题意要求恰好\(0\)个非局部最小值成为局部最小值,直接组合容斥即可

具体来说,设 \(h_k\) 为至少有\(k\)个非局部最小值成为局部最小值,那么就有

\[ ans\ =\ \sum(-1)^kh_k \]

其中 \(h_k\) 不需要具体求出,\(DFS\) 枚举合法的转化数量即可

时间大概 \(O(tnm2^k)\) 其中 \(t\in [\Pi(nm-9i-9k),\Pi(nm-4i-4k)]\) \(t\) 离上界相差很远,总之很优秀

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
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <map>
using namespace std;
inline int read(int f = 1, int x = 0, char ch = ' ')
{
while(!isdigit(ch = getchar())) if(ch == '-') f = -1;
while(isdigit(ch)) x = x*10+ch-'0', ch = getchar();
return f*x;
}
const int N = 4+5, M = 7+5, P = 12345678;
const int dx[8] = {0, 0, 1, 1, -1, -1, 1, -1};
const int dy[8] = {1, -1, 1, -1, 1, -1, 0, 0};
int t, n, m, d;
char w[N][M];
int f[N*M][1<<8], ans, g[1<<8];
int bit(int i, int j) { return (i-1)*m+j-1; }
pair<int, int> dbit(int p) { return make_pair(p/m+1, p%m+1); }
pair<int, int> c[N];
int hateMathers()
{
memset(f, 0, sizeof(f)); d = 0;
memset(g, 0, sizeof(g));
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
if(w[i][j] == 'X') c[d++] = make_pair(i, j);
for(int s = 0; s < 1<<d; ++s)
{
for(int i = 0; i < d; ++i) w[c[i].first][c[i].second] = (s>>i&1)?'.':'X';
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
{
if(w[i][j] == 'X') continue;
int q = 1;
for(int k = 0; k < 8; ++k) if(w[i+dx[k]][j+dy[k]] == 'X') q = 0;
g[s] += q;
}
}
for(int i = 0; i < d; ++i) w[c[i].first][c[i].second] = 'X';
f[0][0] = 1;
for(int i = 1; i <= n*m; ++i)
for(int s = 0; s < 1<<d; ++s)
{
f[i][s] = 1ll*f[i-1][s]*(g[s]-i+1)%P;
for(int p = 0; p < d; ++p)
if(s>>p&1) f[i][s] = (f[i][s]+f[i-1][s^(1<<p)])%P;
}
return f[n*m][(1<<d)-1];
}
void hateRoswaal(int p, int k)
{
if(p == n*m) return void(ans = (ans+k*hateMathers()+P)%P);
pair<int, int> par = dbit(p); int x = par.first, y = par.second;
if(w[x][y] == 'X') hateRoswaal(p+1, k);
else
{
hateRoswaal(p+1, k);
for(int i = 0; i < 8; ++i) if(w[x+dx[i]][y+dy[i]] == 'X') return;
w[x][y] = 'X'; hateRoswaal(p+1, -k); w[x][y] = '.';
}
}
int main()
{
freopen("garden.in", "r", stdin);
freopen("garden.out", "w", stdout);
t = read();
while(t--)
{
n = read(), m = read(); ans = 0;
memset(w, 0, sizeof(w));
for(int i = 1; i <= n; ++i) scanf("%s", w[i]+1);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
{
bool is_emp = true;
for(int k = 0; k < 8&&is_emp; ++k)
if(w[i+dx[k]][j+dy[k]] == 'X')
is_emp = false;
if(!is_emp&&w[i][j] == 'X') ans = -1;
}
if(ans == -1) printf("0\n");
else hateRoswaal(0, 1), printf("%d\n", ans);
}
return 0;
}

Throw

\((a,\ b,\ c)\) 经过 \(f(a,\ b,\ c)\) 变为 \((a',\ b',\ c')\) 的最小操作个数

很好的思维题

考试时写搜索时,想到双向搜索,想到双向搜索又想到建树,但没继续像,转而想 \(exgcd\) 和毒瘤贪心

首先题目中的丢你拉姆过于花哨,与三元素中每一个元素等价这一条件显得十分不自然,我们结合 P1007 独木桥 的想法,我们把调换顺序看作平移相同距离,这样每个元素在变化上与等价的条件相应,即

\[ f_(a,\ b,\ c)\rightarrow \begin{cases} (a+(b-a),\ b+(b-a),\ c),\ b-a<c-b\\\\ (a-(b-a),\ b-(b-a),\ c)\\\\ (a,\ b-(c-b),\ c-(c-b)),\ b-a>c-b\\\\ (a,\ b+(c-b),\ c+(c-b)) \end{cases} \]

显然 \((a,\ b,\ c)\) 的变化不超过 \(3\)

我们考虑按照这种关系建树,发现以 \((A,\ B,\ C)\) 其中 \(B-A=C-B\) 时恰好为树的形态且还是一颗二叉树,那么答案就是树上两点的距离

其次答案具有单调性,所以先调整到同一高度,在二分向上跳的距离判断是否相等即可

在向上跳的过程中可以一下跳多步,用距离除一下贪心即可

或者按照这样理解

\(g(n,\ m)\) 其中 \(n=b-a,\ m=c-b\)

那么向上跳是一个更相减损的过程,用辗转相除优化即可,这也是 [CQOI2017] 小Q的表格 最关键的一步

还有一些细节,在注释里标注

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
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <queue>
using namespace std;
inline int read(int f = 1, int x = 0, char ch = ' ')
{
while(!isdigit(ch = getchar())) if(ch == '-') f = -1;
while(isdigit(ch)) x = x*10+ch-'0', ch = getchar();
return f*x;
}
const int INF = 0x3f3f3f3f;
struct Node
{
int p[3];
int operator [] (const int x) { return p[x]; }
bool operator == (const Node &_) const
{
return p[0]==_.p[0]&&p[1]==_.p[1]&&p[2]==_.p[2];
}
bool operator != (const Node &_) const
{
return !(p[0]==_.p[0]&&p[1]==_.p[1]&&p[2]==_.p[2]);
}
Node(int x = 0, int y = 0, int z = 0)
{
p[0] = x, p[1] = y, p[2] = z;
sort(p, p+3);
}
}s, t;
int ds, dt, ans, buf;
Node get(Node p, int up, int &d)
{
int a = p[1]-p[0], b = p[2]-p[1], c;
if(!up||a == b) return p;
if(a < b) c = min(up, (b-1)/a), p = Node(p[0]+c*a, p[1]+c*a, p[2]); // 不能相等,所以要减 1
else c = min(up, (a-1)/b), p = Node(p[0], p[1]-c*b, p[2]-c*b);
d += c; up -= c;
return get(p, up, d);
}
int main()
{
freopen("throw.in", "r", stdin);
freopen("throw.out", "w", stdout);
s = Node(read(), read(), read());
t = Node(read(), read(), read());
if(get(s, INF, ds) != get(t, INF, dt)) puts("NO");
else
{
puts("YES");
if(ds < dt) swap(s, t), swap(ds, dt);
int l = 0, r = dt; s = get(s, ds-dt, buf);
while(l <= r)
{
int mid = (l+r)>>1;
if(get(s, mid, buf) == get(t, mid, buf)) ans = mid, r = mid-1;
else l = mid+1;
}
printf("%d\n", ans*2+ds-dt); // 注意乘2
}
return 0;
}