Codeforces Round 900 (Div. 3) D-E

D. Reverse Madness

D. Reverse Madness

题意

我们将一个字符串 s s s,分为连续的 k k k个部分,其中每一个部分的边界是 [ l [ i ] , r [ i ] ] [l[i], r[i]] [l[i],r[i]],现在给出q个查询,每个查询给出一个x,要求找到唯一一个满足 l [ i ] ≤ x ≤ r [ i ] l[i] \le x \le r[i] l[i]xr[i]的区间,并翻转 [ m i n ( x , l i + r i , − x ) , m a x ( x , l i + r i − x ) ] [min(x, l_i + r_i, - x), max(x, l_i + r_i - x)] [min(x,li+ri,x),max(x,li+rix)]

分析

由于k个区间是不相交的,所以显然x只属于一个区间,所以可以通过二分来找到x所在的区间,其次可以发现,对于每一个翻转操作,都是以区间中点为对称轴,x为一个边界做翻转,即每一个位置如果要翻转,那么就会课它对称的位置做交换,并且不会影响到其他 k − 1 k-1 k1个区间。
所以,如果一个位置被翻转偶数次,就相当于没有翻转。

题解

利用差分算法,每次对一个翻转的区间做标记,之后对字符串分成的 k k k个部分分别讨论,如果某个位置被翻转奇数次,那么就将这个位置翻转。
时间复杂度 O ( q l o g n ) O(qlogn) O(qlogn)

代码

void solve()
{
	int n, k;
	cin >> n >> k;
	string s;
	cin >> s;
	s = ' ' + s;
	vector<int> l(k), r(k);
	for (int i = 0; i < k; i++) cin >> l[i];
	for (int i = 0; i < k; i++) cin >> r[i];
	
	int q;
	cin >> q;
	vector<int> f(n+5, 0);
	while (q --)
	{
		int x;
		cin >> x;

		auto xx = upper_bound(l.begin(), l.end(), x);//指导大于x的第一个位置
		xx--;//--之后就找到了符合条件的区间
		int t = xx - l.begin();	
		int a = min(x, l[t] + r[t] - x), b = max(x, l[t] + r[t] - x);

		f[a] += 1;//差分
		f[b+1] -= 1;
	}
	
	for (int i = 1; i <= n; i++) f[i] += f[i-1];

	for (int i = 0; i < k; i++)//对每一个部分单独讨论
	{
		for (int j = l[i]; j <= (r[i] + l[i]) / 2; j++)
			if (f[j] % 2) 
			{
				swap(s[j], s[r[i] - (j - l[i])]);//如果被翻转奇数次,就将这个位置和其关于区间中点的对称位置做交换
			}
	}
	
	s.erase(s.begin());
	cout << s << endl;
    //    cout << fixed << setprecision(1);
}

E. Iva & Pav

E. Iva & Pav

题意

给定一个长度为 n n n的数组,给出q个询问,每个询问给出 l , k l, k l,k要求找到最大的右边界 r r r使得区间 [ l , r ] [l, r] [l,r]的按位与结果大于等于 k k k

分析

首先,对于按位与运算来说,只有两个数的一位二进制位都是1才会是1,所以其产生的区间结果一定是单调不增的,即最后最后得到的 r r r满足条件则 r + 1 r+1 r+1一定不满足条件。
其次对于一个区间 [ a , b ] [a, b] [a,b],考虑一个数的二进制第 x x x位,只有区间内所有的数在第 x x x位有 b − a + 1 b - a + 1 ba+1 1 1 1 即所有数在第x位都有1,最后得到的按位与结果第 x x x位才是1。

题解

由于存在单调性,可以考虑二分算法,二分找到 r r r的位置。其二分的check函数可以利用前缀和算法,预处理出区间内,每一个二进制位总共含有的1的数量,如果该二进制位在该区间内共含有 r − l + 1 r - l + 1 rl+1个1,则 a n s + = ( 1 < < i ) ans += (1 << i) ans+=(1<<i),最后判断 a n s ≤ k ans \le k ansk 即可

代码

bool check(vector<vector<int>> &s,int k, int l, int r)
{
	int sum = 0;
	for (int i = 0; i < 31; i++)
	{
		if (s[r][i] - s[l-1][i] == r - l + 1) sum += (1 << i);
	}
	return sum >= k;
}

void solve()
{
	int n;
	cin >> n;
	vector<int> a(n + 1);
	for (int i = 1; i <= n; i++) cin >> a[i];
	
	vector<vector<int>> s(n + 1, vector<int>(35, 0));//前缀和数组 记录区间每一个二进制位总共含有1的数量
	
	for (int i = 1; i <= n; i++)
		for (int j = 0; j < 31; j++)
		{
			s[i][j] = s[i-1][j] + (a[i] >> j & 1);
		}
	
	int q;
	cin >> q;
	while (q --)
	{
		int a, k;
		cin >> a >> k;
		
		int l = a, r = n;

		while (l < r)
		{
			int mid = (l + r + 1) >> 1;
			if (check(s, k, a, mid)) l = mid;
			else r = mid - 1;
		}
		

		if (check(s, k, a, r)) cout << r << ' ';
		else cout << -1 << ' ';
	}
	
	cout << endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值