COCI2016-2017#4

2021/7/15c++

T1 :Bridž

水,略ide

T2 :Kartomat

建個 Trie 樹跑一波,不過這麼小的數據範圍貌似不用優化

T3:Kas

不難發現就是將 \(N\) 個數分紅 \(3\) 堆,前兩堆大小相同,使得第三堆最小。ui

定義狀態 \(f[i][j][k]\) 表示前面 \(i\) 個數,第 \(1\) 堆大小爲 \(j\),第 \(2\) 堆大小爲 \(k\) ,是否可行,直接轉移,貌似能夠 bitset 優化作到 \(\mathcal{O}(\dfrac{N(\sum c)^2}{w})\)。spa

比較套路的作法,對後兩個維度做差,定義狀態 \(f[i][j]\) 表示前 \(i\) 個數,前兩堆差爲 \(j\) 時,第 \(1\) 堆能夠達到的最大重量時多少。字符串

直接轉移 \(\mathcal{O}(N\sum c)\) 已經能夠經過。get

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
using namespace std;
int f[N << 1], n, g[N << 1];
inline void ck(int &x,int y){if(y > x)x = y;}
int main(){
	scanf("%d", &n);
	int sum = 0;
	memset(f, 0xcf, sizeof(f));
	int bas = N - 5;
	f[bas] = 0;
	rep(i, 1, n){
		memset(g, 0xcf, sizeof(g));
		int x;scanf("%d", &x);
		rep(j, -sum, sum)
			ck(g[j + bas], f[j + bas]), 
			ck(g[j + x + bas], f[j + bas] + x), 
			ck(g[j - x + bas], f[j + bas]);
		sum += x;rep(j, -sum, sum)f[j + bas] = g[j + bas];
	}
	printf("%d\n", sum - f[bas]);
	return 0;
}

T4:Rekonstruiraj

感受是個錯題啊,像 \(\frac{1}{3}\) 這樣無限小數怎麼處理精度啊。it

考慮逆向思惟,咱們要將最終集合的數一一刪去。class

顯然最小的數只能被不大於它的數刪去,那麼咱們枚舉整個數。循環

而這個數必定是最小的數除以一個正整數倍率,咱們從小到大枚舉倍率,同時判斷在這個倍率下,區間\([A,B]\) 中是否有刪除了沒有的元素。

因爲 \(K\) 很小,因此判斷和刪除都是 \(\mathcal{O}(K)\) 級別的,因此複雜度是 \(\mathcal{O}(K^2)\) ?感受挺玄學的。

另外賽時發現 luogu 的 spj 全是鍋,直接 Hack spj 就能過。

T5:Rima

挺有意思的題。

剛開始看很是沒有思路就先開 T6 了。

先想到多是 DAG 求最長路,發現不大可作。

觀察一下這個 \(LCS \ge Len - 1\) 的條件。兩個串長度不相等,必定一個是另外一個的前綴且長度相差 \(1\) ,不然必定兩個串最後覺得必定不相等。

因此長度相等的串且前綴相等大概是兩兩連邊,而後不等的串連邊是惟一的。感受也不是很可作。

可是把這個連邊放到 Trie 樹上亂搞,發現這就是一個走 \(Trie\) 樹的過程。咱們要找的是是一條路徑,使得路徑長度加上與路徑直接相連的邊的長度之和最大。

直接樹上 DP 便可,時間複雜度 \(\mathcal{O}(N + \sum|S|)\),須要注意的是 Trie 的空間,大概得用 vector 維護以時間換空間。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 3000005
using namespace std;
int n, idx, ed[N];
vector<pair<int,int>>ch[N];
char s[N];
int get(int x,int y){
	for(auto z:ch[x])if(z.first == y)return z.second;
	return 0;
}
int f[N], ans;
void dfs(int x){
	f[x] = ed[x];
	int mx = 0, nxt = 0, sz = 0;
	for(auto y:ch[x]){
		dfs(y.second);
		if(ed[y.second]){
			sz++, nxt = max(nxt, f[y.second]);
			if(nxt > mx)swap(nxt, mx);
		}
	}ans = max(ans, nxt + mx + f[x] + max(0, sz - 2));
	f[x] += mx + max(0, sz - 1);
}
int main(){
	scanf("%d", &n);
	rep(i, 1, n){
		scanf("%s", s + 1);
		int m = strlen(s + 1), cur = 0;
		pre(j, m, 1){
			int now = get(cur, s[j] - 'a');
			if(!now)
				ch[cur].push_back(make_pair(s[j] - 'a', ++idx)), cur = idx;
			else cur = now;
		}
		ed[cur] = 1;
		//cout<<"ww "<<cur<<endl;
	}
	dfs(0);printf("%d\n", ans);
	return 0;
}

T6:Osmosmjerka

求機率輸出準確值還不用取模,顯然是直接枚舉全部可能(

顯然這個無限矩陣就是以給定的子矩陣爲元不斷循環,若是咱們固定一個方向,那麼以 \((i,j)\)爲起點的串,和以 \((i+an, j+bm),\ a,b\in \mathcal{Z}\) 爲起點的串必定相同。

因此咱們只用枚舉起點在 \((i, j), i\in[0, n - 1], j\in[0,m -1]\) 的串便可,而後就是求這個串的哈希值。

這個字符串開始和結束的幾個單獨拎出來,中間不斷循環。開始和結束的哈希值能夠預處理,中間的是個等比數列,能夠直接通項公式爆算。

細節比較多。注意斜的,橫的,豎的的循環節可能都不相同,斜的循環節和 \(n,m\) 的最大公約數有關,不過咱們能夠直接對整個子矩陣遍歷一遍避免過多討論。

另外建議雙哈希,賽時隨機了一個\(10^9\) 左右的質數 \(934567889\) 被卡了 \(40\) 分,而後又隨了兩個質數仍是掛了,查了半天還覺得是哪裏掛了,後來才發現原來是卡了哈希,估計出題人把 \(10^9\) 左右的模數都卡了。

後來找到 \(bas = 229, P = 10^9 + 97\) 沒有卡(

時間複雜度 \(\mathcal{O}(NM\log)\) ,實現較醜還排了序。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 505
#define P 1000000097
#define bas 229
using namespace std;
int n, m, k, v[N][N], mat[N][N], idx, f[N][N], pw[N * N];
vector<int>c[N];
char s[N][N], cp[N][N];
void ins(int x,int y){
	v[x][y] = ++idx;mat[x][y] = 0;
	c[idx].push_back(s[x][y]);
	while(true){
		x = (x + 1) % n;
		y = (y + 1) % m;
		if(v[x][y])break ;
		v[x][y] = idx, 
		mat[x][y] = c[idx].size(), 
		c[idx].push_back(s[x][y]);
	}
	int w = c[idx].size();
	//cout<<"init " <<w<<endl;
	rep(i, 1, w - 1)c[idx][i] = (1LL * c[idx][i - 1] * bas + c[idx][i]) % P;
}
void init(){
	rep(i, 0, n - 1){
		f[i][0] = s[i][0];
		rep(j, 1, m - 1)
			f[i][j] = (1LL * f[i][j - 1] * bas + s[i][j]) % P;
	}
	rep(i, 0, n - 1)rep(j, 0, m - 1)if(!v[i][j])ins(i, j);
}
vector<int>w;
int Pow(int x,int y){
	int now = 1;
	for(;y;y >>= 1, x = 1LL * x * x % P)if(y & 1)now = 1LL * now * x % P;
	return now;
}
inline int g(int x,int y){
	if(x == 1)return y + 1;
	return 1LL * (Pow(x, y + 1) - 1) * Pow(x - 1, P - 2) % P;
}
int calc(int x,int bs,int cnt){return 1LL * x * g(bs, cnt - 1) % P;}
int row(int x,int y){
	int res = k, pr = 0, cur = 0;
	if(y)pr = f[x][y - 1];
	if(m - y >= res)return (f[x][y + res - 1] - 1LL * pr * pw[res] % P + P) % P;
	cur = (f[x][m - 1] - 1LL * pr * pw[m - y] % P + P) % P, res -= m - y;
	if(res <= m)return (1LL * pw[res] * cur + f[x][res - 1]) % P;
	int cc = res / m; res %= m;
	cur = 1LL * cur * Pow(bas, cc * m) % P;
	cur = (cur + calc(f[x][m - 1], pw[m], cc)) % P;
	if(!res)return cur;
	return (1LL * pw[res] * cur + f[x][res - 1]) % P;
}
int col(int x,int y){
	int t = v[x][y]; 
	y = mat[x][y], x = t;
	int w = c[x].size(), res = k, pr = 0, cur = 0;
	if(y)pr = c[x][y - 1];
	if(w - y >= res)return (c[x][y + res - 1] - 1LL * pr * pw[res] % P + P) % P;
	cur = (c[x][w - 1] - 1LL * pr * pw[w - y] % P + P) % P, res -= w - y;
	if(res <= w)return (1LL * pw[res] * cur + c[x][res - 1]) % P;
	int cc = res / w; res %= w;
	cur = 1LL * cur * Pow(bas, cc * w) % P;
	cur = (cur + calc(c[x][w - 1], pw[w], cc)) % P;
	if(!res)return cur;
	return (1LL * pw[res] * cur + c[x][res - 1]) % P;
}
void rotate(){
	rep(i, 0, n - 1)rep(j, 0, m - 1)cp[j][n - 1 - i] = s[i][j];
	swap(n, m);
	rep(i, 0, n - 1)rep(j, 0, m - 1)s[i][j] = cp[i][j];
}
long long gcd(long long x,long long y){return y ? gcd(y, x % y) : x;}
signed main(){
	scanf("%d%d%d", &n, &m, &k);
	pw[0] = 1;rep(i, 1, n * m)pw[i] = 1LL * pw[i - 1] * bas % P;
	rep(i, 0, n - 1)scanf("%s", s[i]);
	rep(op, 0, 3){
		memset(v, 0, sizeof(v));
		rep(i, 1, idx)c[i].clear();
		idx = 0;init();
		rep(i, 0, n - 1)rep(j, 0, m - 1)
			w.push_back(row(i, j)), 
			w.push_back(col(i, j));
		rotate();
	}
	sort(w.begin(), w.end());int sum = 0, pr = ~0;long long ans = 0;
	for(int x : w){
		if(pr != x)ans += 1LL * sum * sum, sum = 0, pr = x;
		sum++;
	}ans += 1LL * sum * sum;//cout<<endl;
	long long fac = 1LL * w.size() * w.size();
	long long cur = gcd(ans, fac);
	printf("%lld/%lld\n", ans / cur, fac / cur);
	return 0;
}
 
 
 

2021/7/15

T1 :Bridž

水,略

T2 :Kartomat

建個 Trie 樹跑一波,不過這麼小的數據範圍貌似不用

T3:Kas

不難發現就是將 \(N\) 個數分紅 \(3\) 堆,前兩堆大小相同,使得第三堆最小。

定義狀態 \(f[i][j][k]\) 表示前面 \(i\) 個數,第 \(1\) 堆大小爲 \(j\),第 \(2\) 堆大小爲 \(k\) ,是否可行,直接轉移,貌似能夠 bitset 優化作到 \(\mathcal{O}(\dfrac{N(\sum c)^2}{w})\)。

比較套路的作法,對後兩個維度做差,定義狀態 \(f[i][j]\) 表示前 \(i\) 個數,前兩堆差爲 \(j\) 時,第 \(1\) 堆能夠達到的最大重量時多少。

直接轉移 \(\mathcal{O}(N\sum c)\) 已經能夠經過。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
using namespace std;
int f[N << 1], n, g[N << 1];
inline void ck(int &x,int y){if(y > x)x = y;}
int main(){
	scanf("%d", &n);
	int sum = 0;
	memset(f, 0xcf, sizeof(f));
	int bas = N - 5;
	f[bas] = 0;
	rep(i, 1, n){
		memset(g, 0xcf, sizeof(g));
		int x;scanf("%d", &x);
		rep(j, -sum, sum)
			ck(g[j + bas], f[j + bas]), 
			ck(g[j + x + bas], f[j + bas] + x), 
			ck(g[j - x + bas], f[j + bas]);
		sum += x;rep(j, -sum, sum)f[j + bas] = g[j + bas];
	}
	printf("%d\n", sum - f[bas]);
	return 0;
}

T4:Rekonstruiraj

感受是個錯題啊,像 \(\frac{1}{3}\) 這樣無限小數怎麼處理精度啊。

考慮逆向思惟,咱們要將最終集合的數一一刪去。

顯然最小的數只能被不大於它的數刪去,那麼咱們枚舉整個數。

而這個數必定是最小的數除以一個正整數倍率,咱們從小到大枚舉倍率,同時判斷在這個倍率下,區間\([A,B]\) 中是否有刪除了沒有的元素。

因爲 \(K\) 很小,因此判斷和刪除都是 \(\mathcal{O}(K)\) 級別的,因此複雜度是 \(\mathcal{O}(K^2)\) ?感受挺玄學的。

另外賽時發現 luogu 的 spj 全是鍋,直接 Hack spj 就能過。

T5:Rima

挺有意思的題。

剛開始看很是沒有思路就先開 T6 了。

先想到多是 DAG 求最長路,發現不大可作。

觀察一下這個 \(LCS \ge Len - 1\) 的條件。兩個串長度不相等,必定一個是另外一個的前綴且長度相差 \(1\) ,不然必定兩個串最後覺得必定不相等。

因此長度相等的串且前綴相等大概是兩兩連邊,而後不等的串連邊是惟一的。感受也不是很可作。

可是把這個連邊放到 Trie 樹上亂搞,發現這就是一個走 \(Trie\) 樹的過程。咱們要找的是是一條路徑,使得路徑長度加上與路徑直接相連的邊的長度之和最大。

直接樹上 DP 便可,時間複雜度 \(\mathcal{O}(N + \sum|S|)\),須要注意的是 Trie 的空間,大概得用 vector 維護以時間換空間。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 3000005
using namespace std;
int n, idx, ed[N];
vector<pair<int,int>>ch[N];
char s[N];
int get(int x,int y){
	for(auto z:ch[x])if(z.first == y)return z.second;
	return 0;
}
int f[N], ans;
void dfs(int x){
	f[x] = ed[x];
	int mx = 0, nxt = 0, sz = 0;
	for(auto y:ch[x]){
		dfs(y.second);
		if(ed[y.second]){
			sz++, nxt = max(nxt, f[y.second]);
			if(nxt > mx)swap(nxt, mx);
		}
	}ans = max(ans, nxt + mx + f[x] + max(0, sz - 2));
	f[x] += mx + max(0, sz - 1);
}
int main(){
	scanf("%d", &n);
	rep(i, 1, n){
		scanf("%s", s + 1);
		int m = strlen(s + 1), cur = 0;
		pre(j, m, 1){
			int now = get(cur, s[j] - 'a');
			if(!now)
				ch[cur].push_back(make_pair(s[j] - 'a', ++idx)), cur = idx;
			else cur = now;
		}
		ed[cur] = 1;
		//cout<<"ww "<<cur<<endl;
	}
	dfs(0);printf("%d\n", ans);
	return 0;
}

T6:Osmosmjerka

求機率輸出準確值還不用取模,顯然是直接枚舉全部可能(

顯然這個無限矩陣就是以給定的子矩陣爲元不斷循環,若是咱們固定一個方向,那麼以 \((i,j)\)爲起點的串,和以 \((i+an, j+bm),\ a,b\in \mathcal{Z}\) 爲起點的串必定相同。

因此咱們只用枚舉起點在 \((i, j), i\in[0, n - 1], j\in[0,m -1]\) 的串便可,而後就是求這個串的哈希值。

這個字符串開始和結束的幾個單獨拎出來,中間不斷循環。開始和結束的哈希值能夠預處理,中間的是個等比數列,能夠直接通項公式爆算。

細節比較多。注意斜的,橫的,豎的的循環節可能都不相同,斜的循環節和 \(n,m\) 的最大公約數有關,不過咱們能夠直接對整個子矩陣遍歷一遍避免過多討論。

另外建議雙哈希,賽時隨機了一個\(10^9\) 左右的質數 \(934567889\) 被卡了 \(40\) 分,而後又隨了兩個質數仍是掛了,查了半天還覺得是哪裏掛了,後來才發現原來是卡了哈希,估計出題人把 \(10^9\) 左右的模數都卡了。

後來找到 \(bas = 229, P = 10^9 + 97\) 沒有卡(

時間複雜度 \(\mathcal{O}(NM\log)\) ,實現較醜還排了序。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 505
#define P 1000000097
#define bas 229
using namespace std;
int n, m, k, v[N][N], mat[N][N], idx, f[N][N], pw[N * N];
vector<int>c[N];
char s[N][N], cp[N][N];
void ins(int x,int y){
	v[x][y] = ++idx;mat[x][y] = 0;
	c[idx].push_back(s[x][y]);
	while(true){
		x = (x + 1) % n;
		y = (y + 1) % m;
		if(v[x][y])break ;
		v[x][y] = idx, 
		mat[x][y] = c[idx].size(), 
		c[idx].push_back(s[x][y]);
	}
	int w = c[idx].size();
	//cout<<"init " <<w<<endl;
	rep(i, 1, w - 1)c[idx][i] = (1LL * c[idx][i - 1] * bas + c[idx][i]) % P;
}
void init(){
	rep(i, 0, n - 1){
		f[i][0] = s[i][0];
		rep(j, 1, m - 1)
			f[i][j] = (1LL * f[i][j - 1] * bas + s[i][j]) % P;
	}
	rep(i, 0, n - 1)rep(j, 0, m - 1)if(!v[i][j])ins(i, j);
}
vector<int>w;
int Pow(int x,int y){
	int now = 1;
	for(;y;y >>= 1, x = 1LL * x * x % P)if(y & 1)now = 1LL * now * x % P;
	return now;
}
inline int g(int x,int y){
	if(x == 1)return y + 1;
	return 1LL * (Pow(x, y + 1) - 1) * Pow(x - 1, P - 2) % P;
}
int calc(int x,int bs,int cnt){return 1LL * x * g(bs, cnt - 1) % P;}
int row(int x,int y){
	int res = k, pr = 0, cur = 0;
	if(y)pr = f[x][y - 1];
	if(m - y >= res)return (f[x][y + res - 1] - 1LL * pr * pw[res] % P + P) % P;
	cur = (f[x][m - 1] - 1LL * pr * pw[m - y] % P + P) % P, res -= m - y;
	if(res <= m)return (1LL * pw[res] * cur + f[x][res - 1]) % P;
	int cc = res / m; res %= m;
	cur = 1LL * cur * Pow(bas, cc * m) % P;
	cur = (cur + calc(f[x][m - 1], pw[m], cc)) % P;
	if(!res)return cur;
	return (1LL * pw[res] * cur + f[x][res - 1]) % P;
}
int col(int x,int y){
	int t = v[x][y]; 
	y = mat[x][y], x = t;
	int w = c[x].size(), res = k, pr = 0, cur = 0;
	if(y)pr = c[x][y - 1];
	if(w - y >= res)return (c[x][y + res - 1] - 1LL * pr * pw[res] % P + P) % P;
	cur = (c[x][w - 1] - 1LL * pr * pw[w - y] % P + P) % P, res -= w - y;
	if(res <= w)return (1LL * pw[res] * cur + c[x][res - 1]) % P;
	int cc = res / w; res %= w;
	cur = 1LL * cur * Pow(bas, cc * w) % P;
	cur = (cur + calc(c[x][w - 1], pw[w], cc)) % P;
	if(!res)return cur;
	return (1LL * pw[res] * cur + c[x][res - 1]) % P;
}
void rotate(){
	rep(i, 0, n - 1)rep(j, 0, m - 1)cp[j][n - 1 - i] = s[i][j];
	swap(n, m);
	rep(i, 0, n - 1)rep(j, 0, m - 1)s[i][j] = cp[i][j];
}
long long gcd(long long x,long long y){return y ? gcd(y, x % y) : x;}
signed main(){
	scanf("%d%d%d", &n, &m, &k);
	pw[0] = 1;rep(i, 1, n * m)pw[i] = 1LL * pw[i - 1] * bas % P;
	rep(i, 0, n - 1)scanf("%s", s[i]);
	rep(op, 0, 3){
		memset(v, 0, sizeof(v));
		rep(i, 1, idx)c[i].clear();
		idx = 0;init();
		rep(i, 0, n - 1)rep(j, 0, m - 1)
			w.push_back(row(i, j)), 
			w.push_back(col(i, j));
		rotate();
	}
	sort(w.begin(), w.end());int sum = 0, pr = ~0;long long ans = 0;
	for(int x : w){
		if(pr != x)ans += 1LL * sum * sum, sum = 0, pr = x;
		sum++;
	}ans += 1LL * sum * sum;//cout<<endl;
	long long fac = 1LL * w.size() * w.size();
	long long cur = gcd(ans, fac);
	printf("%lld/%lld\n", ans / cur, fac / cur);
	return 0;
}
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息