0607模擬賽 隨機除法(div) 炮塔(tower) 最大子段和

A.隨機除法(div)

題面

在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

題解

在這裏插入圖片描述
因爲n很大,所以手寫高精。

然後代碼中前綴和數組的定義與上面的題解有一點不一樣(而且上面的題解好像把jj寫成ii了)。含義如下

sum[e][i]=e(j<i[ej=ej]ji[ejej]dp[e])sum[e][i]=\sum_{e'}\left(\prod_{j<i}[e'_j=e_j]\prod_{j\ge i}[e'_j\le e_j]dp[e']\right)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef vector<int> vec;
const int mod = 1e9 + 7;
const int MAXN = 200005;
const ULL sd = 19260817;	
const LL lim = (LL)(1e12);
map<ULL, int>ID;
ULL hsh(vec V) { sort(V.begin(), V.end()); ULL re = 0; for(auto x : V) re = re * sd + x; return re; }
inline int qpow(int a, int b) { int re = 1; for(; b; b>>=1, a=1ll*a*a%mod)if(b&1)re=1ll*re*a%mod; return re; }
char ss[30];
struct big {
	LL a[2];
	big(int x = 0){ a[0] = x, a[1] = 0; }
	big operator *=(const int x) {
		a[0] *= x; a[1] *= x;
		a[1] += a[0]/lim, a[0] %= lim;
		return *this;
	}
	bool operator <(const big &o)const { return a[1] == o.a[1] ? a[0] < o.a[0] : a[1] < o.a[1]; }
	int operator %(const int x) { return (a[0] % x + a[1] % x * lim) % x; }
	void operator /= (const int x) { (a[0] += a[1] % x * lim) /= x, a[1] /= x; }
	bool read() {
		if(!~scanf("%s", ss)) return 0;
		int len = strlen(ss); a[0] = a[1] = 0;
		for(int i = 0; i*12 < len; ++i)
			for(int j = min(12, len-i*12); j; --j) a[i] = a[i] * 10 + ss[len-i*12-j] - '0';
		return 1;
	}
}Mx;
int pr[25] = { 0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71 }, K = 19, cnt;
pair<big, vec>S[MAXN];
void dfs(big x, int k, int pre, vec V) {
	S[++cnt] = make_pair(x, V); V.push_back(0);
	for(int i = 1; i <= pre && (x *= pr[k]) < Mx; ++i)
		V.back() = i, dfs(x, k+1, i, V);
}
int sum[MAXN][21], ans[MAXN];
void pre() {
	Mx.a[1] = lim, Mx.a[0] = 1;
	dfs(big(1), 1, 80, vec(0));
	sort(S + 1, S + cnt + 1);
	for(int i = 1; i <= cnt; ++i) ID[hsh(S[i].second)] = i;
	for(int i = 2; i <= cnt; ++i) {
		for(int j = S[i].second.size()-1; ~j; --j) {
			sum[i][j] = sum[i][j+1];
			if(S[i].second[j]) {
				vec tmp = S[i].second; --tmp[j];
				sum[i][j] = (sum[i][j] + sum[ID[hsh(tmp)]][j]) % mod;
			}
		}
		int d = 1; for(int x : S[i].second) d *= x+1;
		ans[i] = 1ll * (d + sum[i][0]) * qpow(d-1, mod-2) % mod;
		for(int j = 0; j <= K; ++j) sum[i][j] = (sum[i][j] + ans[i]) % mod;
	}
}
int main () {
	freopen("div.in", "r", stdin);
	freopen("div.out", "w", stdout);
	pre(); big n; int m;
	while(n.read()) {
		scanf("%d", &m); vec a;
		for(int i = 1, x; i <= m; ++i) {
			scanf("%d", &x); int mi = 0;
			while(n % x == 0) ++mi, n /= x;
			a.push_back(mi);
		}
		printf("%d\n", ans[ID[hsh(a)]]);
	}
}

B. 炮塔(tower)

題面

在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

題解

考試時以爲寫了正解,其實就差了一個情況沒考慮,只有40…

發現只要出現連續3個#\#,"###\#\#\#"那麼一定無法走過去。就可以不管後面的了。

那麼現在考慮連續2個#\#,如果要走過去,一定是"##\#\#*",然後在前面放一個*才能走過去。

如果是1個#\#,要麼是"#\#*"直接走過去,要麼就在#\#前面放一個*再走過去。

而且可以發現,如果手裏有2個*,走過1個#\#對我們來說其實是“如履平地”的。因爲可以在前面放個,然後過去在後面放個,再走回來取走前面的*,這樣我們沒有損失。

然後對於連續2個#\#的情況我們必須要留一個*在原地。

那麼我們用"##\#\#"分割整個序列爲若干塊,從左往右走,如果手裏的*能達到2塊,那麼我們就能暢通無阻地走過#\#。所以從最開始的塊到當前塊的所有*都能拿到,但還要減去##\#\#的代價。

如果向右走走不動了就break就行了。答案就是手裏的*max\max

但還有一種情況未考慮,就是#...*\#...*這種情況,我們如果只往右走,手裏的*數不能達到2個,但是可以往回走取掉前面的#*\#*。這種情況只需要設一個變量作爲標記就行了。

具體細節可以看代碼。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 400005;
int n, m, sum[MAXN], L[MAXN], R[MAXN];
char s[MAXN];
int main () {
	freopen("tower.in", "r", stdin);
	freopen("tower.out", "w", stdout);
	int T; scanf("%d", &T);
	while(T--) {
		scanf("%s", s+1); n = strlen(s+1);
		m = 0; s[n+1] = '#';
		for(int i = 1; i <= n; ++i) sum[i] = sum[i-1] + (s[i] == '*');
		for(int i = 1, j; i <= n; i = j+2) {
			if(i > 1 && s[i] != '*') break; //除了第一塊 判斷是不是 ##*
			L[++m] = i;
			for(j = i; j <= n && !(s[j] == '#' && s[j+1] == '#'); ++j);
			R[m] = j-1;
		}
		int now = 1, ans = 0;
		for(int x = 1; x <= m; ++x) {
			--now; if(now < 0) break; //減去 *## 放在前面的那個*
			bool flg = 0; //用來判斷前面是否有 *# 的標記
			for(int i = L[x]; i <= R[x]; ++i) {
				if(s[i] == '*') {
					ans = max(ans, ++now);
					if(now >= 2 || now == 1 && flg) break;
				}
				if(s[i] == '#') {
					if(s[i+1] == '*') { flg = 0; continue; } //#* 的情況 清標記
					--now; flg = 1; //*# 的情況 加標記
					if(now < 0) break;
				}
			}
			if(now >= 2 || now == 1 && flg) now = sum[R[x]] - (x-1); //判斷能否如履平地
			ans = max(ans, now);
		}
		printf("%d\n", ans);
	}
}

C. 最大子段和

題面

在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述

題解

在這裏插入圖片描述

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10005;
const int MAXn = 5005;
int N, n, K, a[MAXN], sum[MAXN], all, dp[MAXN][2], q[MAXN];
int main () {
	freopen("subsegment.in", "r", stdin);
	freopen("subsegment.out", "w", stdout);
	scanf("%d%d", &n, &K); N = 2*n-1;
	for(int i = 1; i <= N; i+=2) scanf("%d", &a[i]);
	if(a[1] < 0) a[1] = 0;
	if(a[N] < 0) a[N] = 0;
	for(int i = 1; i <= N; ++i) sum[i] = sum[i-1] + ((i&1)?a[i]:K), all += a[i];
	memset(dp, 0x3f, sizeof dp); dp[0][0] = 0;
	int now = 1, ans = 0;
	for(int j = 0; j < n; ++j) { now ^= 1;
		int l = 0, p = 0, s = 1, t = 0;
		for(int i = 1; i <= N; ++i) { dp[i][now] = 0x3f3f3f3f;
			if(a[i] < 0) l = i, p = i-1, s = 1, t = 0;
			if(l) dp[i][now] = max(dp[l-1][now], sum[i]-sum[l]);
			else dp[i][now] = sum[i];
			while(p+2 <= i && dp[p+1][now^1] + sum[p+2] <= sum[i]) p += 2;
			if(l < p) dp[i][now] = min(dp[i][now], sum[i] - sum[p]);
			if(!(i&1)) { while(s <= t && dp[q[t]-1][now^1] >= dp[i-1][now^1]) --t; q[++t] = i; }
			while(s <= t && q[s] <= p) ++s;
			if(s <= t) dp[i][now] = min(dp[q[s]-1][now^1], dp[i][now]);
		}
		ans = max(ans, (all + (n-1-j)*K - j) - dp[N][now]);
	}
	printf("%d\n", ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章