2021.10.20CSP模拟模拟赛 赛后总结
意想不到的骗分……
首先开 \(T1\),怎么又是算期望的……不会……先跳过。
开 \(T2\),emm……似乎是一道 \(dp\), 套路的设出 \(dp[i][j]\) (分别表示前 \(i\) 和前 \(j\) 个字符)状态,然后用 \(KMP\) 优化一下?
但是不会转移方程,先跳过。
开 \(T3\),咦?
60% 的数据:n,m <= 30,q <= 10
暴力似乎很可做的样子,写一发吧,不写怕不是要保龄。
1.5h后……
终于写完了,测一下样例,哎,过了,可以。大样例……算了,不测了,反正也是 \(T\)。
开 \(T4\),这是什么题啊……我只会 \(n = 1\) 的做法 \(QwQ\)。
于是再回去看 \(T2\),列了列式子,然后教练叫我们出去做核酸检测,趁机与机房大佬们交流了一下考试题目,人均 200+,心态小崩。
半个小时后,回机房了,经过房神的指点继续写 \(T2\),然而还是不会……最后写了个 \(KMP\) 板子,输出了一下短串在长串中出现的次数,摸了。
然后再去看 \(T1\),但是这个期望到底 tmd 怎么算啊。
随便写了个深搜,统计了一下次数,乘了个 \(n\) 的逆元,摸了。
最后打 \(T4\) 的暴力,那么直接计算一下 \(n = 1\) 时的答案为 \(ans_1\),假设 \(n\) 个点经过 \(t\) 时间后没有重叠,直接输出 \(n * ans_1\),结果还忘取模了,似乎挂了 10pts。
考完了,去干饭,吃完饭后像往常一样回机房摸一会,lj 突然进来给我吓一跳,然后过来告诉我说:“你文件输入输出写错了。” 我:“???”
看了一眼,发现 \(T2\) 确实写错了。
然后 lj 又说:“我又给你测了一下,好像是 90 分左右,你看你考场上要是文件写错了……”(此处省略一顿教育)
我:“90分???那岂不是说我 \(KMP\) 后输出了一下得了 90 分???”(震惊我一整年)
事实证明数据确实水,好端端的一道 \(AC\) 自动机上 \(dp\) 变成了 \(KMP\) 贪心……
预期: 10 + ? + 60 + 10 = 80pts
事实上:10 + 90 + 80 + 25 = 205pts
\(CSP \ RP--\)
下面我们回归正题,简单来讲一下考试题。
T1. F
Solution
首先我们发现环上的点选哪一个都是一样的,所以先 \(Tarjan\) 缩个点。
然后跑一遍拓扑排序,找到有多少个点可以到达当前点。
每个点被选后贡献就是 1,所以直接把被选的概率加在一起即可。
答案即为 \(\sum\limits_{i = 1}^{n}{c_i}\)。
\(c_i\) 表示能到达点 \(i\) 的点的个数。
还要用 \(bitset\) 维护一下,这样就可以直接求出 \(c\) 了。
实践复杂度 \(O(\frac{n^3}{ω})\)。
code
dddddddddddd#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <bitset>
#include <queue>
#define ll long long
using namespace std;
const ll mod = 998244353;
const ll N = 1010;
char s[N];
ll n;
vector <ll> g[N], G[N];
bitset <N> vis[N];
ll dfn[N], low[N], tim;
ll stk[N], top, t[N];
ll scc[N], cnt;
ll in[N];
inline void tarjan(ll x){
dfn[x] = low[x] = ++tim;
stk[++top] = x;
t[x] = 1;
for(auto y : g[x]){
if(!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]);
else if(t[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x]){
cnt++;
ll k;
do{
k = stk[top--];
scc[k] = cnt;
t[k] = 0;
vis[cnt][k] = 1;
}while(top && x != k);
}
}
inline ll power(ll x, ll b){
ll res = 1;
while(b){
if(b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
inline void topo(){
queue <ll> q;
for(ll i = 1; i <= n; i++)
if(!in[i]) q.push(i);
while(!q.empty()){
ll x = q.front();
q.pop();
for(auto y : G[x]){
vis[y] |= vis[x];
if((--in[y]) == 0) q.push(y);
}
}
return;
}
signed main(){
scanf("%lld", &n);
for(ll i = 1; i <= n; i++){
scanf("%s", s + 1);
for(ll j = 1; j <= n; j++)
if(s[j] == '1') g[i].push_back(j);
}
for(ll i = 1; i <= n; i++)
if(!dfn[i]) tarjan(i);
for(ll i = 1; i <= n; i++)
for(auto j : g[i])
if(scc[i] != scc[j])
G[scc[i]].push_back(scc[j]), in[scc[j]]++;
// cout << "topi" << endl;
topo();
ll ans = 0;
for(ll i = 1; i <= n; i++)
ans = (ans + power(vis[scc[i]].count(), mod - 2)) % mod;
printf("%lld\n", ans);
return 0;
}
T2. S
Description
给出 \(S\),\(T\) 两个字符串,问至少删去 \(S\) 中多少个字符,才能使得 \(T\) 不在 \(S\) 中出现 即不存在 \(l\),\(r\) 使得 \(S_{l∼r}\)=T
Solution
\(AC\) 自动机上 \(dp\)。
我们先对 \(T\) 建 \(trie\) 图。
设 \(f[i][j]\) 表示已经匹配到 \(S\) 的前 \(i\) 位,\(T\) 的前 \(j\) 位时,最多保留的字母个数。
如果 \(S\) 的下一位不是 \(T\) 的终点,那么可以从 \(f[i - 1][j] + 1\) 转移过来。
如果第 \(j\) 位不是 \(T\) 的终点,可以从 \(f[i - 1][j]\) 转移过来。
code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 8010;
char s[N], t[N];
int n, m, ans;
int trie[N][26], tot, fail[N];
int f[N][N], End[N];
inline void insert(char s[]){
int now = 0;
for(int i = 1; i <= m; i++){
int c = s[i] - 'a';
if(!trie[now][c]) trie[now][c] = ++tot;
now = trie[now][c];
}
End[now] = 1;
}
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];
}
}
}
int main(){
// freopen("s.in", "r", stdin);
// freopen("s.out", "w", stdout);
scanf("%s%s", s + 1, t + 1);
n = strlen(s + 1), m = strlen(t + 1);
insert(t);
build();
for(int i = 1; i <= n; i++){
for(int j = 0; j <= m; j++){
if(!End[trie[j][s[i] - 'a']]) f[i][trie[j][s[i] - 'a']] = max(f[i][trie[j][s[i] - 'a']], f[i - 1][j] + 1);
if(!End[j]) f[i][j] = max(f[i][j], f[i - 1][j]);
}
}
int ans = 0;
for(int i = 0; i <= tot; i++)
ans = max(ans, f[n][i]);
printf("%d\n", n - ans);
return 0;
}
/*
abbabbab
ab
*/
T3. Y
Description
Solution
暴力做法就是记录一下空格及起点,注意两个点都要记录。
每次暴力向空格的四个方向搜索,如果空格不在起点四周,那么就直接令空格的四个方向的点入队。
如果空格在起点四周,交换空格和起点入队。
下面是正解:
我们发现,暴力 \(bfs\) 时会有许多无用的状态,就是在空格向起点方向移动时,会向许多不优的点移动。
所以可以优化。
我们把每个点的四个方向重新标号,然后向四方连边,跑最短路。
code
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 35;
int n, m, q, ans;
int a[N][N];
struct node{
int x, y;
}e, s, t;
struct Edge{
int v, w, nxt;
}edge[20010];
int head[5010], tot;
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
bool vis[5010];
int d[N][N], id[N][N][5];
int dis[5010];
bool check(int x, int y){
if(x < 1 || x > n || y < 1 || y > m || !a[x][y]) return 0;
return 1;
}
inline void add(int x, int y, int z){
edge[++tot] = (Edge){y, z, head[x]};
head[x] = tot;
}
inline void bfs(int sx, int sy, int tx, int ty){
memset(d, 0x3f, sizeof(d));
queue <node> q;
q.push((node){sx, sy});
d[sx][sy] = 0;
while(!q.empty()){
node now = q.front();
q.pop();
for(int i = 0; i < 4; i++){
int mx = now.x + dx[i];
int my = now.y + dy[i];
if(mx == tx && my == ty) continue;
if(check(mx, my)){
if(d[mx][my] > d[now.x][now.y] + 1){
d[mx][my] = d[now.x][now.y] + 1;
q.push((node){mx, my});
}
}
}
}
}
inline void spfa(int sx, int sy){
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
queue <int> q;
for(int i = 0; i < 4; i++){
int mx = sx + dx[i];
int my = sy + dy[i];
if(check(mx, my)){
dis[id[sx][sy][i]] = d[mx][my];
q.push(id[sx][sy][i]);
vis[id[sx][sy][i]] = 1;
}
}
while(!q.empty()){
int x = q.front();
q.pop();
vis[x] = 0;
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(dis[y] > dis[x] + edge[i].w){
dis[y] = dis[x] + edge[i].w;
if(!vis[y]) vis[y] = 1, q.push(y);
}
}
}
}
int main(){
// freopen("y.in", "r", stdin);
// freopen("y.out", "w", stdout);
scanf("%d%d%d", &n, &m, &q);
for(int i = 1, tot = 0; i <= n; i++)
for(int j = 1; j <= m; j++){
scanf("%d", &a[i][j]);
for(int k = 0; k < 4; k++)
id[i][j][k] = ++tot;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++){
if(!a[i][j]) continue;
for(int k = 0; k < 4; k++){
int mx = i + dx[k];
int my = j + dy[k];
if(check(mx,my))
add(id[i][j][k], id[mx][my][k ^ 1], 1);
}
for(int k = 0; k < 4; k++){
int nx = i + dx[k];
int ny = j + dy[k];
if(check(nx, ny)){
bfs(nx, ny, i, j);
for(int l = 0; l < 4; l++){
if(k == l) continue;
int mx = i + dx[l], my = j + dy[l];
if(check(mx, my)) add(id[i][j][k], id[i][j][l], d[mx][my]);
}
}
}
}
while(q--){
scanf("%d%d%d%d%d%d", &e.x, &e.y, &s.x, &s.y, &t.x, &t.y);
// cout << e.x << " " << e.y << " " << s.x << " " << s.y << " " << t.x << " " << t.y << endl;
if(s.x == t.x && s.y == t.y){
puts("0");
continue;
}
bfs(e.x, e.y, s.x, s.y);
spfa(s.x, s.y);
int ans = 1e9;
for(int i = 0; i < 4; i++){
int mx = t.x + dx[i];
int my = t.y + dy[i];
if(check(mx, my)) ans = min(ans, dis[id[t.x][t.y][i]]);
}
if(ans == 1e9) puts("-1");
else printf("%d\n", ans);
}
return 0;
}
T4. F
一道 \(cf\) 3500 分的题,完全不会做,我还是太菜了 \(QwQ\)。