CF1439 Codeforces Round #684 (Div. 1)

CF 1439 Codeforces Round #684 (Div. 1)

A1

题意

一个0 / 1二维矩阵,每次操作可以选一个四宫格,并将其中三个数取反,找到在3nm步骤内将矩阵变成全0矩阵的方法。

思路

在这里插入图片描述
如果执行上图中的三个操作,四宫格内只有一个位置被操作了奇数次,其他位置都被操作了偶数次,因而相当于没有改变。因此,只需对每一个为1的位置都进行类似的操作就可以了。

A2

题意

一个0 / 1二维矩阵,每次操作可以选一个四宫格,并将其中三个数取反,找到在nm步骤内将矩阵变成全0矩阵的方法。

思路

  1. 考虑一个四宫格内,如何将步数减少到4步以内。在一个四宫格内共有四种不同的操作。我们发现,按照上面的思路分别将四宫格内的数全部变为0时,有些操作是重复出现的。重复偶数次的步骤相当于不做,因此可以统计每一种操作出现的次数,然后只做出现奇数次的操作。这样,处理一个四宫格内的数最多4次就可以了。
  2. 考虑只有n* 2(即只有两列)的情况。对于前n-2行,如果这一行有一个1,那么就选择这一个1和下一行的两列做一次操作;如果这一行有两个1,那么就选择这两个1和下一行的一个位置做一次操作;如果没有1,那么就无需做操作了。这样就可以将n * 2的棋盘最终缩小成四宫格。
  3. 对于nm的棋盘,先处理其中的(n-2)(m-2)的位置,如果mp[i][j]是1,就将mp[i][j],mp[i+1][j],mp[i][j+1]同时操作。这样就可以将棋盘缩成两行+两列,然后,将最后的两行/列分别按照2.中的方法消除,最终变成一个四宫格。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N = 110; 
struct NODE{
	int x1, y1, x2, y2, x3, y3;
	NODE(int a = 0, int b = 0, int c = 0, int d = 0, int e = 0, int f = 0)
	{
		x1 = a;
		y1 = b;
		x2 = c;
		y2 = d;
		x3 = e;
		y3 = f;
	}
};
int mp[N][N];
char s[N][N];
int cnt[5];
vector<NODE>ans;
int main()
{
	int T;
	scanf("%d", &T);
	while(T)
	{
		T--;
		ans.clear();
		int n, m;
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= n; i++)
			scanf("%s", s[i] + 1);
		for(int i = 1 ; i <= n; i++)
			for(int j = 1; j <= m; j++)
				mp[i][j] = s[i][j] - '0';
		for(int i = 1; i <= n - 2; i ++)
		{
			for(int j = 1; j <= m - 2; j++)
			{
				if(mp[i][j])
				{
					ans.push_back(NODE(i, j, i + 1, j, i, j + 1));
					mp[i][j] ^= 1;
					mp[i][j + 1] ^= 1;
					mp[i + 1][j] ^= 1;
				}
			} 
		}
		for(int i = 1; i <= n - 2; i++)
		{
			if(mp[i][m - 1] && mp[i][m])
			{
				ans.push_back(NODE(i, m - 1, i, m, i + 1, m - 1));
				mp[i][m - 1] ^= 1;
				mp[i][m] ^= 1;
				mp[i + 1][m - 1] ^= 1;	
			}
			else
			{
				if(mp[i][m - 1])
				{
					ans.push_back(NODE(i, m - 1, i + 1, m - 1, i + 1, m));
					mp[i][m - 1] ^= 1;
					mp[i + 1][m - 1] ^= 1;
					mp[i + 1][m] ^= 1;
				}
				else
				{
					if(mp[i][m])
					{
						ans.push_back(NODE(i, m, i + 1, m - 1, i + 1, m));
						mp[i][m] ^= 1;
						mp[i + 1][m - 1] ^= 1;
						mp[i + 1][m] ^= 1;
					} 
				} 
			} 
		}
		for(int j = 1; j <= m - 2; j++)
		{
			if(mp[n - 1][j] && mp[n][j])
			{
				ans.push_back(NODE(n - 1, j, n, j, n - 1, j + 1));
				mp[n - 1][j] ^= 1;
				mp[n][j] ^= 1;
				mp[n - 1][j + 1] ^= 1;
			}
			else
			{
				if(mp[n - 1][j])
				{
					ans.push_back(NODE(n - 1, j, n - 1, j + 1, n, j + 1));
					mp[n - 1][j] ^= 1;
					mp[n - 1][j + 1] ^= 1;
					mp[n][j + 1] ^= 1;
				}
				else
				{
					if(mp[n][j])
					{
						ans.push_back(NODE(n, j, n - 1, j + 1, n, j + 1));
						mp[n][j] ^= 1;
						mp[n - 1][j + 1] ^= 1;
						mp[n][j + 1] ^= 1;
					}
				}
			}
		}
		cnt[1] = cnt[2] = cnt[3] = cnt[4] = 0;
		if(mp[n - 1][m - 1])
		{
			cnt[1]++;
			cnt[2]++;
			cnt[3]++;
		}
		if(mp[n - 1][m])
		{
			cnt[1]++;
			cnt[2]++;
			cnt[4]++;
		}
		if(mp[n][m - 1])
		{
			cnt[1]++;
			cnt[3]++;
			cnt[4]++;
		}
		if(mp[n][m])
		{
			cnt[2]++;
			cnt[3]++;
			cnt[4]++; 
		} 
		if(cnt[1] % 2)
		{
			ans.push_back(NODE(n - 1, m - 1, n, m - 1, n - 1, m));
			mp[n - 1][m - 1] ^= 1;
			mp[n][m - 1] ^= 1;
			mp[n - 1][m] ^= 1;
		}
		if(cnt[2] % 2)
		{
			ans.push_back(NODE(n - 1, m - 1, n - 1, m, n, m));
			mp[n - 1][m - 1] ^= 1;
			mp[n - 1][m] ^= 1;
			mp[n][m] ^= 1; 
		}
		if(cnt[3] % 2)
		{
			ans.push_back(NODE(n - 1, m - 1, n, m - 1, n, m));
			mp[n - 1][m - 1] ^= 1;
			mp[n][m - 1] ^= 1;
			mp[n][m] ^= 1;
		} 
		if(cnt[4] % 2)
		{
			ans.push_back(NODE(n - 1, m, n, m - 1, n, m));
			mp[n - 1][m] ^= 1;
			mp[n][m - 1] ^= 1;
			mp[n][m] ^= 1;
		}
		printf("%d\n", ans.size());
		for(int i = 0; i < ans.size(); i++)
			printf("%d %d %d %d %d %d\n", ans[i].x1, ans[i].y1, ans[i].x2, ans[i].y2, ans[i].x3, ans[i].y3);
	}	
	return 0;
}

C - Greedy Shopping

题意

n家商店排成一列,价格 a i a_{i} ai是一个单调不增的序列。有两种操作:

  1. 将1~x家商店的价格改成 m a x ( a i , y ) max(a_{i}, y) max(ai,y)
  2. 总y元,从第x家商店开始购物,走到第i家商店时,如果剩下的钱足够 a i a_{i} ai,则消费 a i a_{i} ai元,否则走到下一家,求出一个可以在多少家商店购物。

思路

线段树。
对于1操作,先找出第一个比y小的数的位置pos,如果pos <= x,则修改[pos, x]区间为y。为防止找不到比y小的数,可以在末尾加一个0。
对于2操作,记录区间和,如果区间和小于当前剩余的钱数,答案加上区间长度;否则,继续二分。但是这样处理,遍历到的区间数最大可达到 n 2 \frac{n}{2} 2n,TLE。考虑最坏的情况,即每隔一家商店购物一次的情况。此时,设走到[i, i + 1]区间, y ≤ a i + a i + 1 ≤ 2 a i y \leq a_{i} + a_{i + 1} \leq 2a_{i} yai+ai+12ai,则有 y − a i ≤ y 2 y-a_{i} \leq \frac{y}{2} yai2y,也就是说,每次走到一个可以购物的区间,y至少减少一半。那么总共的可购物区间不超过 l o g 2 y log_{2}{y} log2y个,如果只对可购物区间进行询问,那么时间复杂度会大大降低。因此,在二分区间之前,先判断这个区间的最小值是否小于钱数,如果小于,则继续二分区间,否则不能在该区间购物。总时间复杂度为O( q ∗ l o g 2 n ∗ l o g 2 y q * log_{2}n * log_{2}y qlog2nlog2y)。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int val[N * 4], mina[N * 4];
LL sum[N * 4];
bool flag[N * 4];
int a[N];
void pushdown(int x, int l, int r)
{
	int mid = (l + r) >> 1;
	if(flag[x])
	{
		val[x * 2] = val[x * 2 + 1] = val[x];
		mina[x * 2] = mina[x * 2 + 1] = mina[x];  
		sum[x * 2] = (LL)val[x] * (LL)(mid - l + 1);
		sum[x * 2 + 1] = (LL)val[x] * (LL)(r - mid); 
		flag[x * 2] = flag[x * 2 + 1] = true;
		flag[x] = false;
	 } 
}
void pushup(int x)
{
	sum[x] = sum[x * 2] + sum[x * 2 + 1];
	mina[x] = mina[x * 2 + 1];
}
void build(int x, int l, int r)
{
	if(l == r)
	{
		val[x] = a[l];
		sum[x] = a[l];
		mina[x] = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(x * 2, l, mid);
	build(x * 2 + 1, mid + 1, r); 
	pushup(x);
}
int get_lower(int x, int l, int r, int v)
{
	if(l == r)
		return l;
	int mid = (l + r) >> 1;
	pushdown(x, l, r);
	if(mina[x * 2] >= v)
		return get_lower(x * 2 + 1, mid + 1, r, v);
	return get_lower(x * 2, l, mid, v); 
}
void update(int x, int l, int r, int ll, int rr, int v)
{
	if(r < ll || rr < l)
		return;
	if(ll <= l && r <= rr)
	{
		flag[x] = true;
		val[x] = v;
		mina[x] = v;
		sum[x] = (LL)v * (LL)(r - l + 1);
		return;
	}
	pushdown(x, l, r);
	int mid = (l + r) >> 1;
	update(x * 2, l, mid, ll, rr, v);
	update(x * 2 + 1, mid + 1, r, ll, rr, v);
	pushup(x);
} 
int query(int x, int l, int r, int ll, int rr, int *v)
{
	if(r < ll || rr < l || mina[x] > (*v))
		return 0;
	if(ll <= l && r <= rr && sum[x] <= (LL)(*v))
	{
		(*v) -= sum[x];
		return r - l + 1;
	}
	if(l == r) return 0;
	pushdown(x, l, r);
	int mid = (l + r) >> 1;
	int ans = query(x * 2, l, mid, ll, rr, v);
	ans += query(x * 2 + 1, mid + 1, r, ll, rr, v);
	return ans;
}
int main()
{
	int n, q;
	scanf("%d%d", &n, &q);
	for(int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	build(1, 1, n + 1);
	for(int i = 1; i <= q; i++)
	{
		int opt, x, y;
		scanf("%d%d%d", &opt, &x, &y);
		if(opt == 1)
		{
			int pos = get_lower(1, 1, n + 1, y);
			if(pos <= x)
				update(1, 1, n + 1, pos, x, y);
		} 
		else
		 	printf("%d\n", query(1, 1, n + 1, x, n, &y));
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值