Codeforces Round #833 (Div. 2) A~E

6 problems, 3 accepted.
2h.
vp.

A. The Ultimate Square

猜结论 - AC

看样例猜结论。

cin >> n;
cout << (n + 1) / 2 << "\n";

B. Diverse Substrings

枚举 - AC

最多10种数,所以一个diverse substring长度最多是100。
枚举,时间复杂度 O ( 100 n ) O(100n) O(100n)

正当我以为找出了标算,我突然发现,有最多 1 0 4 10^4 104组数据。那么,上面的算法就会超时了。
1 0 4 10^4 104优化不了,而 n ≤ 1 0 5 n\leq 10^5 n105,这怎么想都是不可能完成的。

想了半天,又突然发现,

It is guaranteed that the sum of n over all test cases does not exceed 10^5.

白想了。

while (tt--) {
	ans = 0;
	
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		char ch; cin >> ch;
		a[i] = ch - '0';
	}
	
	for (int i = 1; i <= n; ++i) {
		map<int, int> vh; int cnt = 0, mx = 0;
		for (int j = i; j && i - j + 1 <= 100; --j) {
			if (vh[a[j]] == 0) ++cnt;
			ckmax(mx, ++vh[a[j]]);
			if (mx <= cnt) ++ans;
		}
	}
	cout << ans << "\n";
}

C. Zero-Sum Prefixes

分析 - AC

0随便变。

一开始想,后面的位置的前缀和,会受到前面很多0的影响。难搞。
后来一想,不如在每个0,先把它的前缀和变成0,这就相当于删去了前面的所有数。具体地,把1 2 0 3 4先变成1 2 -3 3 4,不考虑前面的1 2,就相当于0 3 4了。
那么,把序列根据0,分割成几个互相独立的部分。2 0 1 -1 0 3变成20 1 -10 3。它们每个里面只有一个0,求前缀和是0的个数的最大值,简单,不讲。

可是我一开始却WA了,看了将近10min没找出来错误。后来花20min把后三题都大致看了一遍,又回来,发现没开long long

while (tt--) {
		m = 0, ans = 0;
		
		cin >> n;
		vector<ll>().swap(a[++m]);
		for (int i = 1; i <= n; ++i) {
			ll tmp; cin >> tmp;
			if (!tmp) vector<ll>().swap(a[++m]);
			a[m].push_back(tmp);
		}
		
		ll s = 0;
		for (int i : a[1]) {
			s += i;
			if (!s) ++ans;
		}
		for (int i = 2; i <= m; ++i) {
			map<ll, int> vh; int mx = 0; s = 0;
			for (int j : a[i]) {
				s += j;
				ckmax(mx, ++vh[s]);
			}
			ans += mx;
		}
		cout << ans << "\n";
	}

D. ConstructOR

分析 - AC(补)

不如让 x = x ∣ a = x ∣ b x=x|a=x|b x=xa=xb.
那么,问题就转化为,把 a ∣ b a|b ab的某些0变成1,使它变成d的倍数。
d的倍数, k d kd kd,k可以二进制表示。也就是说,二进制下,d的倍数是几个左移不同位数的d加起来的。
比如 a ∣ b = ( 101111 ) 2 , d = ( 101 ) 2 a|b=(101111)_2,d=(101)_2 ab=(101111)2,d=(101)2,从低位往高位,算 x x x
在这里插入图片描述
a ∣ b a|b ab本来的1不能变,根据这个条件,把x用d凑出来。

而无解的情况,即d末尾0个数比 a ∣ b a|b ab末尾0个数多。此时,d就够不到 a ∣ b a|b ab最低的1了。
在这里插入图片描述

int tt;
ll a, b, d;
 
ll lowbit(ll x) { return x & -x; }
 
int main() {
	setIO("");
	
	cin >> tt;
	
	while (tt--) {
		cin >> a >> b >> d;
		
		a |= b;
		if (lowbit(d) > lowbit(a)) {
			cout << -1 << "\n";
			continue;
		}
		ll lb = lowbit(d), x = 0;
		d /= lb; a /= lb;
		for (int i = 0; (x & a) != a; ++i) {
			if ((a & (1 << i)) && !(x & (1 << i))) {
				x += d << i;
			}
		}
		cout << x * lb << "\n";
	}
	
	return 0;
}

E. Yet Another Array Counting Problem

RMQ、分治、树形dp - AC(赛后)

下称leftmost maximum为lm。

对于 [ l , r ] [l,r] [l,r],本题的条件只与lm有关,我们只能看lm了。
根据题意,若lm位置是mid,数组b中,在 [ l , r ] [l,r] [l,r]上,mid上的数大于所有它左边的数,大于等于所有它右边的数。
我们该看哪个区间的lm呢?先看 [ 1 , n ] [1,n] [1,n]的吧。根据上一行得到一个对b的约束条件。这显然不是全部。

不难发现一个性质:含有mid的 [ l , r ] [l,r] [l,r]的子区间的lm位置也是mid。就像全年级第一同样是全班第一。
看完 [ 1 , n ] [1,n] [1,n],如果再看一个包含mid的子区间,得到的条件不会变多。所以,下面应该看 [ 1 , m i d − 1 ] , [ m i d + 1 , n ] [1,mid-1],[mid+1,n] [1,mid1],[mid+1,n]。像是分治算法。
对了,刚刚说,“在 [ l , r ] [l,r] [l,r]上,mid上的数大于所有它左边的数,大于等于所有它右边的数”,事实上,只需要知道mid大于左边子区间的lm,大于等于右边子区间的lm,就算是知道了所有。

综合上面的想法,算法便明晰了:不断拆分大区间,建二叉树,在树上做dp。
lm相当于RMQ问题,用st表。
定义 d p ( u , i ) dp(u,i) dp(u,i),点u的值最大是i,方案数。 d p ( u , i ) = d p ( l c , i − 1 ) × d p ( r c , i ) + d p ( u , i − 1 ) dp(u,i)=dp(lc,i-1)\times dp(rc,i)+dp(u,i-1) dp(u,i)=dp(lc,i1)×dp(rc,i)+dp(u,i1).


比赛时想出了标算,顿时感到这真是一道好题。

然而此时离比赛结束只剩10min了,没时间写代码。事实上,在查出C题的错误后,我有1h时间看剩下三题,但是自以为能够攻克D题,所以把大部分时间花在了D上,事实上,D这样的二进制题我做的并不多。而E对我来说长得倒是更好看一点,但我没有留太多时间

另外,由于数据范围,本题要用vector<vector<int> >注意代码中对resize的使用以及初始化方法vector<int>[]也行。

int tt, n, m, a[MAXN], logn[MAXN], st[MAXN][MAXP];
vector<vector<ll> > dp;
 
int build(int l, int r) {
	if (l > r) return 0;
	int p = logn[r - l + 1], mid;
	if (a[st[l][p]] >= a[st[r - (1 << p) + 1][p]]) mid = st[l][p];
	else mid = st[r - (1 << p) + 1][p];
	int lc = build(l, mid - 1), rc = build(mid + 1, r);
	for (int i = 1; i <= m; ++i) {
		dp[mid][i] = mod(mod(dp[lc][i - 1] * dp[rc][i]) + dp[mid][i - 1]);
	}
	return mid;
}
 
int main() {
	setIO("");
	
	cin >> tt;
	
	while (tt--) {
		cin >> n >> m;
		dp.resize(n + 1); dp[0].resize(m + 1);
		for (int i = 1; i <= n; ++i) {
			cin >> a[i];
			dp[i].resize(m + 1);
			for (int j = 0; j <= m; ++j) dp[i][j] = 0;
		}
		
		logn[1] = 0;
		for (int i = 2; i <= n; ++i) {
			logn[i] = logn[i / 2] + 1;
		}
		
		for (int i = 1; i <= n; ++i) st[i][0] = i;
		for (int j = 1; j < MAXP; ++j) {
			for (int i = 1; i <= n - (1 << j) + 1; ++i) {
				if (a[st[i][j - 1]] >= a[st[i + (1 << j - 1)][j - 1]]) st[i][j] = st[i][j - 1];
				else st[i][j] = st[i + (1 << j - 1)][j - 1];
			}
		}
		
		for (int i = 0; i <= m; ++i) dp[0][i] = 1;
		int rt = build(1, n);
		cout << dp[rt][m] << "\n";
	}
	
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值