[比赛][ICPC2020] 2020ICPC小米网络选拔赛第一场

一、前言

听说今年难得有场邀请赛,而且还是在北京线下,感觉是个大好的机会 —— 毕竟上次参加南昌场的邀请赛直接夺银,感觉状态再好点冲个金也没问题。然而事实告诉我这并不是场一如既往的邀请赛,43 个名额实在少得可怜,而且学校似乎并没有名额限制,以至于清北直接屠榜了,不知道是今年情况特殊还是这场比赛特殊。最后勉为其难写了 5 道题,距离入围依旧遥不可及。

二、比赛概况

1、比赛链接

https://ac.nowcoder.com/acm/contest/7501

2、题目情况

A 题(AC,已填坑):数论 + 动态规划

B 题(已填坑):计算几何 + 最短路

C 题(AC,已填坑):模拟

D 题(AC):图论

E 题:略

F 题:贪心 + 二分答案

G 题:略

H 题:略

I 题(AC):搜索(BFS)+ 图论

J 题(AC):二维前缀和 + 二维差分

K 题:略

3、得分情况

AC 了 A, C, D, I, J 五道,终榜 rank300+。

三、题目分析

A. Intelligent Warehouse

1、题目链接

https://ac.nowcoder.com/acm/problem/211807

2、题面

In MI Intelligent Warehouse, there are nn products, where the i-th product is of size ai. We always need to box producsts into all kinds of containers, and it will be safer and more cost efficient if for any two sizes of products, one is the other's multiple, since there won't be any residual room in one container. So for each boxing we need to choose some products that for any two chosen products, either ai is multiple of aj or aj is multiple of ai. Print the maximum number of products that can be chosen in one boxing.

3、题目大意

给出数列 a,从中选出尽可能多的数,使任意两个数之间存在倍数关系。

4、分析与思路

很早就开了这道题,首先确定了几个事情:

① 数的顺序没有意义,故可以预处理为从小到大排序;

② 任意两个数均有倍数关系,考虑 b 是 a 的倍数,如果 c 是 a 的倍数,则 c 必然也是 a 的倍数,集合 a, b, c 也就满足条件;故对于数列 s,从小到大排序后,任意相邻的两个元素存在倍数关系时,数列的任意两个元素均存在倍数关系;

③ 根据第 ② 条,考虑动态规划毋庸置疑,f[i] 表示 “最大值为 i 时从数列中最多选出的数的个数能够满足条件”。举个例子,对于数列 {1, 2, 3, 4, 8},f[1] = 1, f[2] = 2, f[3] = 2, f[4] = 3, f[8] = 4。

根据上述分析,提供三种算法:

① O(w * sqrt(w)) 枚举约数(TLE)

首先想到的是 O(w * sqrt(w)) 的做法。值域只有 1e7,则可以预处理出某个数是否在数列中,然后枚举数列中每个数的因数是否存在于该数列,存在则转移,交了一发 TLE 了;

然后想到,枚举因数的这个过程存在太多冗余的判断,比如假设 b 是 a 的约数, c 是 b 的约数,则恒存在 f[a] >= f[b] >= f[c],在找到 b 这个 a 的约数并进行 f[a] = max(f[a], f[b] + 1) 的转移后,f[a] = max(f[a], f[c] + 1) 的判断则没有任何意义。

② O(w log w) 枚举倍数(评测机抖动可 A 可 T)

结束后看了眼 A 题的最短代码 300+ byte,属实震惊,细细品读后发现是发现就是直接枚举所有倍数关系。二队其实想到了这个但写 WA 了,分析之后觉得必然会 T 于是也没再写下去。然而我们将这个 O(w log w) 的算法交了若干次竟然每次结果都不同,可能这就是评测机抖动⑧。这也太看脸了。谁又敢把自己已经 T 了的代码再交一次呢?

③ O(w log log w) 线性筛 + 枚举倍数(AC,正解)

在方法 ② 的基础上进行改进 —— 使用 Eratosthenes 筛法(请参见 6.4.1  素数与最大公约数,预处理出所有值域范围内的质数,从 1 至值域最大值枚举,如果当前数 i 存在于数列,则 f[i]++,然后枚举该数的所有质数倍数 j,构成倍数关系即可进行状态转移 f[j] = f[i]。

上述表述还有一定问题,题目并没有说数列中不存在重复数,故我们并非是预处理数是否在数列中,而是出现次数。事后重写的时候被这里卡了无数次,淦。

④ O(w log sqrt(w)) 线性筛 + 枚举约数(AC)

其实枚举约数和枚举倍数可以看做两个相反的过程,对于本题的数据,枚举倍数显然优于枚举约数;加上线性筛后,两者复杂度均可接受。思路和方法 ① 是一样的,相对麻烦一点,略过不提。

5、题解

设Dp[i]表示当前选的所有数都是i的约数且符合题意的情况下能选的数的个数的最大值。最后答案就是所有Dp值中的最大值。

一个非常直观的转移就是用Dp[i]去更新i的所有倍数的Dp值,但是这样复杂度是O(W log W)的,可能会TLE(但实际上跑得和正解一样快)。实际上只用枚举i的素数倍就可以了,因为合数总可以被若干个素数的乘积给凑出来,就没必要再枚举了,复杂度和埃拉托斯特尼筛法的复杂度是一样的。

时间复杂度:O(W log log W)

(出题人给出的题解 pdf 加密了,所以是通过 OCR 复制下来的)

6、代码

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int MAXM = 1e7 + 5;
 5 const int MAXN = 2e5 + 5;
 6 
 7 int n, mx, x, o, in[MAXM], p[MAXM], s[MAXM], f[MAXM], ans;
 8 
 9 int main() {
10     cin >> n;
11     p[1] = 1;
12     for (int i = 1; i <= n; i++)
13         cin >> x, in[x]++, mx = max(x, mx);
14     for (int i = 2; i <= mx; i++) {
15         if (!p[i]) s[o++] = i;
16         for (int j = 0; j < o && i * s[j] <= mx; j++) {
17             p[i * s[j]] = 1;
18             if (i % s[j] == 0) break;
19         }
20     }
21     for (int i = 1; i <= mx; i++) {
22         f[i] += in[i];
23         for (int j = 0; j < o && i * s[j] <= mx; j++)
24             f[i * s[j]] = max(f[i * s[j]], f[i]);
25         ans = max(ans, f[i]);
26     }
27     cout << ans;
28     return 0;
29 }

B. Intelligent Robot

1、题目链接

https://ac.nowcoder.com/acm/problem/211808

2、题面

Given a maze of size n×m, whose lower-left corner is (0, 0)​ while upper-right corner is (n, m). And there are k​ segment walls in the maze. One cannot cross the walls but can touch them or walk along them. Now a MI robot is assigned to go from (X1​, Y1​) to (X2, Y2), as an intelligent robot, MI robot will automatically determine the shortest valid path and go along it. Print the length of the shortest valid path between (X1​, Y1​) and (X2​, Y2​).

3、题目大意

给出 n * m 的坐标系,给出 k 条线段,求出从 (0, 0) 到 (n, m) 的路径,要求不穿过 k 条线段(但允许穿过线段端点或者沿着线段走)。

4、分析与思路

神经刀地想了个方法,其实主要是当时没题写了就正好用这道题练练计算几何的手感,过了个样例就交了,WA 了也没觉得可惜,因为根本没觉得能 A。结束后看分数竟然高达 50,再看题解 —— 这不就是一个意思?今天又看了眼其他队的 AC 代码,才发现自己加了个没用的判断条件,删掉那句话就行了。。。。

我的想法是,墙是起到格挡作用的,原本可以直接走起点终点相连的路径被阻挡了;但是题目允许经过墙的端点,根据最短路原则,存在墙阻挡的情况下,最短路径必然经过墙的端点,那么我们可以把问题重构为所有墙的端点加上起点终点总共 2 * k + 2 个点的完全图上的最短路问题,使用 Dijkstra 算法求解即可。

由于是完全图,边的数量达到了 k ^ 2。显然并不是所有端点相连的线段都是可行的边,如果线段和墙相交,则不能连边,那么就需要对所有边都和 k 面墙进行线段相交的判断,时间复杂度为 O(k ^ 3),算上常数,理论上达到了 (300 * 2) ^ 3 ≈ 2e8 的运算量,可能会 TLE,但也许是数据没有达到这么极限,所以可行。

5、题解

首先有用的点只有O(k)个,然后问题在于判断两点之间是否可以直达,这个就O(k)地枚举每堵墙看有没有挡住线路,具体来说就看两点组成的线段与墙对应的线段师是否严格相交。最后再用Dijkstra 算法求最短路即可。
时间复杂度:O(k ^ 3)

6、代码

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define MAXN 1000005
 5 #define INF 1000005.0
 6 
 7 int n, m, k;
 8 int o, h[MAXN], s1, t1, vis[MAXN], tot;
 9 double dis[MAXN];
10 
11 class Node {
12 public:
13     int o;
14     double d;
15     bool operator < (const Node &o) const {
16         return o.d < d;
17     }
18 };
19 
20 class Edge {
21 public:
22     int v, nxt;
23     double w;
24 } e[MAXN << 1];
25 
26 class Point {
27 public:
28     int x, y;
29     bool operator == (const Point &o) const {
30         return x == o.x && y == o.y;
31     }
32 } p1[MAXN], p2[MAXN], a[MAXN], s, t;
33 
34 priority_queue <Node> q;
35 
36 double mult(Point a, Point b, Point c) {  
37     return (a.x - c.x) * (b.y - c.y) - (b.x - c.x) * (a.y - c.y);  
38 }  
39   
40 bool is(Point a, Point b, Point c, Point d) {  
41     return max(a.x, b.x) < min(c.x, d.x) || max(a.y, b.y) < min(c.y, d.y) ||
42         max(c.x, d.x) < min(a.x, b.x) || max(c.y, d.y) < min(a.y, b.y) ||
43         mult(c, b, a) * mult(b, d, a) <= 0 || mult(a, d, c) * mult(d, b, c) <= 0 ? 0 : 1;
44 }  
45 
46 double getDis(Point a, Point b) {
47     return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
48 }
49 
50 void add(int u, int v) {
51     o++, e[o] = (Edge){v, h[u], getDis(a[u], a[v])}, h[u] = o;
52 }
53 
54 void dijkstra() {
55     for (int i = 1; i <= tot; i++)
56         dis[i] = INF;
57     dis[s1] = 0;
58     q.push((Node){s1, 0});
59     Node o;
60     while (!q.empty()) {
61         o = q.top(); q.pop();
62         if (!vis[o.o]) {
63             vis[o.o] = 1;
64             for (int x = h[o.o]; x; x = e[x].nxt) {
65                 int v = e[x].v;
66                 if (dis[v] > o.d + e[x].w)
67                     dis[v] = o.d + e[x].w, q.push((Node){v, dis[v]});
68             }
69         }
70     }
71 }
72 
73 int main() {
74     cin >> n >> m >> k;
75     for (int i = 1; i <= k; i++)
76         cin >> p1[i].x >> p1[i].y >> p2[i].x >> p2[i].y;
77     cin >> s.x >> s.y >> t.x >> t.y;
78     for (int i = 1; i <= k; i++)
79         a[++tot] = p1[i], a[++tot] = p2[i];
80     a[++tot] = s, a[++tot] = t;
81     s1 = tot - 1, t1 = tot;
82     for (int i = 1; i < tot; i++)
83         for (int j = i + 1; j <= tot; j++) {
84             int nob = 0;
85             for (int l = 1; l <= k; l++) {
86                 if (a[i] == p1[l] && a[j] == p2[l]) continue;
87                 if (is(a[i], a[j], p1[l], p2[l])) {
88                     nob = 1;
89                     break;
90                 }
91             }
92             if (!nob) add(i, j), add(j, i);
93         }
94     dijkstra();
95     cout << fixed << setprecision(4) << dis[t1];
96     return 0;
97 }

C. Smart Browser

1、题目链接

https://ac.nowcoder.com/acm/problem/211809

2、题面

In some social platforms, there are some netizen who like to post "www". Since the string is like the grass, which contains plenty of "/" shapes, so there are also some netizen who post "grass".

As a fast, smart, and convenient browser, MI Browser can recognize this kind of text and show some special effects. Specifically, given a string, MI Browser will determine the number of "/" shapes made by character "w", where "w" itself and the gap between two adjacent "w"s both give one `/\` shape. If still not clear, please turn to sample input/output for more details.

As a MI fan, you want to detect the relationship between the special effects and the number of "/" shapes, so the thing you should do first is to determine the number of "/" shapes by yourself.

3、题目大意

给出一个字符串,求出若干个由连续的 w 组成的子串。

4、分析与思路

签到题。1 个 w(“w”)有 1 个 “/”,2 个 w(“ww”)有 3 个 “/”,因为字符相连处还有一个,所以 n 个 w 有 2 * n - 1 个 w,那么我们找出所有由连续的 “w” 组成的子串,并求 “/” 的总和即可。

开始没太认真看题,结合样例里出现的 “v”,还以为只要是构成了 “/” 就都算,浪费了太多时间。

5、出题人题解

按照题意模拟即可。

6、代码

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3  
 4 #define MAXN 100005
 5  
 6 int l, o = 1, tot, ans;
 7 char a[MAXN];
 8  
 9 int main() {
10     cin >> a + 1;
11     l = strlen(a + 1);
12     while (o <= l) {
13         if (a[o] == 'w') {
14             tot = 0;
15             while (a[o] == 'w') o++, tot++;
16             ans += tot * 2 - 1;
17         }  
18         o++;
19     }
20     cout << ans;
21     return 0;
22 }

D. Router Mesh 

1、题目链接

https://ac.nowcoder.com/acm/problem/211810

2、题面

In a Mesh networking system, there are nn MI Routers, where mm pairs of MI Routers are bidirectionally connected. In order to check the stability of the system, for each MI Router, we should determine the number of connected components after removing it and relative connections from the system. And then assess that if we should add more MI Routers into the system. Print the numbers of connected components in residual system after removing each MI Router.

3、题目大意

给出由 n 个点,m 条边组成的非连通无向图,求出每次删除其中一个点后的连通块个数。

4、分析与思路

5、题解

这题乍一眼看上去像是个时间分治+可撤销并查集(按秩合并)的数据结构题,但复杂度是O(m+nlog2n)的,可能会TLE。
实际上这题有图论做法,只需要对每个点i求出其所在的点双连通分量的个数ci,当然也要求出初始的连通块个数C,那么i的答案就是C+ci - 1。
时间复杂度:O(n+m)

6、代码

 1 #include <bits/stdc++.h>
 2  
 3 using namespace std;
 4 typedef long long ll;
 5 typedef unsigned long long ull;
 6 const int N = 3e5 + 50;
 7 const int NN = 2e5 + 50;
 8 const int M = 1e9 + 7;
 9 int n,m;
10 struct Edge{
11     int u,v,nxt,cut;
12 } e[N*2];
13 int cnt = 1, head[N], dfn[N],low[N];
14 int ans[N];
15 int mx = 0;
16 int clk = 0;
17 #define multi_case
18 #undef multi_case
19 void add_edge(int u,int v) {
20     e[++cnt] = (Edge){u,v,head[u],0};
21     head[u] = cnt;
22     e[++cnt] = (Edge){v,u,head[v],0};
23     head[v] = cnt;
24 }
25 void pre() {
26     cin >> n >> m;
27     for(int i = 1; i <= m; i++) {
28         int v,u;
29         cin >> u >> v;
30         add_edge(u,v);
31     }
32 }
33 void Tarjan(int u,int fa) {
34     dfn[u]=low[u]=++clk;
35     ans[u] = 0;
36     int cnt = 0;
37     if(fa == 0) ans[u] = -1;
38     for(int i=head[u];i;i=e[i].nxt) {
39         int v = e[i].v;
40         if(!dfn[v])
41         {
42             cnt++;
43             Tarjan(v,u);
44             low[u]=min(low[v],low[u]);
45             if(fa == 0) {
46                 ans[u] = cnt-1;
47             }
48             else if(fa != 0 && low[v] >= dfn[u])
49                 ans[u]++;
50         }
51         else if(v != fa)
52             low[u]=min(low[u],dfn[v]);
53     }
54 }
55 void solve(int _case) {
56     int block_cnt = 0;
57     for(int i = 1; i <= n; i++) {
58         if(dfn[i] == 0) {
59             Tarjan(i,0);
60             block_cnt++;
61         }
62     }
63     for(int u = 1; u <= n; u++) {
64  
65         printf("%d%c", block_cnt + ans[u], " 
"[u==n]);
66     }
67 }
68 int main() {
69     ios::sync_with_stdio(false);
70     cin.tie(0);
71     int t;
72     pre();
73 #ifdef multi_case
74     cin >> t;
75 #else
76     t = 1;
77 #endif
78     for (int i = 1; i <= t; i++) {
79         solve(i);
80     }
81     return 0;
82 }

(樊总的代码)

E. Phone Network 

1、题目链接

https://ac.nowcoder.com/acm/problem/211815

2、题面

In M Country, there are n cities and these cities are placed in one line, where the cities are labeled 1, 2, ⋯, n one by one. Citizens in M Country all use MI-phones, and there are totally m network modes among the MI-phones. For better management, MI-phones in i-th city can only use Ai-th mode.

As a MI fan, you want to experience all kind of network modes. So for each i(i∈{1, 2, ⋯, m}), you want to find a shortest subinterval [Li, Ri] in M Country that all network modes from 1 to i are used at least once among the cities whose labels are in interval [Li, Ri], and we denote li as the length of the shortest subinterval. In this problem, you need to determine l1, l2, ..., lm.

3、题目大意

4、分析与思路

5、题解

先考虑维护一个 R[i][l],表示 l 为左端点,包含 1 ~ i 中所有数字的最小右端点。那么当 i 转移到 i + 1 时,不妨设数字 i + 1 的位置从左到右分别为 p1, p2, …, pk,那么可知对于 [p[j - 1] + 1, p[j]]中的 l,其 R[i + 1][l] = max{R[i][l], p[j]},就变成区间求 max 了。特别地,对于 [ p[k] + 1, n] 的这一段 R[i + 1][l],可以令其变成 +∞。

注意到 R[i][1 ~ n] 是单调的,所以我们可以在 [p[j - 1] + 1, p[j]] 中找到 R[i + 1][l] < p[j] 的一段l,这样就变成区间赋值了。然后考虑再维护一个 R[i][l] - l + 1 的值,每次取其中的最小值即为答案。

时间复杂度:O(n log n)

6、代码

F. Design Problemset 

1、题目链接

https://ac.nowcoder.com/acm/problem/211817

2、题面

In xCPC contest, there are totally k​ types of problems. Now you have aia_iai​ problems in type i​, and you want to make some problemsets with these problems.

In your opinion, the number of problems in one problemset must be in interval [L,R]​, and the number of problems in type i​ must be in interval [li,ri].

Now you want to know the maximum number of problemsets you can make, where one problem can be used in no more than one problemset.

3、题目大意

给出 k 类题,要求组出尽可能多套卷子。每类题有总题数,每套卷子题数的最小值和最大值三种属性。每套卷子也有总题数的最小值和最大值限制。

4、分析与思路

5、题解

这题可能有个坑点,就是所有题的 li, ri 的限制与 L, R 的限制冲突,即 ∑ri < L 或者 R < l,这样的话答案自然是 0。
首先一套题的题目个数肯定是越少越好,所以就取 max{L, ∑li} 作为一套题的题目数量 P。然后二分答案,不妨设当前答案为 A,考虑检验其合法性。首先看所有种类的题目是否满足ai ≥ A × li,如果不满足则肯定就不合法,否则每套题还需要 P - ∑li 道题来充数,总共就需要A × (P - ∑li) 道充数题,然后每种题目可以拿出 min{ai - A × l, A × (ri - li)} 道题来充数,看已有的可充数题是否不少于所需充数题即可。
时间复杂度:O(n log w)。

6、代码

G. Tree Protection 

1、题目链接

https://ac.nowcoder.com/acm/problem/211818

2、题面

Given two 1∼n permutations A, B​, you should construct an unrooted tree T​, which satisfies that A​ is a possible topological order of T​ if A1​ is made the root, and that B​ is a possible dfs order of T​ if B1​ is made the root.

Here, a permutation P​ of size n​ is a valid topological order of a rooted tree T​ of size n​ iff that for all edges in T​, the parent nodes are at front of the child nodes in permutation P​.

3、题目大意

4、分析与思路

5、题解

做法很多,标程的做法如下(记 PosA[i] 为 i 在 A 中的出现位置):

puts("YES");
for (int i = 2, cur = B[1]; i <= n; i ++) {
    printf("%d %d
", cur, B[i]);
    if (PosA[B[i]] < PosA[cur])
        cur = B[i];
}

大概是说从左到右枚举排列 B,对于一个 B[i](i > 1),找到 B[1 ~ i - 1] 中在排列 A 中出现位置最靠前的一个并与其连边。
可以验证上述算法运行结果就是一个合法解。

考虑排列 A:对于每一条边 (cur, B[i]),可知谁在排列 A 中更靠前,谁就离 A[1] 更近,也就是说在 A 中靠前的点是靠后的点的父亲。所以当 A[1] 为根时,A 是 T 的一个合法拓扑序。

考虑排列 B:当 B[1] 为根时,对于一个点 B[i],可知要么这个点是叶子节点,要么后面所有的点 B[i + 1 ~ n] 都是这个点的子孙,也就是说每个点的子树都在排列 B 的某个子区间中。所以当 B[1] 为根时,B 是 T的一个合法 dfs 序。

6、代码

H. Grouping 

1、题目链接

https://ac.nowcoder.com/acm/problem/211819

2、题面

Given an integer n and 2n integers a1, a2, ⋯, a2n, you should divide these numbers into n groups and each group contains exactly two integers. Now define the weight of a group as the difference between the two integers in the group, and the weight of a dividing manner as the variance of the n group weights. Determine the expectation value of the dividing manner weight modulo 998244353.

Here, the variance of n numbers a1, a2, ⋯, an equals 1 / n ∑i(a[i] - /a) ^ 2, where /a = 1 / n * ∑i(a[i]).

3、题目大意

4、分析与思路

5、题解

6、代码

I. Walking Machine

1、题目链接

https://ac.nowcoder.com/acm/problem/211820

2、题面

Given a maze of size n×m, where upper left corner is (1,1) and lower right corner is (n,m). For each cell (x,y), there is exactly one character c (c∈{W,A,S,D}) on it, denoting the moving restriction:

* If c=W, you can only go up from this cell, specifically go from (x,y) to (x−1,y)

* If c=A, you can only go left from this cell, specifically go from (x,y) to (x,y−1)

* If c=S, you can only go down from this cell, specifically go from (x,y) to (x+1,y)

* If c=D, you can only go right from this cell, specifically go from (x,y) to (x,y+1)

We say one is out if x<1 or x>n or y<1 or y>m holds, now you should determine the number of cells that one can be out if start walking on it.

3、题目大意

给出一个 n * m 的矩阵,每一个格子有固定且唯一的方向,求从 (1, 1) 到 (n, m) 的路径的长度。

4、分析与思路

签到题 2,暴力搜索,DFS / BFS 均可,fwl 写的,没啥好分析的。

5、题解

把图倒过来建,然后从迷宫外开始 BFS,看能搜到多少个格子即可。时间复杂度:O(nm)

6、代码

 1 #include <bits/stdc++.h>
 2  
 3 using namespace std;
 4 typedef long long ll;
 5 typedef unsigned long long ull;
 6 const int N = 1000 + 5;
 7 const int M = 1e9 + 7;
 8 int n,m;
 9 char s[N][N];
10 int ans[N][N];
11 #define multi_case
12 #undef multi_case
13 bool out(int i,int j) {
14     return i <= 0 || i > n || j <= 0 || j > m;
15 }
16 bool dfs(int x,int y) {
17     if(out(x,y)) return true;
18     if(ans[x][y] == 1) return true;
19     if(ans[x][y] == 0) return false;
20     ans[x][y] = false;
21  
22     int dx = x, dy = y;
23     if(s[x][y] == 'W') dx--;
24     else if(s[x][y] == 'S') dx++;
25     else if(s[x][y] == 'A') dy--;
26     else if(s[x][y] == 'D') dy++;
27     ans[x][y] = dfs(dx,dy);
28     return ans[x][y];
29 }
30 void pre() {
31     cin >> n >> m;
32     for(int i = 1; i <= n; i++) {
33         cin >> (s[i]+1);
34     }
35     for(int i = 1; i <= n; i++) {
36         for(int j = 1; j <= m; j++) {
37             ans[i][j] = -1;
38         }
39     }
40 }
41 void solve(int _case) {
42     for(int i = 1; i <= n; i++) {
43         for(int j = 1; j <= m; j++) {
44             if(ans[i][j] == -1) dfs(i,j);
45         }
46     }
47     int cnt = 0;
48     for(int i = 1; i <= n; i++) {
49         for(int j = 1; j <= m; j++) {
50             if(ans[i][j]) cnt++;
51         }
52     }
53     printf("%d
", cnt);
54 }
55 int main() {
56 //    freopen("data.in","r",stdin);
57 //    freopen("data.out","w",stdout);
58     ios::sync_with_stdio(false);
59     cin.tie(0);
60     int t;
61     pre();
62 #ifdef multi_case
63     cin >> t;
64 #else
65     t = 1;
66 #endif
67     for (int i = 1; i <= t; i++) {
68 //        printf("Case #%d: ",i);
69         solve(i);
70     }
71     return 0;
72 }

(樊总的代码)

J. Matrix Subtraction

1、题目链接

https://ac.nowcoder.com/acm/problem/211822

2、题面

Given a matrix M of size n×m and two integers a,b, determine weither it is possible to make all entrys of M zero by repeatedly choosing a×b submatrices and reduce the values in the chosen matrices by 1. If possible, print "^_^" in one line, or print "QAQ" in one line.

3、题目大意

给出一个 n * m 的矩阵,每次对一个 a * b 矩阵的每个元素减 1,求能够将矩阵的元素全部变成 0。

4、分析与思路

二维前缀和 + 二维差分

5、题解

考虑 M(1, 1) 只能被 (1,1) - (a,b) 的子矩阵处理,所以 (1,1) - (a,b) 的选择次数是确定的,同理可以求出 (1, 2) - (a, b + 1) 以及其他所有 a x b 的子矩阵的选择次数。
记 C[i][j] 为 (i, j) - (i + a - 1, j + b - 1) 的选择次数,可知有:

C[i][j] = M[i][j] - ∑u∑v(C[i - u][j - v])

这个用二维前缀和 + 二维差分就可以在 O(1) 的时间内算出来。最后看是否所有的 C[i][j] 都为非负以及 M 是否恰好会变成全 0 即可。

时间复杂度:O(∑nm)

6、代码

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define fi first
  4 #define se second
  5 #define all(x) (x).begin(),(x).end()
  6 #define mp make_pair
  7 #define pb push_back
  8 #define input_fast std::ios::sync_with_stdio(false);std::cin.tie(0)
  9 #define local freopen("in.text","r",stdin)
 10 #define pi acos(-1)
 11 const int mo=1e9+7;
 12 typedef long long ll;
 13 typedef pair<int,int> pii;
 14 typedef vector<int> vi;
 15 ll qp(ll a,ll b){if(a==0) return 0;ll res=1;a%=mo;for(;b;b>>=1){if(b&1)res=res*a%mo;a=a*a%mo;}return res;}
 16 template<class T> bool read(T & ret)//ll,int,double,float,+/-
 17 {
 18     char c;int sgn;T bit=0.1;if(c=getchar(),c==EOF) return 0;while(c!='-' && c!='.' && (c<'0' || c>'9')) c=getchar();sgn=(c=='-')?-1:1;ret=(c=='-')?0:(c-'0');while(c=getchar(),c>='0' && c<='9') ret=ret*10+(c-'0');if(c==' '||c=='
') {ret*=sgn;return 1;}while(c=getchar(),c>='0' && c<='9') ret+=(c-'0')*bit,bit/=10;ret*=sgn;return 1;
 19 }
 20  
 21 int a,b,n,m;
 22 ll g[1010][1010];
 23 ll d[1010][1010];
 24 ll sum[1010][1010];
 25  
 26 void add(int x1,int y1,int x2,int y2,ll c)
 27 {
 28     d[x1][y1]+=c;
 29     d[x2+1][y1]-=c;
 30     d[x1][y2+1]-=c;
 31     d[x2+1][y2+1]+=c;
 32 }
 33  
 34 ll get(int x,int y)
 35 {
 36     return sum[x-1][y]+sum[x][y-1]-sum[x-1][y-1]+d[x][y];
 37 }
 38 void solve()
 39 {
 40     memset(sum,0,sizeof sum);
 41     memset(d,0,sizeof d);
 42     scanf("%d%d%d%d",&n,&m,&a,&b);
 43     for(int i=1;i<=n;++i)
 44     {
 45         for(int j=1;j<=m;j++)
 46         scanf("%lld",&g[i][j]);
 47     }
 48     int ok=1;
 49     for(int i=1;i+a-1<=n;++i)
 50     {
 51         for(int j=1;j+b-1<=m;++j)
 52         {
 53             ll now=g[i][j];
 54             ll pre=get(i,j);
 55              
 56              
 57             sum[i][j]=pre;
 58 /*         
 59                 for(int i=1;i<=n;++i)
 60     {
 61         for(int j=1;j<=m;++j)
 62         {
 63             printf("%d ",sum[i][j]);
 64         }
 65         puts("");
 66     }
 67             */
 68             if(pre==now){
 69                 sum[i][j]=pre;
 70                 continue;
 71             }
 72             else if(pre>now) {
 73                 ok=0;break;
 74             }
 75             else {
 76                 ll tt=now-pre;
 77                 add(i,j,i+a-1,j+b-1,tt);
 78                 sum[i][j] = get(i,j);
 79             }
 80              
 81 //          cout<<"fuck:"<<endl;
 82              
 83      
 84         }
 85          
 86         for(int j=m-b+1;j<=m;++j){
 87             sum[i][j]=get(i,j);
 88             if(sum[i][j]!=g[i][j]) {
 89                 ok=0;break;
 90             }
 91         }
 92         if(!ok) break;
 93     }
 94     for(int i=n-a+1;i<=n;++i)
 95     {
 96      
 97         for(int j=1;j<=m;++j)
 98         {
 99             sum[i][j]=get(i,j);
100             if(sum[i][j]!=g[i][j]) {
101                 ok=0;
102                 break;
103             }
104              
105         }
106         if(!ok) break;
107     }
108 /*  for(int i=1;i<=n;++i)
109     {
110         for(int j=1;j<=m;++j)
111         {
112             printf("%d ",sum[i][j]);
113         }
114         puts("");
115     }
116     */
117     if(!ok) puts("QAQ");
118     else puts("^_^");
119 }
120  
121 signed main(){
122      
123     int t;
124     cin>>t;
125     while(t--)
126     {
127         solve();
128     }
129      
130     return 0;
131 }

(lrj 的代码)

K. Sqrt Approaching

1、题目链接

https://ac.nowcoder.com/acm/problem/211824

2、题面

Given three positive integers A, B, n, where n is NOT a perfect square number, you should find two positive integers C, D satisfying (BC - AD)(C - D√n) < 0.
You can assume that solution always exists, and you should print any one of them.

3、题目大意

4、分析与思路

5、题解

这个题的本质就是:给定一个分数 X(X = A / B),要求找到另一个分数 Y(Y = C / D),使得 Y 在 X 与 √n 之间。

一个直观的思路是把 A, B 扩大若干倍,比如就往后面加零直到长度为 99999,得到 C, D,然后再根据 A ^ 2和 n * B ^ 2 的大小关系来给 C, D 进行微调,但这样是会 WA 的,如果输入的 A, B, n 满足 |A ^ 2 - n * B ^ 2| = 1,那么有 |A - √n| = 1 / B * (A + √n * B),这样的话误差就是 10 ^ -199980 的级别,然而一般的微调都会产生10^ -10000 的浮动,就算套上二分也很难达到10 ^ -199980 的精度。

于是我们想要一个通解,标程的做法是:

C = (n + 1) * A + 2 * n * B, D = 2 * A + (n+1) * B

可以验证满足题目要求的条件。易证从略。

ps.这个题是怎么出出来的?首先本人从某个地方看到了一个题:

是否存在一组正整数n, m,使得 (3 + 5√2) ^ n = (5 + 3√2) ^ m?

这题本人一眼看上去就是不存在,但想了很久不会证,就寻思写个程序打表找规律,这里不妨把 (a + b√2) ^ n 表示成 A ^ n + B ^ n√2,然后发现 An / Bn 会越来越趋近于 √2,并且似乎对于所有的 a, b 都有这个性质,本人就尝试证明了一下,进而发现对于其他的根号也有类似的性质,然后就想到出这样一个根号数值逼近的题了。

6、代码

原文地址:https://www.cnblogs.com/jinkun113/p/13874694.html