“科大讯飞杯”第18届上海大学程序设计联赛春季赛暨高校网络友谊赛题解。
        共12题,5小时,难度不高的比赛。

A、组队比赛

        给定四个整数,两两分组,最小化组间差距。一共就3种情况,算一遍即可。签到题。

1
2
3
4
5
6
7
8
9
10
11
12
#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
int main()
{
int a, b, c, d;
scanf("%d%d%d%d", &a, &b, &c, &d);
printf("%d", min(min(abs(a + c - b - d), abs(a + b - c - d)), abs(a + d - b - c)));
// PAUSE;
return 0;
}

B、每日一报

        多元排序,写好排序函数,一遍sort即过。

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
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
struct Node
{
int date;
int num;
double tw;
} op[500];
bool cmp(Node a, Node b)
{
if (a.date != b.date) return a.date > b.date;
if (a.tw != b.tw) return a.tw > b.tw;
return a.num < b.num;
}
int main()
{
int n;
scanf("%d", &n);
int s = 1;
for (int i = 1; i <= n; i++) {
scanf("%d%d%lf", &op[s].date, &op[s].num, &op[s].tw);
if (op[s].tw >= 38.0) ++s;
}
sort(op + 1, op + s, cmp);
printf("%d\n", s - 1);
for (int i = 1; i < s; i++) {
printf("%d %d %.1lf\n", op[i].date, op[i].num, op[i].tw);
}
// PAUSE;
return 0;
}

C、最长非公共子序列

        当两者长度不等时,答案就是较长的那一个串。当长度相同时,考虑两个串是否相等,若相等,则不存在符合题意的串,否则就是两者其中之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
char s1[5005], s2[5005];
int l1, l2;
int main()
{
scanf("%s%s", s1, s2);
l1 = strlen(s1), l2 = strlen(s2);
if (l1 != l2) {
printf("%d", max(l1, l2));
} else {
if (strcmp(s1, s2) == 0)
printf("-1");
else
printf("%d", l1);
}
// PAUSE;
return 0;
}

D、最大字符集

        按长度讨论。对于$n$为1、2时的情况很容易求出答案,对于$n$更大的情况,考虑形如$01\cdots 10$(或$10\cdots 01$)的串,它们必然不会出现某一个串是另一串子串的问题,并且长度任意选(除了长度为1的),因此其余的答案也容易得到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <cstdio>
int main()
{
int n;
scanf("%d", &n);
if (n == 1) {
printf("1\n0");
} else if (n == 2) {
printf("2\n0\n11");
} else {
printf("%d\n", n - 1);
for (int i = 2; i <= n; i++) {
printf("0");
for (int j = 1; j <= i - 2; j++) printf("1");
printf("0\n");
}
}
// PAUSE;
return 0;
}

E、美味的序列

        正着反着都来一遍答案就有了,全场第二简单题。

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
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int op[100005], n;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", op + i);
long long ans = 0, s = 0;
for (int i = 1; i <= n; i++) {
s += op[i] - i + 1;
}
ans = s;
s = 0;
reverse(op + 1, op + n + 1);
for (int i = 1; i <= n; i++) {
s += op[i] - i + 1;
}
ans = max(ans, s);
printf("%lld", ans);
// PAUSE;
return 0;
}

F、日期小助手

        模拟题,需要考虑闰年,还有考虑一些英语问题。总而言之就是没有多少思维性,纯肝向的题目。
        最简单的方式是打一个表,然后在里面直接找出日期。

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
#include <cstdio>
#include <cstring>
const char* MQ[] = {"2000514", "2001513", "2002512", "2003511", "2004509", "2005508", "2006514", "2007513", "2008511", "2009510", "2010509", "2011508", "2012513", "2013512", "2014511", "2015510", "2016508", "2017514", "2018513", "2019512", "2020510", "2021509", "2022508", "2023514", "2024512", "2025511", "2026510", "2027509", "2028514", "2029513", "2030512", "2031511", "2032509", "2033508", "2034514", "2035513", "2036511", "2037510", "2038509", "2039508", "2040513", "2041512", "2042511", "2043510", "2044508", "2045514", "2046513", "2047512", "2048510", "2049509", "2050508", "2051514", "2052512", "2053511", "2054510", "2055509", "2056514", "2057513", "2058512", "2059511", "2060509", "2061508", "2062514", "2063513", "2064511", "2065510", "2066509", "2067508", "2068513", "2069512", "2070511", "2071510", "2072508", "2073514", "2074513", "2075512", "2076510", "2077509", "2078508", "2079514", "2080512", "2081511", "2082510", "2083509", "2084514", "2085513", "2086512", "2087511", "2088509", "2089508", "2090514", "2091513", "2092511", "2093510", "2094509", "2095508", "2096513", "2097512", "2098511", "2099510", "2100509", "2101508", "2102514", "2103513", "2104511", "2105510"};
const char* FQ[] = {"2000618", "2001617", "2002616", "2003615", "2004620", "2005619", "2006618", "2007617", "2008615", "2009621", "2010620", "2011619", "2012617", "2013616", "2014615", "2015621", "2016619", "2017618", "2018617", "2019616", "2020621", "2021620", "2022619", "2023618", "2024616", "2025615", "2026621", "2027620", "2028618", "2029617", "2030616", "2031615", "2032620", "2033619", "2034618", "2035617", "2036615", "2037621", "2038620", "2039619", "2040617", "2041616", "2042615", "2043621", "2044619", "2045618", "2046617", "2047616", "2048621", "2049620", "2050619", "2051618", "2052616", "2053615", "2054621", "2055620", "2056618", "2057617", "2058616", "2059615", "2060620", "2061619", "2062618", "2063617", "2064615", "2065621", "2066620", "2067619", "2068617", "2069616", "2070615", "2071621", "2072619", "2073618", "2074617", "2075616", "2076621", "2077620", "2078619", "2079618", "2080616", "2081615", "2082621", "2083620", "2084618", "2085617", "2086616", "2087615", "2088620", "2089619", "2090618", "2091617", "2092615", "2093621", "2094620", "2095619", "2096617", "2097616", "2098615", "2099621", "2100620", "2101619", "2102618", "2103617", "2104615", "2105621"};
struct Date
{
int y, m, d;
bool operator<(Date p)
{
if (p.y != y) return y < p.y;
if (p.m != m) return m < p.m;
return d < p.d;
}
} mq, fq, now;
Date convert(const char* s)
{
Date p;
p.y = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + s[3] - '0';
p.m = s[4] - '0';
p.d = (s[5] - '0') * 10 + s[6] - '0';
return p;
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d%d", &now.y, &now.m, &now.d);
for (int j = 0; j < 106; j++) {
Date s = convert(MQ[j]);
// printf("%d %d %d\n", s.y, s.m, s.d);
if (now < s) {
mq = s;
break;
}
}
for (int j = 0; j < 106; j++) {
Date s = convert(FQ[j]);
if (now < s) {
fq = s;
break;
}
}
Date s;
if (fq < mq) {
printf("Father's Day: June %d", fq.d);
s = fq;
} else {
printf("Mother's Day: May %d", mq.d);
s = mq;
}

if (s.d == 11 || s.d == 12 || s.d == 13)
printf("th");
else if (s.d % 10 == 1)
printf("st");
else if (s.d % 10 == 2)
printf("nd");
else if (s.d % 10 == 3)
printf("rd");
else
printf("th");
printf(", %d\n", s.y);
}
// PAUSE;
return 0;
}


难题分割线(雾

G、血压游戏

        有很多做法,一种很优雅的做法是使用虚树配合DP。
        显然只有同一个深度的节点会互相干扰,因此我们只需要按深度讨论,找出同一个深度的节点,建立虚树,然后在上面DP,DP方程很好列。

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
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <set>
#include <vector>
#define N 200005
struct Edge
{
int next, to;
} edge[N << 1], es[N << 1];
using namespace std;
int n, root, num[N], head[N], cnt = 1, gr[N][21], dep[N], sta[N], top, dfn[N], DFN, hs[N], cn = 1;
set<int> tmp;
vector<int> vec[N];
inline void add(int x, int y)
{
edge[cnt].next = head[x], edge[cnt].to = y, head[x] = cnt++;
}
inline void adde(int x, int y)
{
tmp.insert(x), tmp.insert(y), es[cn].next = hs[x], es[cn].to = y, hs[x] = cn++;
}
void DFS(int x)
{
dfn[x] = ++DFN;
for (int i = head[x]; i; i = edge[i].next) {
if (dep[edge[i].to]) continue;
dep[edge[i].to] = dep[x] + 1, gr[edge[i].to][0] = x;
for (int j = 1; j <= 20; j++) gr[edge[i].to][j] = gr[gr[edge[i].to][j - 1]][j - 1];
DFS(edge[i].to);
}
}
inline int LCA(int x, int y)
{
if (dep[x] < dep[y]) swap(x, y);
for (int i = 20; i >= 0; i--) {
if (dep[gr[x][i]] > dep[y]) x = gr[x][i];
}
if (x == y) return x;
for (int i = 20; i >= 0; i--) {
if (gr[x][i] != gr[y][i]) x = gr[x][i], y = gr[y][i];
}
return gr[x][0];
}
inline void insert(int x)
{
if (top <= 1)
sta[++top] = x;
else {
int lca = LCA(sta[top], x);
if (lca == sta[top])
sta[++top] = x;
else {
while (top > 1 && dfn[lca] <= dfn[sta[top - 1]]) adde(sta[top - 1], sta[top]), --top;
if (lca != sta[top]) adde(lca, sta[top]), sta[top] = lca;
sta[++top] = x;
}
}
}
long long DP(int x)
{
if (hs[x] == 0 && x != root) return num[x];
long long dp = 0;
for (int i = hs[x]; i; i = es[i].next) {
long long ch = DP(es[i].to);
if (ch) ch = max(1ll, ch - (dep[es[i].to] - dep[x]));
dp += ch;
}
return dp;
}
inline int cmp(int x, int y)
{
return dfn[x] < dfn[y];
}
int main()
{
long long ans = 0;
scanf("%d%d", &n, &root);
for (int i = 1; i <= n; i++) scanf("%d", num + i);
for (int i = 1, x, y; i < n; i++) scanf("%d%d", &x, &y), add(x, y), add(y, x);
dep[root] = 1, DFS(root);
for (int i = 1; i <= n; i++) vec[dep[i]].push_back(i);
for (int i = 1; i <= n; i++) sort(vec[i].begin(), vec[i].end(), cmp);
ans = num[root] > 1 ? num[root] - 1 : num[root];
for (int i = 2; i <= n; i++) {
sta[top = 1] = root, tmp.insert(root);
for (int j = 0; j < vec[i].size(); j++) insert(vec[i][j]);
while (top > 1) adde(sta[top - 1], sta[top]), --top;
long long dp = DP(root);
ans += dp > 1 ? dp - 1 : dp;
for (set<int>::iterator it = tmp.begin(); it != tmp.end(); it++) hs[*it] = 0;
cn = 1, tmp.clear();
}
printf("%lld", ans);
// PAUSE;
return 0;
}

H、纸牌游戏

        首先需要知道十进制整数能被3整除的充要条件是该整数的各个位数字相加的和能被3整除,问题转化为了数字的和。然后考虑贪心。
        贪心的方法就是从9到0依次遍历,枚举每一个数字能够取的次数,然后判断剩下的数字能否构造出符合题意的数。现在的问题就是如何进行判定。
        由于枚举的复杂度就是$O(n)$的,判定这一步的复杂度不能到$O(n)$。现在假定数字$i$出现的次数为$a_i$,考虑给定的数字能否找出一种方案,使得选$k$个数,它们的和模3为$p$。
        事实上,0到9这10个数可以分为3类,一类模3为0,另两类模3为1和2,我们不妨只考虑三类数的数量,记为$b_0,b_1,b_2$,显然$b_0=a_0+a_3+a_6+a_9$,$b_1,b_2$同理可以推得。
        假设存在一种方案符合题意,这个方案中三类数分别取了$x,y,z$个,那么可以列出下面的方程:

        枚举$y$与$z$模3的性质(共9种情况),对于其中满足方程的数对$(m,n)$,有:

        这样可以将$y,z$写成如下形式:

        根据$b_1,b_2$,我们很容易推出$k_1,k_2$的取值范围,进而得到$y,z$的取值范围,从而得到$x$的范围。然后判断这一个范围是否满足$b_0$的限制即可。
        这样我们就得到了$O(1)$的判定方法,本题迎刃而解。

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
#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
inline int check(int a, int b, int c, int k, int sv)
{
if (a + b + c < k) return 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if ((i + 2 * j) % 3 != sv) continue;
if (b - i < 0 || c - j < 0) continue;
int k1 = (b - i) / 3, k2 = (c - j) / 3;
double down = (k - i - j - a) / 3.0;
double up = (k - i - j) / 3.0;
int pd = ceil(down), pu = floor(up);
if (pu < pd) continue;
if (k1 + k2 < pd || pu < 0) continue;
return 1;
}
}
return 0;
}
char op[1000005], ans[1000005];
int num[20], k, n0, n1, n2, at;
void DFS(int x, int sv)
{
if (x == -1) return;
for (int i = num[x]; i >= 0; i--) {
if (i > k) continue;
if (x % 3 == 0) {
n0 -= num[x];
} else if (x % 3 == 1) {
n1 -= num[x];
} else {
n2 -= num[x];
}
if (check(n0, n1, n2, k - i, ((sv - x % 3 * i) % 3 + 3) % 3)) {
for (int j = 1; j <= i; j++) ans[at++] = x + '0';
k -= i;
DFS(x - 1, ((sv - x % 3 * i) % 3 + 3) % 3);
return;
}
if (x % 3 == 0) {
n0 += num[x];
} else if (x % 3 == 1) {
n1 += num[x];
} else {
n2 += num[x];
}
}
}
int main()
{
int t;
// printf("%d\n", check(0, 3, 0, 1, 0));
scanf("%d", &t);
for (int i = 1; i <= t; i++) {
scanf("%s%d", op, &k);
for (int j = 0; j < 10; j++) num[j] = 0;
for (int j = 0; op[j]; j++) ++num[op[j] - '0'];
n0 = num[0] + num[3] + num[6] + num[9];
n1 = num[1] + num[4] + num[7];
n2 = num[2] + num[5] + num[8];
at = 0, DFS(9, 0);
if (at == 0 || (ans[0] == '0' && at != 1)) {
printf("-1");
} else {
for (int j = 0; j < at; j++) printf("%c", ans[j]);
}
printf("\n");
}
// PAUSE;
return 0;
}

I、古老的打字机

        整场比赛我最喜欢的一道题,比较有思维含量。
        要解决本题,需要熟悉AC自动机的相关内容。回想AC自动机中是如何求很多串在一个串内各自的出现次数的:建出AC自动机后,把这一个串丢到AC自动机上跑,记录每一个状态被访问的次数,最后在fail树上一遍DFS统计一波。
        由于需要最后再fail树上统计,我们不妨直接一开始就进行一遍DFS,将每一个串的权值下放到其在fail树上的子树节点上,这样遍历一遍AC自动机,串的价值就是走过状态权值之和。
        然后本题可以转化为在AC自动机上进行期望DP的问题。DP转移方程还是比较好列的。但是到这一步本题还没有完全解决,这是因为退格操作的存在。退格操作使得我们可能回退一个字符,这样就需要减去权值,对于DP来说需要记额外状态,这显然不可取。
        但是可以退一步想:无论退不退格,在哪里退格,最后得到就是一个串,只不过长度不定。凭借这个思路,可以得到一个方法:对于固定长度为$p$的串,禁止使用退格操作(下一步填每一个字母的概率都是$\frac {1}{26}$),得到期望$e_p$,如果再记算上退格操作得到的长度为$p$的串概率为$f_i$,那么答案就是:

        问题转化为求$f$,然而这并不是一个简单的二项分布问题,求$f$仍然需要DP,比较常规。
        本题同时考察了AC自动机(含fail树),期望概率DP以及一丁点数论(乘法逆元),兼具思维性,给出题人点赞。

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
#include <bits/stdc++.h>
#define N 1005
#define MOD 1000000007
using namespace std;
char str[N], op[N];
int vis[N], to[N], ct = 1, fail[N], tr[N][26], cnt = 1, v[N], n, m, fa[N], head[N], dp[N][N];
int inv26, inv27, f[N][N];
struct Edge
{
int next, to;
} edge[N];

inline void add(int x, int y)
{
edge[cnt].to = y, edge[cnt].next = head[x], head[x] = cnt++;
}
inline int qPow(int x, int y = MOD - 2)
{
int ans = 1, sta = x;
while (y) {
if (y & 1) ans = 1ll * sta * ans % MOD;
sta = 1ll * sta * sta % MOD, y >>= 1;
}
return ans;
}
queue<int> que;

void insert(const char* p, int ps)
{
int i = 0, now = 1;
while (p[i]) {
if (tr[now][p[i] - 'a'] == 0) tr[now][p[i] - 'a'] = ++ct;
now = tr[now][p[i] - 'a'], i++;
}
v[now] += ps;
}

void DFS(int x, int s)
{
v[x] += s;
for (int i = head[x]; i; i = edge[i].next) DFS(edge[i].to, v[x]);
}
int DP(int x, int c)
{
if (c == 0) return dp[x][c] = v[x];
if (~dp[x][c]) return dp[x][c];
dp[x][c] = 0;
for (int i = 0; i < 26; i++) {
dp[x][c] = (dp[x][c] + 1ll * (v[x] + DP(tr[x][i], c - 1)) * inv26 % MOD) % MOD;
}
return dp[x][c];
}
int main()
{
scanf("%d%d", &n, &m), inv26 = qPow(26), inv27 = qPow(27);
for (int i = 1, x; i <= n; i++) scanf("%s%d", op, &x), insert(op, x);
for (int i = 0; i < 26; i++) tr[0][i] = 1;
que.push(1);
while (!que.empty()) {
int p = que.front();
que.pop();
for (int i = 0; i < 26; i++) {
if (tr[p][i])
fail[tr[p][i]] = tr[fail[p]][i], que.push(tr[p][i]);
else
tr[p][i] = tr[fail[p]][i];
}
}
for (int i = 2; i <= ct; i++) add(fail[i], i);
DFS(1, 0), memset(dp, -1, sizeof(dp)), f[0][0] = 1;
for (int i = 1; i <= m; i++) {
f[i][0] = 1ll * (f[i - 1][1] + f[i - 1][0]) * inv27 % MOD;
f[i][m] = 1ll * f[i - 1][m - 1] * (1 - inv27) % MOD;
for (int j = 1; j < m; j++) {
f[i][j] = (1ll * f[i - 1][j - 1] * (1 - inv27) % MOD + 1ll * f[i - 1][j + 1] * inv27 % MOD) % MOD;
}
}
int ans = 0;
for (int i = 0; i <= m; i++) ans = (ans + 1ll * f[m][i] * DP(1, i) % MOD) % MOD;
ans = 1ll * ans * qPow(27, m) % MOD;
printf("%d", (ans + MOD) % MOD);
return 0;
}

J、能到达吗

        一个搜索,不过数据范围过大。
        由于障碍物很少,但是图很大,可以将一大片的点合并,然后用并查集合并求解。
        合并节点的方法是对于存在障碍物的一行,将这一行按障碍物分开,对于连续的没有障碍物的若干行,将它们合并成一个。这样做能将块的复杂度降到$O(k)$。
        本题细节较多。

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
#include <bits/stdc++.h>
#define N 4000005
#define MOD 1000000007
using namespace std;
int n, m, k, fa[N];
vector<int> vec[N >> 2], up, down, l, r;
vector<long long> num;
set<int> ssp;
int inv2 = 500000004;
int ff(int x)
{
return fa[x] == x ? x : fa[x] = ff(fa[x]);
}
inline void merge(int x, int y)
{
if (ff(x) == ff(y)) return;
int f1 = ff(x), f2 = ff(y);
fa[f1] = f2, num[f2] += num[f1], num[f2] %= MOD;
}
inline int les(int x, int y)
{
return up[y] > down[x] + 1 || (up[y] == down[x] + 1 && l[y] > r[x]);
}
inline int ok(int x, int y)
{
return up[y] == down[x] + 1 && l[y] <= r[x] && r[y] >= l[x];
}
int findL(int x, int l, int r) //(,]
{
if (l >= r) return -1;
if (r == l + 1) return ok(x, r) ? r : -1;
int mid = (l + r) >> 1;
if (les(x, mid) || ok(x, mid)) return findL(x, l, mid);
return findL(x, mid, r);
}
int findR(int x, int l, int r) //[,)
{
if (l >= r) return -1;
if (r == l + 1) return ok(x, l) ? l : -1;
int mid = (l + r) >> 1;
if (!les(x, mid)) return findR(x, mid, r);
return findR(x, l, mid);
}
int main()
{
int t;
scanf("%d", &t);
while (t--) {
scanf("%d%d%d", &n, &m, &k);
up.clear(), down.clear(), l.clear(), r.clear(), num.clear();
for (int i = 1, x, y; i <= k; i++) scanf("%d%d", &x, &y), vec[x].push_back(y), ssp.insert(x);
int now = 1;
for (int i : ssp) {
if (i > now) {
num.push_back(1ll * (i - now) * m % MOD);
up.push_back(now), down.push_back(i - 1), l.push_back(1), r.push_back(m);
}
sort(vec[i].begin(), vec[i].end());
int sv = 1;
for (int j = 0; j < vec[i].size(); j++) {
if (vec[i][j] - sv > 0) {
up.push_back(i), down.push_back(i);
l.push_back(sv), r.push_back(vec[i][j] - 1), num.push_back(vec[i][j] - sv);
}
sv = vec[i][j] + 1;
}
if (m - sv + 1 > 0) {
up.push_back(i), down.push_back(i);
l.push_back(sv), r.push_back(m), num.push_back(m - sv + 1);
}
now = i + 1;
}
if (n - now + 1 > 0) {
up.push_back(now), down.push_back(n);
l.push_back(1), r.push_back(m), num.push_back(1ll * (n - now + 1) * m % MOD);
}
long long ans = 0;
for (int i = 0; i < up.size(); i++) fa[i] = i;
for (int i = 0; i < up.size(); i++) {
int l = findL(i, i, up.size() - 1), r = findR(i, i + 1, up.size());
if (l != -1)
for (int j = l; j <= r; j++) merge(i, j);
}
for (int i = 0; i < up.size(); i++) {
if (fa[i] == i) ans += 1ll * num[i] * (num[i] + 1) % MOD * inv2 % MOD, ans %= MOD;
}
printf("%lld\n", ans);
for (int i : ssp) vec[i].clear();
ssp.clear();
}
return 0;
}

K、迷宫

        这题出的还是很有技术含量的。
        可以预见的是只要$d$不是0,那么合理使用穿越一定不会使答案更差。于是我们对$d=0$特判,对$d>0$的情况默认都使用一次穿越。
        下一步从起点来一遍BFS,再从终点来一遍BFS,对于某一个点$p$,前者求出的距离记为$dis(p,1)$,后者记为$dis(p,2)$。
        问题转化为找两个点$p,q$(两个点可以互相穿越),使得$dis(p,1)+1+dis(q,2)$尽可能小。
        强行遍历显然不可取。考虑一下切比雪夫距离,正好是一个正方形的范围,这样问题转化为在一个正方形区域中求最小值问题,即一个二维RMQ问题。二维RMQ问题有很多方法解决,但是这里不能用二维ST表,因为它空间复杂度是$O(nmlognlogm)$,会炸空间。用阉割版(只能求正方形范围)的ST表(时空复杂度为$O(nmlog(\min(n,m)))$)或许可以,不过不推荐。最好的做法是使用单调队列,可以在时空复杂度$O(nm)$下解决这个问题。

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
#include <cstdio>
#include <iostream>
#include <queue>
#define N 2005
#define inf 0x3f3f3f3f
using namespace std;
int n, m, d, bx, by, ex, ey, dis[N][N][2], minn[N][N], minn2[N][N];
char op[N][N];
const int X[] = {-1, 0, 1, 0}, Y[] = {0, 1, 0, -1};
queue<pair<int, int>> que;
pair<int, int> fr[N][N][2];
deque<int> dq;
inline void BFS(int x, int y, int at)
{
que.push(make_pair(x, y)), dis[x][y][at] = 0;
while (!que.empty()) {
pair<int, int> now = que.front();
que.pop();
for (int i = 0, xx, yy; i < 4; i++) {
xx = now.first + X[i], yy = now.second + Y[i];
if (xx < 0 || xx >= n || yy < 0 || yy >= m || op[xx][yy] == 'X') continue;
if (dis[xx][yy][at] > dis[now.first][now.second][at] + 1) {
dis[xx][yy][at] = dis[now.first][now.second][at] + 1;
fr[xx][yy][at] = now;
que.push(make_pair(xx, yy));
}
}
}
}

void FDFS(int x, int y)
{
if (x == bx && y == by) {
printf("%d %d\n", bx, by);
} else {
FDFS(fr[x][y][0].first, fr[x][y][0].second);
printf("%d %d\n", x, y);
}
}
void DFS(int x, int y)
{
if (x == ex && y == ey) {
printf("%d %d\n", x, y);
} else {
printf("%d %d\n", x, y);
DFS(fr[x][y][1].first, fr[x][y][1].second);
}
}
inline void addX(int x, int y)
{
while (!dq.empty() && dis[x][dq.back()][1] > dis[x][y][1]) dq.pop_back();
dq.push_back(y);
}
inline void addY(int y, int x)
{
while (!dq.empty() && minn[dq.back()][y] > minn[x][y]) dq.pop_back();
dq.push_back(x);
}
inline int getQ(int p)
{
while (!dq.empty() && dq.front() < p) dq.pop_front();
return dq.front();
}
int main()
{
scanf("%d%d%d", &n, &m, &d);
for (int i = 0; i < n; i++) scanf("%s", op + i);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (op[i][j] == 'S') bx = i, by = j;
if (op[i][j] == 'T') ex = i, ey = j;
dis[i][j][0] = dis[i][j][1] = 0x3f3f3f3f;
}
}
BFS(bx, by, 0), BFS(ex, ey, 1);
if (d == 0) {
if (dis[ex][ey][0] >= inf) {
printf("-1");
} else {
printf("%d\n", dis[ex][ey][0]);
FDFS(ex, ey);
}
return 0;
}
for (int i = 0, at; i < n; i++) {
while (!dq.empty()) dq.pop_back();
at = 0;
while (at <= d && at < m) addX(i, at++);
for (int j = 0; j < m; j++) {
while (at <= j + d && at < m) addX(i, at++);
minn[i][j] = dis[i][getQ(max(0, j - d))][1];
}
}
for (int i = 0, at; i < m; i++) {
while (!dq.empty()) dq.pop_back();
at = 0;
while (at <= d && at < n) addY(i, at++);
for (int j = 0; j < n; j++) {
while (at <= j + d && at < n) addY(i, at++);
minn2[j][i] = minn[getQ(max(0, j - d))][i];
}
}
int ans = inf, tx, ty;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int get = minn2[i][j];
if (dis[i][j][0] + 1 + get < ans) {
ans = dis[i][j][0] + 1 + get;
tx = i, ty = j;
}
}
}
if (ans >= inf) {
printf("-1");
} else {
printf("%d\n", ans);
FDFS(tx, ty);
int get = minn2[tx][ty];
for (int i = max(0, tx - d); i <= min(n - 1, tx + d); i++) {
for (int j = max(0, ty - d); j <= min(m - 1, ty + d); j++) {
if (dis[i][j][1] == get) {
DFS(i, j);
return 0;
}
}
}
}
return 0;
}

L、动物森友会

        后六道题中最简单的一道,放最后了(毒瘤。方法是二分答案配合网络流。
        二分出一个答案,然后建网络流图,答案判定就看最大流看是否满流即可。

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
#include <iostream>
#include <queue>
#define inf 0x3f3f3f3f

#define N 2000
using namespace std;
struct
{
int to, next, v;
} edge[20005];
int head[N], cnt = 0, n, b, e, deep[N], cur[N] = {0};
queue<int> que;

inline void add(int x, int y, int z)
{
edge[cnt].to = y, edge[cnt].next = head[x], edge[cnt].v = z, head[x] = cnt++;
}

int DFS(int x, int limit)
{
if (limit == 0 || x == e) return limit;
int f, flow = 0;
for (int i = cur[x]; ~i; i = edge[i].next) {
cur[x] = i;
if (deep[edge[i].to] == deep[x] + 1 && (f = DFS(edge[i].to, min(limit, edge[i].v)))) {
edge[i].v -= f, edge[i ^ 1].v += f, flow += f, limit -= f;
if (!limit) break;
}
}
return flow;
}

bool BFS()
{
while (!que.empty()) que.pop();
for (int i = 1; i <= n; i++) deep[i] = n, cur[i] = head[i];
deep[b] = 0, que.push(b);
while (!que.empty()) {
int f = que.front();
que.pop();
for (int i = head[f]; ~i; i = edge[i].next) {
if (edge[i].v > 0 && deep[edge[i].to] == n) {
deep[edge[i].to] = deep[f] + 1, que.push(edge[i].to);
if (edge[i].to == e) return true;
}
}
}
return deep[e] != n;
}
int events, li, c[1005], os[7][1005], sum;
inline int check(int x)
{
cnt = 0;
for (int i = 1; i <= n; i++) head[i] = -1;
for (int i = 1; i <= 7; i++) {
add(b, i + 1, min(1ll * x / 7 * li + (i <= x % 7 ? li : 0), 1ll * sum)), add(i + 1, b, 0);
}
for (int i = 0; i < 7; i++) {
for (int j = 1; j <= os[i][0]; j++) {
add(i + 2, 8 + os[i][j], sum);
add(8 + os[i][j], i + 2, 0);
}
}
for (int i = 1; i <= events; i++) add(8 + i, e, c[i]), add(e, 8 + i, 0);
int flow = 0;
while (BFS()) flow += DFS(b, inf);
return flow == sum;
}
int main()
{
scanf("%d%d", &events, &li);
n = 2 + 7 + events, b = 1, e = n;
for (int i = 1, p; i <= events; i++) {
scanf("%d%d", c + i, &p), sum += c[i];
for (int j = 1, x; j <= p; j++) {
scanf("%d", &x);
os[x - 1][++os[x - 1][0]] = i;
}
}
int l = 0, r = 700000000, mid;
while (l < r) {
if (r == l + 1) {
printf("%d", r);
break;
}
mid = (l + r) >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid;
}
}
// PAUSE;
return 0;
}