「SOL」NOI2016 Day1 解题报告 | Lucky_Glass's Blog
0%

「SOL」NOI2016 Day1 解题报告

第一次打 NOI,还是先把以前的 NOI 题刷一遍吧?


# A. 优秀的拆分 excellent

> Link 优秀的拆分 - LOJ

如果能对每个位置求出以它开头/结尾的形如 AA 的串的数量,那这个问题就非常简单了。

考虑如何对 AA 串计数。和另外一道题(忘了题目了)非常类似,那道题是回文串,将原串 $kL$ 的位置设为关键点,任何长度为 $2L$ 的回文串一定经过两个关键点。这道题则是任何 $|A|=L$ 的 AA 串都会经过两个关键点。

SA 快速处理出两个前缀的 LCS 和两个后缀的 LCP。我们可以判断:

png1

—— 以 $S_1$ 到 $S_2$ 这些位置开头存在长度为 $2L$ 的 AA 串,以 $T_1$ 到 $T_2$ 这些位置结尾存在长度为 $2L$ 的 AA 串。

差分计数即可。


# B. 网格 grid

> Link 网格 - LOJ

看来做的题比较多还是有好处。有一类题要求最小化操作数量,看起来非常复杂,但是通过一些人类智慧可以发现最大答案其实很小,即存在简单的合法构造方案。这样将问题转化为「判断是否存在答案为 $x$ 的可行解」。

(称“跳蚤”为白格,“蛐蛐”为黑格)

同样的道理,分析这道题。首先不难发现无解的情况只有:

  • 只有一个白格;
  • 只有两个白格且相邻。

排除这两个情况,剩下的都有解。

可以用至多 $4$ 次操作把一个白格围起来,因此答案至多为 $4$。远远不够,我们还发现可以利用一个极大白格连通块的边角处,一个白格连通块必定存在“角”这种只需要两次操作就可以围起来的格子,因此答案至多为 $2$。

换句话说,答案只能是 $0,1,2$。

答案是 $0$ 可以直接判断连通性(关于建图之后再分析),那么就只剩下判断答案是 $1$ 还是 $2$。
直观上感觉 $2$ 比 $1$ 难判断,的确如此。答案是 $1$ 意味着只需要填一个格子就可以把原本的连通块割成多个……这不就是割点吗?

可以暴力建出点、边数量都是 $\mathcal O(nm)$ 的图通过部分数据。

我们只需要用到这张图的连通性以及可能的割点,显然图中有很多点是可以合并的:

  • 合并点不会改变原图连通性;
  • 如果两个点都不可能是割点,则可以合并,合并后的点不可能是原图的割点

什么点可能是割点?必然是在周围(八联通)有一个黑格的白格。因此我们只需要把黑格周围的白格单独建点,其他的按行合并。如图:

png2

这样点数、边数都与黑格数量同阶,可以用 Tarjan 求割点判断答案,顺便还可以判断连通性。


# C. 循环之美 cyclic

久仰大名

结论

一个数 $A$ 是 $k$ 进制 $L$ 纯循环的充要条件是 $A\times k^L-A$ 是整数。

如果 $A$ 表示成最简分数 $\frac xy$,则上述条件等价于 $y\mid k^L-1$。则 $A$ 是 $k$ 进制纯循环小数当且仅当 $k^L\equiv1\pmod y$ 有 $L\gt 1$ 的解 $L$,运用某些数论知识(忘了,但是我记得住结论 awa)可以得到等价条件为 $k,y$ 互质。

于是我们可以知道答案为:

看着就很莫比乌斯反演,但是我自己做的时候思路错了,不应该直接把两个条件都反演掉(只能做到 $\mathcal O(n\sigma_0(k))$)……

UPD. 两个条件都反演掉也能做,将 $[(y,k)=1]$ 反演后设 $t\mid (y,k)$,则 $y$ 的可取值的数量为 $\lfloor\tfrac m{\mathrm{lcm}(t,d)}\rfloor$,再对 lcm 进行反演:

枚举 $t,d$ 的 GCD 为 $p$:

再反演掉,第二行是转为枚举 $r=qp$:

整理一下整除的关系:$p\mid r\to r\mid t\to t\mid k$,还有 $r\mid d$,简化一下式子,直接枚举 $(p,r,t)$:

后边的那个求和可以写为

继续推式子:

当 $r\neq1$ 时可以递归计算,只会递归 $\mathcal O(\log n)$ 层,当 $r=1$ 时

可以数论分块+杜教筛。

还是下面这个做法比较小清新……

直接上正解吧,两个条件中取较简单的一个反演 —— $k$ 是常数,所以尝试反演 $[(y,k)=1]$:

注意到后面的式子和原问题很像,实际上令 $f(n,m,k)$:

则反演后的结果为:

递归处理 $d\gt1$ 的情况,直到 $nm=0$ 答案为 $0$;对于 $d=1$ 即 $k=1$ 直接计算:

直接数论分块+杜教筛。


# 小结

A 题有一定思维难度,不过题目给的部分分比较生艹,$\mathcal O(n^2)$ 给到了 95pts,如果没有时间想正解拿了 95pts 也差不多了。

B 题纯粹考实现,也就是建图那部分。只要把答案只有 $-1,0,1,2$ 这一点想到,正解就呼之欲出了,只是看实现问题,预估将会是考场上用时最长的一道题。

C 题的结论可以靠感知(背结论 awa)+暴力验证,我是写了一个 BSGS 来验证了 $500$ 范围内是正确的,然后就默认结论没有错。不管怎么莫比乌斯反演,至少能够得到一个 $\mathcal O(n\sigma_0(k))$ 的可以直接暴力算的式子,然后线性筛 $\le2\times10^7$ 的就可以得到 84pts,应该是比较好的成绩了。说起来,要敢开数组,只要不爆空间,虽然看起来 $\mathcal O(n\sigma_o(k))$ 跑 $n=2\times10^7$ 看起来很离谱,但是敢写就能过……


# 源代码

差点忘了
几份代码写的时间间隔比较长,中途还改了码风……

点击展开/折叠 excellent.cpp
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/*Lucky_Glass*/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 30005;
#define con(typ) const typ &

int n, ncas;
int elg2[N], cnts[N], cntt[N];
char str[N];

struct Sa {
int sa[N], rnk[N], nsa[N], nrnk[N], bin[N];
int hgt[N][20];
void clean(con(int) len) {
memset(bin, 0, sizeof bin);
for (int i = 1; i <= len; i++)
rnk[i] = nrnk[i] = 0;
}
void build(char *sstr, con(int) len) {
clean(len);
for (int i = 1; i <= len; i++) bin[sstr[i] - 'a' + 1]++;
for (int i = 1; i <= 26; i++) bin[i] += bin[i - 1];
for (int i = 1; i <= len; i++) sa[bin[sstr[i] - 'a' + 1]--] = i;
rnk[sa[1]] = 1;
for (int i = 2; i <= len; i++) {
rnk[sa[i]] = rnk[sa[i - 1]];
if ( sstr[sa[i]] != sstr[sa[i - 1]]) rnk[sa[i]]++;
}
#define ikey(i) ((i) > len ? 0 : rnk[i])
for (int l = 1; rnk[sa[len]] < len; l <<= 1) {
for (int i = 1; i <= len; i++) bin[rnk[sa[i]]] = i;
for (int i = len; i >= 1; i--)
if ( sa[i] > l )
nsa[bin[rnk[sa[i] - l]]--] = sa[i] - l;
for (int i = len - l + 1; i <= len; i++)
nsa[bin[rnk[i]]--] = i;
nrnk[nsa[1]] = 1;
for (int i = 2; i <= len; i++) {
nrnk[nsa[i]] = nrnk[nsa[i - 1]];
if ( ikey(nsa[i]) != ikey(nsa[i - 1])
|| ikey(nsa[i] + l) != ikey(nsa[i - 1] + l) )
nrnk[nsa[i]]++;
}
swap(rnk, nrnk), swap(sa, nsa);
}
for (int i = 1, j = 0; i <= len; i++) {
if ( j ) j--;
while ( rnk[i] < len && sstr[i + j] == sstr[sa[rnk[i] + 1] + j] ) j++;
hgt[rnk[i]][0] = j;
}
// for (int i = 1; i < len; i++)
// printf("%d%c", hgt[i][0], i == len - 1 ? '\n' : ' ');
for (int i = len; i; i--)
for (int j = 1; j < 20; j++)
if ( i + (1 << (j - 1)) <= len )
hgt[i][j] = min(hgt[i][j - 1], hgt[i + (1 << (j - 1))][j - 1]);
else hgt[i][j] = hgt[i][j - 1];
}
int query(con(int) s, con(int) t) {
if ( s > n || t > n ) exit(-1);
int ss = rnk[s], tt = rnk[t];
if ( ss > tt ) swap(ss, tt);
tt--;
int l = elg2[tt - ss + 1];
return min(hgt[ss][l], hgt[tt - (1 << l) + 1][l]);
}
} tol, tor;

void init() {
elg2[1] = 0;
for (int i = 2; i < N; i++)
elg2[i] = elg2[i >> 1] + 1;
}
int matchR(con(int) s, con(int) t) {
if ( s > n || t > n ) return 0;
return tor.query(s, t);
}
int matchL(con(int) s, con(int) t) {
return tol.query(n - s + 1, n - t + 1);
}
int main() {
// freopen("input.in", "r", stdin);
init();
scanf("%d", &ncas);
while ( ncas-- ) {
scanf("%s", str + 1);
n = (int) strlen(str + 1);
for (int i = 1; i <= n; i++) cntt[i] = cnts[i] = 0;
tor.build(str, n);
reverse(str + 1, str + 1 + n);
tol.build(str, n);
for (int i = 1; i <= n; i++) {
for (int j = i; j + i <= n; j += i) {
int pl = matchL(j, j + i), pr = matchR(j + 1, j + i + 1);
pl = min(pl, i), pr = min(pr, i - 1);
int tmp = pr + pl - i + 1;
if ( tmp > 0 ) {
// printf("%d: start at [%d, %d]\n", i, i - pl + 1, i - pl + tmp);
// printf("%d: end at [%d, %d]\n", i, i - pl + 2 * i, i - pl - 1 + tmp + 2 * i);
cnts[j - pl + 1]++, cnts[j - pl + 1 + tmp]--;
cntt[j - pl + 2 * i]++, cntt[j - pl + tmp + 2 * i]--;
}
}
}
for (int i = 1; i <= n; i++) cnts[i] += cnts[i - 1], cntt[i] += cntt[i - 1];
// for (int i = 1; i <= n; i++)
// printf("start at %d = %d\n", i, cnts[i]);
long long ans = 0;
for (int i = 1; i < n; i++)
ans += 1ll * cnts[i + 1] * cntt[i];
printf("%lld\n", ans);
}
return 0;
}
点击展开/折叠 grid.cpp
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/*Lucky_Glass*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int DIR[9][2] = {
{-1, -1}, {-1, 0}, {-1, 1},
{0, -1}, {0, 0}, {0, 1},
{1, -1}, {1, 0}, {1, 1}
};

inline int rin(int &r) {
int b = 1, c = getchar(); r = 0;
while ( c < '0' || '9' < c ) b = c == '-' ? -1 : b, c = getchar();
while ( '0' <= c && c <= '9' ) r = (r * 10) + (c ^ '0'), c = getchar();
return r *= b;
}

const int N = 1e5 + 10, NP = N * 20, NE = N * 80;
#define con(typ) const typ &

struct Graph {
int head[NP], to[NE], nxt[NE], ncnt, np;
void addEdge(con(int) u, con(int) v) {
int p = ++ncnt, q = ++ncnt;
to[p] = v, nxt[p] = head[u], head[u] = p;
to[q] = u, nxt[q] = head[v], head[v] = q;
}
inline int operator [] (con(int) u) const {return head[u];}
} gr;

struct SPNode {
int x, y, tag;
bool operator < (con(SPNode) p) const {
if ( x == p.x ) return y < p.y;
return x < p.x;
}
} nod[N * 9];

bool isans1;
int ncas, n, m, nc, nnod, itmp, ndfn;
int dfn[NP], low[NP];
bool ptag[NP];
int tmp[2][NP][3], ntmp[2];
pair<int, int> pos[N];

void dfs(con(int) u) {
bool iscut = false;
int cntson = 0;
dfn[u] = low[u] = ++ndfn;
for (int it = gr[u]; it; it = gr.nxt[it]) {
int v = gr.to[it];
if ( dfn[v] ) low[u] = min(low[u], dfn[v]);
else {
cntson++;
dfs(v);
low[u] = min(low[u], low[v]);
if ( low[v] >= dfn[u] )
if ( ptag[u] )
iscut = true;
}
}
if ( iscut )
isans1 |= (u != 1 || cntson > 1);
}
void newTemp() {itmp ^= 1, ntmp[itmp] = 0;}
void setTemp(con(int) l, con(int) r, con(bool) tag) {
int p = ++gr.np;
if ( p >= NP ) exit(0);
dfn[p] = 0, gr.head[p] = 0;
ptag[p] = tag;
tmp[itmp][++ntmp[itmp]][0] = l, tmp[itmp][ntmp[itmp]][1] = r;
tmp[itmp][ntmp[itmp]][2] = p;
}
void arrangeTemp() {
for (int i = 1; i < ntmp[itmp]; i++)
if ( tmp[itmp][i][1] + 1 == tmp[itmp][i + 1][0] )
gr.addEdge(tmp[itmp][i][2], tmp[itmp][i + 1][2]);
if ( !ntmp[0] || !ntmp[1] ) return;
for (int i = 1, jl = 1, jr = 0; i <= ntmp[0]; i++) {
while ( jr < ntmp[1] && tmp[1][jr + 1][0] <= tmp[0][i][1] ) jr++;
while ( jl <= jr && tmp[1][jl][1] < tmp[0][i][0] ) jl++;
for (int j = jl; j <= jr; j++)
gr.addEdge(tmp[0][i][2], tmp[1][j][2]);
}
}

bool bf[N];

bool isNeg() {
if ( 1ll * n * m - nc > 2 ) return false;
if ( 1ll * n * m - nc <= 1 ) return true;
#define id(x, y) ((x) * m - m + (y))
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
bf[id(i, j)] = false;
for (int i = 1; i <= nc; i++)
bf[id(pos[i].first, pos[i].second)] = true;
const int DIR[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
if ( !bf[id(i, j)] )
for (int k = 0; k < 4; k++) {
int x = i + DIR[k][0], y = j + DIR[k][1];
if ( 1 <= x && x <= n && 1 <= y && y <= m && !bf[id(x, y)] )
return true;
}
return false;
#undef id
}
int main() {
rin(ncas);
while ( ncas-- ) {
ndfn = nnod = 0;
gr.np = gr.ncnt = 0;
itmp = 0;
isans1 = false;
//
rin(n), rin(m), rin(nc);
for (int i = 1; i <= nc; i++)
rin(pos[i].first), rin(pos[i].second);
if ( isNeg() ) {printf("-1\n"); continue;}
sort(pos + 1, pos + 1 + nc);
if ( m == 1 ) {
int cnti = 0;
if ( nc ) {
if ( pos[1].first > 1 ) cnti++;
for (int i = 2; i <= nc; i++)
if ( pos[i - 1].first + 1 <= pos[i].first - 1 )
cnti++;
if ( pos[nc].first < n ) cnti++;
} else cnti = 1;
if ( cnti > 1 ) printf("0\n");
else printf("1\n");
continue;
}
if ( n == 1 ) {
int cnti = 0;
if ( nc ) {
if ( pos[1].second > 1 ) cnti++;
for (int i = 2; i <= nc; i++)
if ( pos[i - 1].second + 1 <= pos[i].second - 1 )
cnti++;
if ( pos[nc].second < m ) cnti++;
} else cnti = 1;
if ( cnti > 1 ) printf("0\n");
else printf("1\n");
continue;
}
for (int i = 1, p, q; i <= nc; i++) {
p = pos[i].first, q = pos[i].second;
for (int k = 0; k < 9; k++) {
int x = p + DIR[k][0], y = q + DIR[k][1];
if ( 1 <= x && x <= n && 1 <= y && y <= m ) {
// cerr << "build " << x << ' ' << y << endl;
nod[++nnod].x = x, nod[nnod].y = y, nod[nnod].tag = k == 4;
}
}
}
//
sort(nod + 1, nod + 1 + nnod);
ntmp[0] = ntmp[1] = 0;
if ( nod[1].x > 1 ) newTemp(), setTemp(1, m, false);
int lasl = nod[1].x - 1;
for (int i = 1; i <= nnod; i++) {
if ( lasl < nod[i].x - 1 ) {
newTemp(), setTemp(1, m, false);
arrangeTemp();
}
newTemp();
if ( nod[i].y > 1 ) setTemp(1, nod[i].y - 1, false);
int toi = i; while ( toi < nnod && nod[toi + 1].x == nod[i].x ) toi++;
for (int j = i; j <= toi; j++) {
int toj = j; bool itag = nod[j].tag;
while ( toj < toi && nod[toj + 1].y == nod[j].y )
itag |= nod[++toj].tag;
if ( !itag) setTemp(nod[j].y, nod[j].y, true);
if ( toj < toi && nod[j].y + 1 < nod[toj + 1].y )
setTemp(nod[j].y + 1, nod[toj + 1].y - 1, false);
j = toj;
}
if ( nod[toi].y < m ) setTemp(nod[toi].y + 1, m, false);
arrangeTemp();
lasl = nod[i].x, i = toi;
}
if ( lasl < n ) {
newTemp(), setTemp(1, m, false);
arrangeTemp();
}
dfs(1);
if ( ndfn < gr.np ) printf("0\n");
else if ( isans1 ) printf("1\n");
else printf("2\n");
}
return 0;
}
点击展开/折叠 cyclic.cpp
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
/*Lucky_Glass*/
#include <cstdio>
#include <cstring>
#include <algorithm>

typedef long long llong;
#define con(typ) const typ &
const int M = 1e6 + 10, HMOD = 1000793;

int dv[200], prm[M], mu[M], summu[M];
bool bok[M];
int ndv, nprm;

struct HashTable {
int head[HMOD], nxt[M << 1], val[M << 1], key[M << 1];
int ncnt;
int& operator [] (con(int) _key) {
for (int it = head[_key % HMOD]; it; it = nxt[it])
if ( key[it] == _key )
return val[it];
int p = ++ncnt;
key[p] = _key, nxt[p] = head[_key % HMOD], head[_key % HMOD] = p;
return val[p] = 2e9;
}
} htab;

void init(con(int) vk) {
mu[1] = 1;
for (int i = 2; i < M; ++i) {
if ( !bok[i] ) mu[i] = -1, prm[++nprm] = i;
for (int j = 1; j <= nprm && 1ll * prm[j] * i < M; ++j) {
bok[i * prm[j]] = true;
if ( i % prm[j] == 0 ) break;
mu[i * prm[j]] = -mu[i];
}
}
for (int i = 1; i < M; ++i) summu[i] = summu[i - 1] + mu[i];
for (int i = 1; i <= vk; ++i)
if ( vk % i == 0 && mu[i] )
dv[++ndv] = i;
}
int funSum(con(int) n) {
if ( n < M ) return summu[n];
int &ret = htab[n];
if ( ret != 2e9 ) return ret;
llong tmp = 1;
for (int i = 2; i <= n; ++i) {
int ii = n / (n / i);
tmp -= (ii - i + 1ll) * funSum(n / i);
i = ii;
}
return ret = (int)tmp;
}
llong fun(con(int) n, con(int) m, con(int) vk) {
if ( !n || !m ) return 0ll;
llong ret = 0;
if ( vk == 1 ) {
int las = 0;
for (int i = 1, li = std::min(n, m); i <= li; ++i) {
int ii = std::min(n / (n / i), m / (m / i)), tmp = funSum(ii);
ret += 1ll * (n / i) * (m / i) * (tmp - las);
las = tmp;
i = ii;
}
return ret;
}
if ( vk < ndv ) {
for (int i = 1; i <= vk; ++i)
if ( vk % i == 0 && mu[i] )
ret += mu[i] * fun(m / i, n, i);
} else {
for (int i = 1; i <= ndv; ++i)
if ( vk % dv[i] == 0 )
ret += mu[dv[i]] * fun(m / dv[i], n, dv[i]);
}
return ret;
}
int main() {
int n, m, vk;
scanf("%d%d%d", &n, &m, &vk);
init(vk);
printf("%lld\n", fun(n, m, vk));
return 0;
}

THE END

Thanks for reading!

准备升空 到广阔云漠中
日出穿透我安睡的眼眸
整个世界 所有声音在视线上跳动
尽情弹奏属于我的与众不同

——《世界不静默》 By 双笙

> Link 世界不静默 - Bilibili