0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和

A.随机除法(div)

题面

0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第1张图片
0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第2张图片在这里插入图片描述
0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第3张图片

题解

0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第4张图片
因为n很大,所以手写高精。

然后代码中前缀和数组的定义与上面的题解有一点不一样(而且上面的题解好像把 j j j写成 i i i了)。含义如下

s u m [ e ] [ i ] = ∑ e ′ ( ∏ j < i [ e j ′ = e j ] ∏ j ≥ i [ e j ′ ≤ e j ] d p [ e ′ ] ) sum[e][i]=\sum_{e'}\left(\prod_{jsum[e][i]=e(j<i[ej=ej]ji[ejej]dp[e])

#include 
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)

题面

0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第5张图片0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第6张图片
0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第7张图片

题解

考试时以为写了正解,其实就差了一个情况没考虑,只有40…

发现只要出现连续3个 # \# #," # # # \#\#\# ###"那么一定无法走过去。就可以不管后面的了。

那么现在考虑连续2个 # \# #,如果要走过去,一定是" # # ∗ \#\#* ##",然后在前面放一个 ∗ * 才能走过去。

如果是1个 # \# #,要么是" # ∗ \#* #"直接走过去,要么就在 # \# #前面放一个 ∗ * 再走过去。

而且可以发现,如果手里有2个 ∗ * ,走过1个 # \# #对我们来说其实是“如履平地”的。因为可以在前面放个,然后过去在后面放个,再走回来取走前面的 ∗ * ,这样我们没有损失。

然后对于连续2个 # \# #的情况我们必须要留一个 ∗ * 在原地。

那么我们用" # # \#\# ##"分割整个序列为若干块,从左往右走,如果手里的 ∗ * 能达到2块,那么我们就能畅通无阻地走过 # \# #。所以从最开始的块到当前块的所有 ∗ * 都能拿到,但还要减去 # # \#\# ##的代价。

如果向右走走不动了就break就行了。答案就是手里的 ∗ * max ⁡ \max max

但还有一种情况未考虑,就是 ∗ # . . . ∗ *\#...* #...这种情况,我们如果只往右走,手里的 ∗ * 数不能达到2个,但是可以往回走取掉前面的 ∗ # *\# # ∗ * 。这种情况只需要设一个变量作为标记就行了。

具体细节可以看代码。

#include 
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. 最大子段和

题面

0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第8张图片
0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第9张图片0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第10张图片

题解

0607模拟赛 随机除法(div) 炮塔(tower) 最大子段和_第11张图片

#include 
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);
}

你可能感兴趣的:(模拟赛题解)