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\)
考完了,去幹飯,吃完飯後像往常一樣回機房摸一會,lj 突然進來給我嚇一跳,然後過來告訴我說:“你檔案輸入輸出寫錯了。” 我:“???”
看了一眼,發現 \(T2\) 確實寫錯了。
然後 lj 又說:“我又給你測了一下,好像是 90 分左右,你看你考場上要是檔案寫錯了……”(此處省略一頓教育)
我:“90分???那豈不是說我 \(KMP\) 後輸出了一下得了 90 分???”(震驚我一整年)
事實證明資料確實水,好端端的一道 \(AC\) 自動機上 \(dp\) 變成了 \(KMP\) 貪心……
預期: 10 + ? + 60 + 10 = 80pts
事實上:10 + 90 + 80 + 25 = 205pts
\(CSP \ RP--\)
下面我們迴歸正題,簡單來講一下考試題。
T1. F
首先我們發現環上的點選哪一個都是一樣的,所以先 \(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
暴力做法就是記錄一下空格及起點,注意兩個點都要記錄。
每次暴力向空格的四個方向搜尋,如果空格不在起點四周,那麼就直接令空格的四個方向的點入隊。
如果空格在起點四周,交換空格和起點入隊。
下面是正解:
我們發現,暴力 \(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;
}
本文來自部落格園,作者:{xixike},轉載請註明原文連結:https://www.cnblogs.com/xixike/p/15432797.html