贪心 + 模拟,区间 dp(记搜),AC 自动机
骗分大赛,给我整不会了 /lh
# A. 我永远喜欢月
考场上瞎特判了几种无解的情况,成功拿到 70pts, 直接骗到 97pts(后面 3 分是 数据)orz
下面说正解。
我们发现从 推到 的限制条件非常强,不是很好处理,所以考虑反过来推,也就是判断从 能不能推到 。
反过来考虑的话,一次操作就变成了从 中找一个 1,把它变成 0,然后同行同列的数想变成多少都行。
中有一些行列是不能操作的,即当某一行 / 列出现了两个及两个以上的 1 时,这些 1 所在的行,列上的数都不能进行操作。这个应该比较好理解吧,如果你操作了,就会有 1 变成 0,然后你就变不回来了。
我们称这些 1 为限制点,称所有限制点所在行列的集合为限制区间 。
接下来就要判断是否有解了。
- 当  和  完全相同时,直接输出 
Yes。 - 限制点的值是不可能变的,所以判一下  和  中限制点的值是否相同,如果有相同的直接输出 
No。 - 我们想要从  转化到  必须有一个可行点(如果你一步都动不了还怎么转化),所以  中必须有一个不在限制区间的 0, 中必须有一个不在限制区间的 1,如果有一个没有,那么输出 
No。 
最后输出 Yes 。
下面是巨无霸丑陋代码(清晰点的可以去看 zxgg 的代码 qwq)
#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. 找朋友
错误贪心(每次找最大的)可以获得前三个 的分,甚至只会被最后一个测试点卡 /lh
本题做法为区间 ,记搜也可以通过。
首先计算出每条鱼能到达的最左边和最右边的鱼缸编号是多少。然后离散化。这样范围就变成 了。
对于当前正在处理的区间 ,计算出每个鱼缸可以被跳到的鱼的数量,然后枚举一遍,取最大值即可。
#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,真的没话说了……
这题是个裸的 自动机二次加强版。
把每个人的名字倒过来(正着插字符个数是 的)插到 树里,每插进去一个点,令那个点的 加 1。
然后我们再把询问的串反过来插进去,但是此时的 不需要加 1,因为这是询问串。
剩下的类似于 自动机二次加强版。
先建出 树,然后跑个 ,统计子树大小,最后直接输出即可。
另外这题比较卡时空,不要用 cin , cout ,不需要的数组也不要多开。
#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;  | |
} |