Comet OJ - Contest #3 简要题解

A

小水题。

直接枚举后排序即可。

#include <bits/stdc++.h>
using namespace std;

inline int gi()
{
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	int sum = 0;
	while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

const int maxn = 505;

int n, k, a[maxn];
int tmp[maxn * maxn], cnt;

int main()
{
	n = gi(); k = gi();
	for (int i = 1; i <= n; ++i) a[i] = gi();

	for (int i = 1; i <= n; ++i)
		for (int j = i + 1; j <= n; ++j)
			tmp[++cnt] = a[i] + a[j];

	sort(tmp + 1, tmp + cnt + 1, greater<int>());

	long long ans = 0;
	for (int i = 1; i <= k; ++i) ans += tmp[i];
	printf("%lld\n", ans);
	
	return 0;
}

B

小水题。

直接设 f i , 1 / 2 / 3 f_{i,1/2/3} fi,1/2/3分别表示填到了第 i i i列,只填了第一行/只填了第二行/两行都填了的最小代价。转移显然。

#include <bits/stdc++.h>
using namespace std;

inline int gi()
{
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	int sum = 0;
	while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

const int maxn = 200005;

int n, p[5][maxn], vec[maxn], cnt, ans;
int f[10][maxn];

int main()
{
	n = gi();
	for (int i = 1; i <= n; ++i) p[0][i] = gi();
	for (int i = 1; i <= n; ++i) p[1][i] = gi();
	
	for (int i = 1; i <= n; ++i)
		if (p[0][i] || p[1][i]) vec[++cnt] = i;

	if (!p[1][vec[1]]) {
		f[1][1] = 0;
		f[3][1] = 1;
		f[2][1] = 1e9;
	} else if (!p[2][vec[1]]) {
		f[2][1] = 0;
		f[3][1] = 1;
		f[1][1] = 1e9;
	} else f[3][1] = 0, f[2][1] = f[1][1] = 1e9;
	for (int i = 2; i <= cnt; ++i) {
		int j = vec[i];
		if (!p[1][j]) {
			f[1][i] = min(f[1][i - 1], f[3][i - 1]) + j - vec[i - 1] - 1;
			f[3][i] = min(f[1][i] + 1, min(f[2][i - 1], f[3][i - 1]) + j - vec[i - 1]);
			f[2][i] = 1e9;
		} else if (!p[0][j]) {
			f[2][i] = min(f[2][i - 1], f[3][i - 1]) + j - vec[i - 1] - 1;
			f[3][i] = min(f[2][i] + 1, min(f[1][i - 1], f[3][i - 1]) + j - vec[i - 1]);
			f[1][i] = 1e9;
		} else {
			f[3][i] = min(min(f[1][i - 1], f[2][i - 1]), f[3][i - 1]) + j - vec[i - 1] - 1;
			f[1][i] = f[2][i] = 1e9;
		}
	}
	
	printf("%d\n", min(f[3][cnt], min(f[1][cnt], f[2][cnt])));
	
	return 0;
}

C

一个序列的每个非空子序列中的整数之和为 2 l e n − 1 ∑ a i 2^{len-1} \sum{a_i} 2len1ai。现在要求这个式子 m o d m mod m modm等于 0 0 0。不妨设 m m m含有 k k k 2 2 2因子,除去这些因子得到的数为 M M M(比如当 m = 12 m=12 m=12时, k = 2 k=2 k=2 M = 3 M=3 M=3)。可以发现当 l e n &gt; k len &gt; k len>k时,我们要求 ∑ a i m o d &ThinSpace;&ThinSpace; M = 0 \sum{a_i} \mod M=0 aimodM=0即可。所以对于 l e n ≤ k len \leq k lenk的序列,我们记录长度和非空子序列之和来暴力DP。剩下的序列只需要记录序列和即可,注意要减去长度 ≤ k \leq k k的序列。

#include <bits/stdc++.h>
using namespace std;

inline int gi()
{
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	int sum = 0;
	while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

const int maxn = 5005, mod = 1e9 + 7;

int n, m, a[maxn], f[20][maxn], g[20][maxn], tmp[maxn], h[maxn];

inline int fpow(int x, int k)
{
	int res = 1;
	while (k) {
		if (k & 1) res = res * x % m;
		k >>= 1; x = x * x % m;
	}
	return res;
}

int main()
{
	n = gi(); m = gi();
	for (int i = 1; i <= n; ++i) a[i] = gi();

	int k = 0, tm = m;
	while (tm % 2 == 0) tm >>= 1, ++k;

	f[0][0] = 1;
	for(int i = 1; i <= n; ++i)
		for (int j = min(k, i); j; --j) {
			for (int k = 0; k < m; ++k) {
				f[j][k] += f[j - 1][k >= a[i] ? k - a[i] : k - a[i] + m];
				if (f[j][k] >= mod) f[j][k] -= mod;
			}
		}

	for (int i = 1; i <= n; ++i) a[i] %= tm;
	
	g[0][0] = 1; h[0] = 1;
	for(int i = 1; i <= n; ++i) {
		for (int j = min(k, i); j; --j)
			for (int k = 0; k < tm; ++k) {
				g[j][k] += g[j - 1][k >= a[i] ? k - a[i] : k - a[i] + tm];
				if (g[j][k] >= mod) g[j][k] -= mod;
			}
		for (int k = 0; k < tm; ++k) tmp[k] = h[k];
		for (int k = 0; k < tm; ++k) {
			h[k] += tmp[k >= a[i] ? k - a[i] : k - a[i] + tm];
			if (h[k] >= mod) h[k] -= mod;
		}
	}

	int ans = h[0];
	for (int i = 0; i <= k; ++i) {
		ans -= g[i][0];
		if (ans < 0) ans += mod;
	}

	for (int i = 1; i <= k; ++i)
		for (int j = 0; j < m; ++j)
			if (j * fpow(2, i - 1) % m == 0) {
				ans += f[i][j];
				if (ans >= mod) ans -= mod;
			}

	printf("%d\n", ans);
	
	return 0;
}

D

如果这题只要求单点修改,那么就是一道小水题。考虑设 b i = a i − 1 ⊕ a i b_i=a_{i-1} \oplus a_i bi=ai1ai,那么修改就变成了单点修改。根据简单的线性代数知识,查询 a i ( i ∈ [ l , r ] ) a_i(i \in [l,r]) ai(i[l,r])组成的线性基等价于查询 a l ∪ b i ( i ∈ [ l + 1 , r ] ) a_l \cup b_i(i \in [l+1,r]) albi(i[l+1,r])组成的线性基。

p.s. 这道题有双倍经验 C F 587 E CF587E CF587E

#include <bits/stdc++.h>
using namespace std;

inline int gi()
{
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	int sum = 0;
	while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

const int maxn = 200005;

int n, q, a[maxn], b[maxn], sum[maxn];
int p[maxn * 4][30], tmp[30];

#define lch (s << 1)
#define rch (s << 1 | 1)
#define mid ((l + r) >> 1)

void merge(int *p, int *p1, int *p2)
{
	memcpy(p, p1, sizeof(int) * 30);
	for (int i = 29, v; ~i; --i) {
		if (!p2[i]) continue;
		v = p2[i];
		for (int j = 29; ~j; --j)
			if ((v >> j) & 1) {
				if (!p[j]) {p[j] = v; break;}
				else v ^= p[j];
			}
	}
}

void build(int s, int l, int r)
{
	if (l == r) {
		for (int i = 29; ~i; --i)
			if ((a[l] >> i) & 1) return p[s][i] = a[l], void();
		return ;
	}
	build(lch, l, mid);
	build(rch, mid + 1, r);
	merge(p[s], p[lch], p[rch]);
}

void modify(int s, int l, int r, int x, int v)
{
	if (l == r) {
		a[l] ^= v;
		memset(p[s], 0, sizeof(p[s]));
		for (int i = 29; ~i; --i)
			if ((a[l] >> i) & 1) return p[s][i] = a[l], void();
		return ;
	}
	if (x <= mid) modify(lch, l, mid, x, v);
	else modify(rch, mid + 1, r, x, v);
	merge(p[s], p[lch], p[rch]);
}

void query(int s, int l, int r, int ql, int qr, int *tmp)
{
	if (ql <= l && r <= qr) return merge(tmp, tmp, p[s]);
	if (ql <= mid) query(lch, l, mid, ql, qr, tmp);
	if (qr > mid) query(rch, mid + 1, r, ql, qr, tmp);
}

void insert(int x, int v)
{
	while (x <= n) {
		sum[x] ^= v;
		x += x & (-x);
	}
}

int query(int x)
{
	int res = 0;
	while (x) {
		res ^= sum[x];
		x -= x & (-x);
	}
	return res;
}

int main()
{
	n = gi(); q = gi();
	for (int i = 1; i <= n; ++i)
		b[i] = gi(), insert(i, b[i]), insert(i + 1, b[i]);
	for (int i = n; i >= 1; --i) a[i] = b[i] ^ b[i - 1];
	
	build(1, 1, n);

	int op, l, r, v;
	while (q--) {
		op = gi(); l = gi(); r = gi(); 
		if (op == 1) {
			v = gi();
			modify(1, 1, n, l, v);
			if (r < n) modify(1, 1, n, r + 1, v);
			insert(l, v); insert(r + 1, v);
		} else {
			memset(tmp, 0, sizeof(tmp));
			if (l < r) query(1, 1, n, l + 1, r, tmp);
			int ans = gi(), v = query(l);
			for (int j = 29; ~j; --j)
				if ((v >> j) & 1) {
					if (!tmp[j]) {tmp[j] = v; break;}
					else v ^= tmp[j];
				}
			for (int i = 29; ~i; --i)
				if ((ans ^ tmp[i]) > ans) ans ^= tmp[i];
			printf("%d\n", ans);
		}
	}
	
	return 0;
}

E

先把整除变成一般乘法。

( x − x m o d &ThinSpace;&ThinSpace; i ) ÷ i × j = ( x × j − x × j m o d &ThinSpace;&ThinSpace; i ) ÷ i (x-x \mod i) \div i \times j = (x \times j - x \times j \mod i) \div i (xxmodi)÷i×j=(x×jx×jmodi)÷i
( x − x m o d &ThinSpace;&ThinSpace; i ) × j = x × j − x × j m o d &ThinSpace;&ThinSpace; i (x-x \mod i) \times j = x \times j - x \times j \mod i (xxmodi)×j=x×jx×jmodi
x m o d &ThinSpace;&ThinSpace; i × j = x × j m o d &ThinSpace;&ThinSpace; i x \mod i \times j = x \times j \mod i xmodi×j=x×jmodi
x m o d &ThinSpace;&ThinSpace; i × j = x m o d &ThinSpace;&ThinSpace; i × j m o d &ThinSpace;&ThinSpace; i x \mod i \times j = x \mod i \times j \mod i xmodi×j=xmodi×jmodi
v = x m o d &ThinSpace;&ThinSpace; i × j v=x \mod i \times j v=xmodi×j,则上式可化为 v = v m o d &ThinSpace;&ThinSpace; i v=v \mod i v=vmodi,即 v &lt; i v &lt; i v<i

所以 x m o d &ThinSpace;&ThinSpace; i × j &lt; i x \mod i \times j &lt; i xmodi×j<i,所以 j &lt; ⌈ i x m o d &ThinSpace;&ThinSpace; i ⌉ j &lt; \lceil \frac {i} {x \mod i} \rceil j<xmodii

所以答案为 ∑ i = 1 a m i n ( b , ⌈ i x m o d &ThinSpace;&ThinSpace; i ⌉ − 1 ) \sum_{i=1}^a min(b,\lceil \frac {i} {x \mod i} \rceil - 1) i=1amin(b,xmodii1)

分两种情况考虑。

第一种情况, i &gt; x i &gt; x i>x,那么 x m o d &ThinSpace;&ThinSpace; i = x x \mod i = x xmodi=x

所求为 ∑ i = x + 1 a m i n ( b , ⌈ i x m o d &ThinSpace;&ThinSpace; i ⌉ − 1 ) \sum_{i=x+1}^a min(b, \lceil \frac {i} {x \mod i} \rceil - 1) i=x+1amin(b,xmodii1),直接推下式子 O ( 1 ) O(1) O(1)算即可。

第二种情况, i ≤ x i \leq x ix

考虑到 x m o d &ThinSpace;&ThinSpace; i = x − ⌊ x i ⌋ × i x \mod i = x - \lfloor \frac {x} {i} \rfloor \times i xmodi=xix×i

所以先固定 ⌊ x i ⌋ \lfloor \frac {x} {i} \rfloor ix,然后固定 x x − ⌊ i i ⌋ × i \frac {x} {x - \lfloor \frac {i} {i} \rfloor \times i} xii×ix。那么满足二元组 ( ⌊ x i ⌋ , x x − ⌊ i i ⌋ × i ) (\lfloor \frac {x} {i} \rfloor,\frac {x} {x - \lfloor \frac {i} {i} \rfloor \times i}) (ix,xii×ix)相等的一定是一段区间。可以证明,区间数是 O ( n l o g n ) O(\sqrt n logn) O(n logn)的,那么暴力数论分块算即可。

设当前二元组是 ( c , q ) (c,q) (c,q),下面介绍求当前二元组的右端点的方法。

⌊ i x − c i ⌋ ≤ q \lfloor \frac {i} {x-ci} \rfloor \leq q xciiq

i ≤ q ( x − c i ) i \leq q(x-ci) iq(xci)

i ≤ q x q + 1 i \leq \frac {qx} {q+1} iq+1qx

i ≤ ⌊ q x q + 1 ⌋ i \leq \lfloor \frac {qx} {q+1} \rfloor iq+1qx

所以右端点为 ⌊ q x q + 1 ⌋ \lfloor \frac {qx} {q+1} \rfloor q+1qx

#include <bits/stdc++.h>
using namespace std;

inline int gi()
{
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	int sum = 0;
	while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

typedef long long ll;
#define int ll

int x, a, b;
ll res, ans;

ll calc(int l, int r, int c)
{
	ll res = 0;
	if (x % r == 0) res += b, --r;
	for (int i = l, j, q; i <= r; i = j + 1) {
		q = (i + x - (ll)c * i - 1) / (x - (ll)c * i);
		j = min((ll)r, (ll)q * x / ((ll)c * q + 1));
		res += (ll)(j - i + 1) * min(b, q - 1);
	}
	return res;
}

signed main()
{
	int T = gi();
	while (T--) {
		x = gi(); a = gi(); b = gi(); ans = 0;
		if (a > x) {
			int y = min(a, b * x + 1);
			ans += (a - y) * b; --y;
			int tmp = y / x - 1;
			ans += (ll)tmp * (tmp + 1) / 2 * x + (y % x + 1) * (y / x);
			a = x;
		}
		for (int i = 1, j; i <= a; i = j + 1) {
			j = min(a, x / (x / i));
			ans += calc(i, j, x / i);
		}
		printf("%lld\n", ans);
	}
	
	return 0;
}

F

分块什么的,咕掉吧。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值