贪心 + 模拟,区间 dp(记搜),AC 自动机

骗分大赛,给我整不会了/lh

# A. 我永远喜欢月

考场上瞎特判了几种无解的情况,成功拿到 70pts,sleeping_crawlerssleeping\_crawlers 直接骗到 97pts(后面 3 分是 hack\text{hack} 数据)orz

下面说正解。

我们发现从 A\text{A} 推到 B\text{B} 的限制条件非常强,不是很好处理,所以考虑反过来推,也就是判断从 B\text{B} 能不能推到 A\text{A}

反过来考虑的话,一次操作就变成了从 B\text{B} 中找一个 1,把它变成 0,然后同行同列的数想变成多少都行。

B\text{B} 中有一些行列是不能操作的,即当某一行/列出现了两个及两个以上的 1 时,这些 1 所在的行,列上的数都不能进行操作。这个应该比较好理解吧,如果你操作了,就会有 1 变成 0,然后你就变不回来了。

我们称这些 1 为限制点,称所有限制点所在行列的集合为限制区间

接下来就要判断是否有解了。

  • A\text{A}B\text{B} 完全相同时,直接输出 Yes
  • 限制点的值是不可能变的,所以判一下 A\text{A}B\text{B} 中限制点的值是否相同,如果有相同的直接输出 No
  • 我们想要从 B\text{B} 转化到 A\text{A} 必须有一个可行点(如果你一步都动不了还怎么转化),所以 A\text{A} 中必须有一个不在限制区间的 0,B\text{B} 中必须有一个不在限制区间的 1,如果有一个没有,那么输出 No

最后输出 Yes

下面是巨无霸丑陋代码(清晰点的可以去看 zxgg 的代码 qwq)

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
#include <bits/stdc++.h>

using namespace std;

namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}

template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;

const int N = 1010;
int T, n, m, ok, tot;
char s[N][N];
int pos[N];
bool a[N][N], b[N][N];
bool line[N], row[N];

signed main(){
// freopen("A.in", "r", stdin);
// freopen("A.out", "w", stdout);
T = read();
while(T--){
memset(pos, 0, sizeof(pos));
memset(line, 0, sizeof(line));
memset(row, 0, sizeof(row));
n = read(), m = read();
for(int i = 1; i <= n; ++i){
scanf("%s", s[i] + 1);
for(int j = 1; j <= m; ++j) a[i][j] = s[i][j] - '0';
}
for(int i = 1; i <= n; ++i){
scanf("%s", s[i] + 1);
for(int j = 1; j <= m; ++j) b[i][j] = s[i][j] - '0';
}

int flag = 0;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
if(a[i][j] != b[i][j]) flag = 1;
if(!flag) {puts("Yes"); continue;}

for(int i = 1; i <= n; ++i){
int cnt = 0;
for(int j = 1; j <= m; ++j){
if(cnt && b[i][j]) line[i] = row[cnt] = row[j] = 1, cnt = j;
if(pos[j] && b[i][j]) row[j] = line[pos[j]] = line[i] = 1, pos[j] = i;
if(b[i][j]) cnt = j, pos[j] = i;
}
}

flag = 0;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
if(line[i] && row[j] && (a[i][j] != b[i][j])) {flag = 1; break;}
if(flag) {puts("No"); continue;}

flag = 0;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
if(!a[i][j] && !line[i] && !row[j]) {flag = 1; break;}
if(!flag) {puts("No"); continue;}

flag = 0;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
if(b[i][j] && !line[i] && !row[j]) {flag = 1; break;}
puts(flag ? "Yes" : "No");
}
return 0;
}

# B. 找朋友

错误贪心(每次找最大的)可以获得前三个 subtask\text{subtask} 的分,甚至只会被最后一个测试点卡 /lh

本题做法为区间 dp\text{dp},记搜也可以通过。

首先计算出每条鱼能到达的最左边和最右边的鱼缸编号是多少。然后离散化。这样范围就变成 MM 了。

对于当前正在处理的区间 [l,r][l, r],计算出每个鱼缸可以被跳到的鱼的数量,然后枚举一遍,取最大值即可。

dpi,j=max{dpi,k1+dpk+1,j+sumk×(sumk1)/2}dp_{i, j} = \max\{dp_{i, k - 1} + dp_{k + 1, j} + sum_k \times (sum_k - 1) / 2\}

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 <bits/stdc++.h>

using namespace std;

namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}

template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;

const int N = 1e4 + 10;
const int M = 410;
int n, m, w, p, d, ans;
int h[N], t[N], val[N];
struct node{
int l, r;
}a[M];
int b[M << 1], tot;
int dp[M][M], sum[M];

signed main(){
// freopen("B.in", "r", stdin);
// freopen("B.out", "w", stdout);
n = read(), m = read(), w = read();
for(int i = 1; i <= n; ++i) h[i] = w - read();
for(int i = 1, j; i <= m; ++i){
p = read(), d = read();
for(j = p; j > 1; --j) if(d <= h[j]) break; a[i].l = j;
for(j = p; j < n; ++j) if(d <= h[j]) break; a[i].r = j;
b[++tot] = a[i].l, b[++tot] = a[i].r;
}
sort(b + 1, b + 1 + tot);
n = unique(b + 1, b + 1 + tot) - b - 1;
for(int i = 1; i <= m; ++i){
a[i].l = lower_bound(b + 1, b + 1 + n, a[i].l) - b;
a[i].r = lower_bound(b + 1, b + 1 + n, a[i].r) - b;
}
for(int i = 1; i <= m; ++i)
for(int j = a[i].l; j <= a[i].r; ++j) val[j]++;
for(int i = 1; i <= n; ++i) val[i] = val[i] * (val[i] - 1) / 2;
for(int len = 1; len <= n; ++len)
for(int i = 1; i + len - 1 <= n; ++i){
int j = i + len - 1;
for(int k = 1; k <= m; ++k)
if(a[k].l >= i && a[k].r <= j) sum[a[k].l]++, sum[a[k].r + 1]--;
for(int k = i; k <= j; ++k) sum[k] += sum[k - 1];
for(int k = i; k <= j; ++k)
dp[i][j] = max(dp[i][j], sum[k] * (sum[k] - 1) / 2 + dp[i][k - 1] + dp[k + 1][j]), sum[k] = 0;
sum[j + 1] = 0;
}
write(dp[1][n]), puts("");
return 0;
}

# C. 欧洲皇室女士的名字

暴力可得 82pts,真的没话说了……

这题是个裸的 AC\text{AC} 自动机二次加强版。

把每个人的名字倒过来(正着插字符个数是 n2n^2 的)插到 trie\text{trie} 树里,每插进去一个点,令那个点的 sizsiz 加 1。

然后我们再把询问的串反过来插进去,但是此时的 sizsiz 不需要加 1,因为这是询问串。

剩下的类似于 AC\text{AC} 自动机二次加强版。

先建出 fail\text{fail} 树,然后跑个 dfs\text{dfs},统计子树大小,最后直接输出即可。

另外这题比较卡时空,不要用 cincout,不需要的数组也不要多开。

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
#include <bits/stdc++.h>

using namespace std;

namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}

template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;

const int N = 2e6 + 10;
int n, m, tot;
char c, s[N >> 1];
int p, trie[N][27], siz[N], pos[N], End[N];

inline void insertc(int x, int fa){
int now = pos[fa], ch = c - 'A';
trie[now][ch] = ++tot;
pos[x] = tot;
++siz[tot];
}

inline void inserts(int id){
int len = strlen(s), now = 0;
for(int i = len - 1; i >= 0; --i){
int c = s[i] - 'A';
if(!trie[now][c]) trie[now][c] = ++tot;
now = trie[now][c];
}
End[id] = now;
}

int fail[N];

inline void build(){
queue <int> q;
for(int i = 0; i < 26; ++i)
if(trie[0][i]) q.push(trie[0][i]);
while(!q.empty()){
int now = q.front();
q.pop();
for(int i = 0; i < 26; ++i){
if(trie[now][i]) fail[trie[now][i]] = trie[fail[now]][i], q.push(trie[now][i]);
else trie[now][i] = trie[fail[now]][i];
}
}
}

struct node{
int v, nxt;
}edge[N << 1];
int head[N], etot;

inline void add(int x, int y){
edge[++etot] = (node){y, head[x]};
head[x] = etot;
}

inline void dfs(int x, int fa){
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(y == fa) continue;
dfs(y, x);
siz[x] += siz[y];
}
}

signed main(){
// freopen("C.in", "r", stdin);
// freopen("C.out", "w", stdout);
n = read(), m = read();
for(int i = 1; i <= n; ++i) {c = getchar(); p = read(); insertc(i, p);}
for(int i = 1; i <= m; ++i) {scanf("%s", s); inserts(i);}

build();
for(int i = 1; i <= tot; ++i) add(fail[i], i);
dfs(0, 0);
for(int i = 1; i <= m; ++i) write(siz[End[i]]), putchar('\n');
return 0;
}

阅读次数