1. 程式人生 > 其它 >2021.10.20CSP模擬

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

首先我們發現環上的點選哪一個都是一樣的,所以先 \(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

P1979 [NOIP2013 提高組] 華容道

暴力做法就是記錄一下空格及起點,注意兩個點都要記錄。

每次暴力向空格的四個方向搜尋,如果空格不在起點四周,那麼就直接令空格的四個方向的點入隊。

如果空格在起點四周,交換空格和起點入隊。

下面是正解:

我們發現,暴力 \(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