26寒假营第五场 区间覆盖贪心|合并果子|子段和取模|贪心+dp|不变量|二阶差分

智乃的二进制

https://ac.nowcoder.com/acm/contest/120565/A

B

模拟 相邻的反转 注意反斜杠需要转义

int a[200][200];
void solve() {
	int n, m;
	cin >> n >> m;

	rep(i, 1, n) {
		rep(j, 1, m) {
			if (j == 1) {
				if (i == 1) {
					a[i][j] = 1;
				} else {
					a[i][j] = a[i - 1][j] ^ 1;
				}
			} else {
				a[i][j] = a[i][j - 1] ^ 1;
			}
		}
	}

	rep(i, 1, n) {
		rep(j, 1, m) {
			if (a[i][j]) {
				cout << '/';
			} else {
				cout << '\\';
			}
		}
		cout << '\n';
	}
}

C

计算几何 实数二分 区间覆盖贪心

注意到所有点都在x轴上,所以点,只有以他为圆心的圆,能覆盖一部分y=r的点时才有意义,更具体来说,一个点可能能覆盖这一段的点,形成一个区间。这个区间的半径,可以根据圆的半径和用勾股定理计算出来。

由于时间越多,圆的半径越大,每个点对应的区间越长,我们的目标是选不超过个区间覆盖整个,这显然有单调性,考虑二分时间。接下来就是一个贪心,选择最少的区间,覆盖整个

这个贪心类似跳跃游戏,选中一个区间可以看作从出发,走一步可以到内的所有点,然后下一步可以借助内任意点作为起点,问走到的最小步数。这其实也类似于寒假营第一场的那个

思路就是从开始走,走完一个前缀,维护这个前缀内的点为起点,能到的最远点,以及当前选中的最后一个区间的右端点,如果走完了当前选的最后一个区间,则需要再选一个区间,贪心的来讲,应该选目前访问过左端点的区间里,右端点最远的那个区间,于是

void solve() {
	int n, k, r, c;
	cin >> n >> k >> r >> c;

	vi p(n + 1), v(n + 1);
	rep(i, 1, n) {
		cin >> p[i] >> v[i];
	}

	db lo = 0, hi = 1e7;

	auto check = [&](db mi)->bool{
		vi bin(c + 1);
		rep(i, 1, n) {
			db R = mi * v[i];
			int d = sqrt(R * R - r * r);
			int l = max(0ll, p[i] - d);
			int r = p[i] + d;
			bin[l] = max(bin[l], r);
		}
		int mx = 0, cnt = 0, lim = 0;
		rep(i, 0, c - 1) {
			mx = max(mx, bin[i]);
			if (i == lim) {
				cnt++;
				lim = mx;
			}
		}
		return lim >= c && cnt <= k;
	};
	while (hi - lo > eps) {
		db mi = (lo + hi) / 2;
		if (check(mi))hi = mi;
		else lo = mi;
	}
	cout << fixed << setprecision(10);
	cout << lo;

}

D

贪心 map模拟

合并果子的基础上,改成每种重量的果子有个。整体思想还是每次合并最小的两个,但是为了保证复杂度需要每次把重量最小的两类果子都拿出来,一块合并。

由于这两堆果子个数可能不相等,需要分类讨论,可能剩下一类果子还有一些没用到,还要插入回去。以及如果重量最小的果子不止一个,肯定时这一类果子内部合并更优。

需要维护每个重量的果子的个数,每次查询重量最小的果子,这可以set或者map。

void solve() {
	int n;
	cin >> n;

	map<int, int>s;
	rep(i, 1, n) {
		int c, w;
		cin >> c >> w;
		s[w] += c;
	}

	int ans = 0;
	while (s.size() > 1 || s.begin()->se > 1) {
		auto [w1, c1] = *s.begin();
		s.erase(s.begin());
		if (c1 > 1) {
			s[2 * w1] += c1 / 2;
			if (c1 % 2) {
				s[w1] = 1;
			}
			ans += ((c1 / 2 * 2) % M1 * (w1 % M1)) % M1;
			ans %= M1;
//			cout << w1 << ' ' << ans << '\n';
			continue;
		}

		auto [w2, c2] = *s.begin();
		s.erase(s.begin());

		int c = min(c1, c2);
		ans += (c % M1 * ((w1 + w2) % M1)) % M1;
		ans %= M1;
		s[w1 + w2] += c;

		if (c1 > c) {
			s[w1] = c1 - c;
		}
		if (c2 > c) {
			s[w2] = c2 - c;
		}
//		cout << w1 << ' ' << w2 << ' ' << ans << '\n';
	}

	cout << ans;
}

E

取模 扫描线前缀和

子数组最大和可以扫描线前缀和,问题是,这里问的子数组和取模后的最大值,如何处理?对于子数组和取模有经典结论,首先还是维护所有前缀和,然后加上前缀和取模,那么的话,子数组元素和,取模后就是,也就是可以忽略取模,此时我们枚举确定了,想要子数组最大,找一个最小的即可;如果,那么子数组和取模后是,此时想要子数组最大,找一个大于的最小即可

  void solve() {
	int n, p;
	cin >> n >> p;

	int pre = 0, ans = -1;
	pii res;
	map<int, int>mp;
	mp[0] = 0;
	rep(i, 1, n) {
		int x;
		cin >> x;
		pre = (pre + x) % p;

		auto it = mp.upper_bound(pre);
		if (it != mp.end()) {
			int v = pre - it->fi + p;
			if (v > ans) {
				ans = v;
				res = {it->se, i - 1};
			}
		}
		pii t = *mp.begin();
		if (t.fi <= pre) {
			int v = pre - t.fi;
			if (v > ans) {
				ans = v;
				res = {t.se, i - 1};
			}
		}
		mp[pre] = i;
	}
	cout << res.fi << ' ' << res.se << ' ' << ans;
}

F

贪心+dp/枚举

经典问题,一个容量超大的背包,对于大部分容量,实际上都可以直接贪心,选择性价比最高的物品,对于剩下一点容量,可以来背包dp,或者暴力枚举。唯一的问题就是剩下多少容量来暴力/dp?这题转化完就是有容量2,7,8的三种东西,那么可能有特殊组合的容量不会超过lcm(2,7,8)=56,只留下56来暴力就可以了。这可以推广到一般背包问题,实际上就是,当然,这需要不太大,一般常见于物品种类不太多的情况,这种时候往往分类讨论可解,但是物品种类少也意味着lcm小,选择贪心+暴力是更简单的写法

  void solve() {
	int n, a, b;
	cin >> n >> a >> b;

	vvi t = {
		{56 * a / 7, 7, a},
		{56 * b / 2, 2, b},
		{56 * (a + b) / 8, 8, a + b}
	};
	sort(t.begin(), t.end());

	int c = t[2][1], v = t[2][2];

	int cnt = max(0ll, n / c - 100);
	int ans = cnt * v;
	n -= cnt * c;

	vector<int>f(n + 1);
	rep(i, 1, n) {
		if (i >= 2)f[i] = max(f[i], f[i - 2] + b);
		if (i >= 7)f[i] = max(f[i], f[i - 7] + a);
		if (i >= 8)f[i] = max(f[i], f[i - 8] + a + b);
	}

	cout << ans + f[n] << '\n';
}

G

模拟

可以分类讨论,但注意到转移之和但当前状态+当前操作有关,可以转化成自动机,构造一个数组作为自动机的转移表,就可以不用ifelse分类讨论,是更简单的写法

  int a[4][6]={
	{3,0,1,2,1,3},
	{2,3,0,1,2,0},
	{1,2,3,0,3,1},
	{0,1,2,3,0,2}
};
void solve() {
	string s = "0112233445142015320125410214530214510214102302142025101203201451451522302514203214510021454101002532";

	int x = 0;
	for (char c : s) {
		x=a[x][c-'0'];
        cout<<x;
	}
}

H

不变量

问能否操作到某一个终止状态,一个经典思路是找操作过程中的不变量,如果能到达某个终止状态,那么初态和末态的这个不变量一定是相等的。当然这往往只能作为必要条件,但是很多时候几个必要条件拼一起就能过了。

比如这题就有一些显然的不变量,一次横着一次竖着操作,能把一个2*2矩阵的对角线上一个+2一个-2,剩下位置不变,也就是如果黑白染色的话,黑色格和白色格的元素和都是不变的,所以如果能变成全都一样的,黑色格子的和,要能被黑色格子的个数整除,白色同样。

有这个还不够,注意到一次操作,不改变影响到的行和列的奇偶性,所以每一行的初始奇偶性,要和全变成x后的奇偶相同,,当然必须要能被整除。还有别的条件,但有这俩就能过了。

int a[500][500];
void solve() {
	int n;
	cin >> n;

	auto check = [&]()->bool{
		int s1 = 0, s2 = 0, c1 = 0, c2 = 0;
		int tot = 0;
		rep(i, 1, n) {
			rep(j, 1, n) {
				int x;
				cin >> x;
				a[i][j] = x;
				if ((i + j) % 2) {
					s1 += x;
					c1++;
				} else {
					s2 += x;
					c2++;
				}
				tot += x;
			}
		}
		if (tot % (n * n)) {
			return 0;
		}
		int x = tot / n / n;

		if (c1 && s1 % c1 || c2 && s2 % c2)return 0;

		rep(i, 1, n) {
			int sum = 0;
			rep(j, 1, n) {
				sum += a[i][j];
			}
			if (sum % 2 != (n * x) % 2) {
				return 0;
			}
		}
		rep(j, 1, n) {
			int sum = 0;
			rep(i, 1, n) {
				sum += a[i][j];
			}
			if (sum % 2 != (n * x) % 2) {
				return 0;
			}
		}
		return 1;
	};
	if (check())cout << "Yes";
	else cout << "No";
}

I

二分+二阶差分

每次操作给一个区间增加,增加的值构成一个三角形。也就是能拆成两个等差数列,问最第几次操作后,最大值超过h。只能增不会减,有单调性,考虑二分。区间加等差数列,最后只有一次查询,,选择二阶差分,差分完做两次前缀和就能得到区间加等差数列的效果。只是这个等差数列的修改,需要思考一点,这是唯一难点,这个操作可以封装成一个函数,就比较好做了

转化成差分后,线段树,也能维护区间加等差数列,但是每次只能单点查,如果想查询全局最大值,最后需要一次的查询,整体两个,会被卡常。这里只有最后一次查询,不是动态修改动态查询,没有必要线段树

void solve() {
	int n, m, h;
	cin >> n >> m >> h;

	vector<pii> b(m + 1);
	rep(i, 1, m) {
		int p, f;
		cin >> p >> f;
		b[i] = {p, f};
	}

	int l = 1, r = m;

	auto check = [&](int mid)->bool{
		vi s(n + 10);
        auto add=[&](int l,int r,int st,int ed,int d)->void{
            s[l]+=st;
            s[l+1]+=d-st;
            s[r+1]-=ed+d;
            s[r+2]+=ed;
        };
		rep(i, 1, mid) {
			auto [p, f] = b[i];
            int l , r , st , ed;
			l = max(p - f + 1, 1ll);
            st = f - (p - l);
			r = p;
            ed = f;
            add(l, r, st, ed, 1);
            l = p + 1;
            r = min(p + f - 1 , n);
            st = f - 1;
            ed = st - (r - l);
            add(l , r , st , ed , -1);
		}

		rep(i, 1, n) {
			s[i] += s[i - 1];
		}
		int mx = 0;
		rep(i, 1, n) {
			s[i] += s[i - 1];
			mx = max(mx, s[i]);
		}
		return mx > h;
	};
	while (l <= r) {
		int mid = (l + r) / 2;
		if (check(mid))r = mid - 1;
		else l = mid + 1;
	}
	if (l <= m)cout << "Yes\n" << l;
	else cout << "No";
}

J

模拟

int a[10][10];
void solve() {
	set<int>s;
	rep(i, 1, 3) {
		rep(j, 1, 3) {
			int x;
			cin >> x;
			a[i][j] = x;
			s.insert(x);
		}
	}
	if (s.size() != 9) {
		cout << "No\n";
		return;
	}
	s.clear();
	rep(i, 1, 3) {
		int sum = 0;
		rep(j, 1, 3) {
			sum += a[i][j];
		}
		s.insert(sum);
	}
	rep(i, 1, 3) {
		int sum = 0;
		rep(j, 1, 3) {
			sum += a[j][i];
		}
		s.insert(sum);
	}
	s.insert({a[3][1]+a[2][2]+a[1][3]});
	s.insert({a[1][1]+a[2][2]+a[3][3]});
	if(s.size()==1){
		cout<<"Yes\n";
	}
	else{
		cout<<"NO\n";
	}
}
全部评论

相关推荐

码农索隆:以下是我以我微薄的认知提供的建议: 1.考个教师资格证,去当体育考试。 2.去健身房当健身教练(因为在我印象里面体育生身材都不错)。
点赞 评论 收藏
分享
评论
3
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务