Educational Codeforces Round 71 部分题解

A. There Are Two Types Of Burgers

题意:

你有两种汉堡:汉堡和鸡肉汉堡。制作一个汉堡需要两个面包和一个牛肉饼,售价为 h 美元。制作一个鸡肉汉堡需要两个面包和一个鸡排,售价为 c 美元。你现有 b 个面包,p 个牛肉饼,f 个鸡排。求最大利润。

思路:

法一

由于数据范围较小(≤100),直接枚举汉堡的数量 x(0 ≤ x ≤ min(p, b/2)),则鸡肉汉堡数量 y = min(f, (b/2) - x)。计算利润 hx + cy,取最大值即可。

代码

点击查看代码
void solve() {
    int b, p, f, h, c;
    cin >> b >> p >> f >> h >> c;
    int B = b / 2;
    int ans = 0;
    for (int x = 0; x <= min(p, B); ++x) {
        int y = min(f, B - x);
        ans = max(ans, h * x + c * y);
    }
    cout << ans << endl;
}

法二

采用贪心策略,优先制作单价高的汉堡以最大化利润。如果汉堡单价h更高,则先尽可能多地制作汉堡,直到牛肉饼用完或面包不足两个;然后用剩余的面包制作鸡肉汉堡。反之,如果鸡肉汉堡单价c更高,则先制作鸡肉汉堡。

代码

点击查看代码
void solve() {
	int b,p,f;
	cin>>b>>p>>f;
	int h,c;
	cin>>h>>c;
	int s=0;
	if(h>c){
		s=min(p,b/2)*h;
		b-=s/h*2;
		s+=min(f,b/2)*c;
	}else{
		s=min(f,b/2)*c;
		b-=s/c*2;
		s+=min(p,b/2)*h;
	}
	cout<<s<<endl;

}

B. Square Filling

题意:

给定两个 \(n \times m\) 的矩阵 \(A\)\(B\)\(A\) 由 0 和 1 组成,\(B\) 初始全为 0。每次操作可以选择一个 \(2 \times 2\) 的子矩阵(左上角坐标 \((x,y)\) 满足 \(1 \le x < n\)\(1 \le y < m\)),并将该子矩阵的所有元素设置为 1。问能否通过若干次操作使 \(B\) 等于 \(A\),如果可以,输出任意一种操作序列(不要求最小化操作数)。

思路:

由于每次操作会将一个 \(2 \times 2\) 子矩阵全部设为 1,为了最终 \(B\)\(A\) 一致,每个操作的子矩阵在 \(A\) 中对应的四个位置必须都是 1。因此,我们枚举所有可能的 \(2 \times 2\) 子矩阵,如果其四个元素均为 1,则记录该操作,并在一个全零矩阵 \(C\) 中将这些位置标记为 1。所有操作执行完后,检查 \(C\) 是否等于 \(A\)。若相等,则输出所有记录的操作;否则无解。

代码

点击查看代码
int a[55][55], b[55][55];
int vis[55][55];
string s1, s2;

void solve() {
	int n,m;
	cin>>n>>m;
	vector<PII>ans;
	int f=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
			vis[i][j]=0;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(a[i][j]&&!vis[i][j]){
				if(i-1>=1&&j-1>=1&&a[i-1][j]&&a[i-1][j-1]&&a[i][j-1]){
					vis[i][j]=vis[i-1][j]=vis[i][j-1]=vis[i-1][j-1]=1;
					ans.push_back({i-1,j-1});
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(a[i][j]&&!vis[i][j])f=0;
		}
	}
	if(f){
		cout<<ans.size()<<endl;
		for(auto [x,y]:ans){
			cout<<x<<' '<<y<<endl;
		}
	}else{
		cout<<-1<<endl;
	}

}

C. Gas Pipeline

题意:

给定长度为 \(n\) 的二进制字符串 \(s\),表示道路每个单位区间是否有十字路口(1 表示有)。管道必须从高度 1 开始和结束,在十字路口处管道必须提升到高度 2。每单位管道成本为 \(a\),每单位支柱成本为 \(b\),支柱高度等于该点管道的高度。管道长度为基本水平长度 \(n\) 加上每次高度变化(1↔2)增加的额外长度 1。求最小总成本。

思路:

法一

动态规划。设 \(dp_0\)\(dp_1\) 分别表示当前点管道高度为 1 和 2 的最小成本。遍历每个区间 \(i\)(对应 \(s[i]\)):

  • \(s[i]=1\),则当前点和下一个点高度必须均为 2,只能从 \(dp_1\) 转移到 \(dp_1\)
  • \(s[i]=0\),则允许任意高度转移。
    转移时考虑管道成本(高度不变为 \(a\),变化为 \(2a\))和下一个点的支柱成本(高度 1 为 \(b\),高度 2 为 \(2b\))。
    最终答案为 \(dp_0\)(终点高度必须为 1)。

代码

点击查看代码
void solve() {
	int n, a, b;
	string s;
	cin >> n >> a >> b >> s;
	ll INF = 1e18;
	ll dp0 = b, dp1 = INF;
	for (int i = 0; i < n; i++) {
		ll ndp0 = INF, ndp1 = INF;
		if (s[i] == '1') {
			if (dp1 < INF) {
				ndp1 = dp1 + a + 2 * b;
			}
		} else {
			if (dp0 < INF) {
				ndp0 = min(ndp0, dp0 + a + b);
				ndp1 = min(ndp1, dp0 + 2 * a + 2 * b);
			}
			if (dp1 < INF) {
				ndp0 = min(ndp0, dp1 + 2 * a + b);
				ndp1 = min(ndp1, dp1 + a + 2 * b);
			}
		}
		dp0 = ndp0;
		dp1 = ndp1;
	}
	cout << dp0 << endl;
}

法二

贪心。初始成本为所有路段高度 1 的成本:管道 \(n \cdot a\),支柱 \((n+1) \cdot b\)。遍历字符串,当遇到十字路口时管道必须提升到高度 2。当从十字路口进入普通路段(连续 0)时,先增加提升管道成本 \(2a\) 和第一个点的支柱成本 \(b\)。对于长度为 \(L\) 的连续普通路段,如果保持高度 2,需要额外支柱成本 \((L-1) \cdot b\)(第一个点已计);如果降回高度 1,之后遇到十字路口又需提升,额外管道成本 \(2a\)。若 \((L-1) \cdot b < 2a\),则保持高度 2 更优,将成本差值 \((L-1) \cdot b - 2a\) 加入总成本。

代码

点击查看代码
void solve() {
	int n,a,b;
	cin>>n>>a>>b;
	cin>>s;
	s='0'+s;
	int ans=n*a+(n+1)*b;
	int l0=0,l1=0;
	for(int i=1;i<=n;i++){
		if(s[i]=='0'){
			if(i>1&&s[i-1]=='1'){
				ans+=2*a;
				ans+=b;
			}
			if(l1){
				l0++;
			}
		}else{
			ans+=b;
			l1=1;
			if(l1&&l0){
//				cout<<l0<<' ';
				if((l0-1)*b-2*a<0){
					ans+=(l0-1)*b-2*a;
				}
				l0=0;
			}
		}
	}
	cout<<ans<<endl;

}

D. Number Of Permutations

题意:

给定 \(n\) 个整数对 \((a_i, b_i)\)。定义一个序列是“坏”的,如果按第一个元素排序后是非递减的,或者按第二个元素排序后是非递减的。否则是“好”的。求有多少个 \(n\) 的排列 \(p\),使得将原序列按 \(p\) 重排后得到一个好序列。答案对 \(998244353\) 取模。

思路:

总排列数为 \(n!\)。用容斥原理:减去使第一个元素序列非递减的排列数,减去使第二个元素序列非递减的排列数,再加上同时使两个序列都非递减的排列数。

  • 使第一个元素序列非递减的排列数:相当于将所有元素按 \(a_i\) 排序,相同 \(a_i\) 的元素可以任意交换。设 \(cnt_a[x]\)\(a_i=x\) 的出现次数,则排列数为 \(\prod cnt_a[x]!\)
  • 使第二个元素序列非递减的排列数同理:\(\prod cnt_b[x]!\)
  • 同时使两个序列都非递减的排列数:需要检查当按 \(a_i\) 升序排序后,\(b_i\) 序列是否也是非递减的。如果是,则相当于按 \((a_i,b_i)\) 字典序排序,相同 \((a_i,b_i)\) 对可以任意交换。设 \(cnt_p[(a,b)]\) 为对 \((a,b)\) 的出现次数,则排列数为 \(\prod cnt_p[(a,b)]!\);否则为 \(0\)

最终答案:\(n! - \prod cnt_a[x]! - \prod cnt_b[x]! + \prod cnt_p[(a,b)]!\)(模 \(998244353\))。

代码

点击查看代码
void init(){
	f[0]=1;
	for(int i=1;i<N;i++){
		f[i]=f[i-1]*i%mod;
	}
}

PII a[N];
string s, s2;

bool cmp(PII x,PII y){
	return x.second<y.second;
}

void solve() {
	init();
	int n;
	cin>>n;
	int a1=1,a2=1,a3=1;
	for(int i=1;i<=n;i++){
		cin>>a[i].first>>a[i].second;
	}
	sort(a+1,a+1+n);
	int l=1;
	for(int i=1;i<=n;i++){
		if(i>1){
			if(a[i].first==a[i-1].first){
				l++;
			}else{
				a1*=f[l];
				a1%=mod;
				l=1;
			}
		}
	}
	a1*=f[l];
	a1%=mod;
	l=1;
	for(int i=1;i<=n;i++){
		if(i>1){
			if(a[i-1].first<=a[i].first&&a[i-1].second<=a[i].second){
				
			}else{
				a2=0;
			}
			if(a[i]==a[i-1]){
				l++;
			}else{
				a2*=f[l];
				a2%=mod;
				l=1;
			}
		}
	}
	a2*=f[l];
	a2%=mod;
	l=1;
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++){
		if(i>1){
			if(a[i].second==a[i-1].second){
				l++;
			}else{
				a3*=f[l];
				a3%=mod;
				l=1;
			}
		}
	}
	a3*=f[l];
	a3%=mod;
//	cout<<a1<<' '<<a2<<' '<<a3<<endl;
	a1+=(a3-a2+mod)%mod;
	a1%=mod;
	cout<<(f[n]-a1+mod)%mod<<endl;
}

E. XOR Guessing

题意:

猜一个 \(0\)\(2^{14}-1\) 之间的整数 \(x\)。最多可以问 \(2\) 次查询,每次查询给出 \(100\) 个不同的整数(范围 \(0\)\(2^{14}-1\)),评测机会随机选择一个数 \(a_i\),返回 \(a_i \oplus x\) 的值。所有查询中使用的 \(200\) 个整数必须互不相同。

思路:

\(x\)\(14\) 位整数,可以拆成高 \(7\) 位和低 \(7\) 位。
第一次查询:令 \(a_i = i\),这样 \(a_i\) 的高 \(7\) 位全为 \(0\)。设返回值为 \(r_1\),则 \(r_1\) 的高 \(7\) 位就是 \(x\) 的高 \(7\) 位。
第二次查询:令 \(a_i = i \ll 7\),这样 \(a_i\) 的低 \(7\) 位全为 \(0\)。设返回值为 \(r_2\),则 \(r_2\) 的低 \(7\) 位就是 \(x\) 的低 \(7\) 位。

组合高低 \(7\) 位即得 \(x\)

代码

点击查看代码
void solve() {
	cout<<"?";
	for(int i=1;i<=100;i++){
		cout<<' '<<i;
	}
	cout<<endl;
	int x1;
	cin>>x1;
	cout<<"?";
	for(int i=1;i<=100;i++){
		cout<<' '<<i*128;
	}
	cout<<endl;
	int x2;
	cin>>x2;
	x1>>=7;
	x1<<=7;
	x2&=(1ll<<7)-1;
	x1^=x2;
	cout<<"! "<<x1<<endl;
}

F. Remainder Problem

题意:

给定一个长度为 \(500000\) 的数组 \(a\),初始全为 \(0\)。处理 \(q\) 个操作:

1. 将 \(a_x\) 增加 \(y\)

2. 查询所有下标 \(i\)\(1 \le i \le 500000\))中满足 \(i \bmod x = y\)\(a_i\) 之和。

思路:

直接暴力查询复杂度太高。考虑根号分治:设定阈值 \(S \approx \sqrt{500000} \approx 707\)

  • 对于 \(x \le S\),维护一个二维数组 \(dp[x][y]\) 表示所有满足 \(i \bmod x = y\)\(a_i\) 之和。每次更新 \(a_x\) 时,对所有 \(d \le S\),令 \(dp[d][x \bmod d]\) 增加 \(y\)。查询时直接输出 \(dp[x][y]\)
  • 对于 \(x > S\),直接暴力求和 \(a[y], a[y+x], \dots\),因为项数不超过 \(\frac{500000}{S} \approx 707\)

时间复杂度 \(O(q \cdot S)\),空间 \(O(S^2)\)

代码

点击查看代码
int a[N];
ll sum[705][705];
void solve() {
	int B=700;
	int q;
	cin >> q;
	while (q--) {
		int t, x, y;
		cin >> t >> x >> y;
		if (t == 1) {
			a[x] += y;
			for (int m = 1; m <= B; ++m) {
				sum[m][x % m] += y;
			}
		} else {
			if (x <= B) {
				cout << sum[x][y] << endl;
			} else {
				ll ans = 0;
				int st = y;
				if (y == 0) st = x;
				for (int i = st; i <= N; i += x) {
					ans += a[i];
				}
				cout << ans << endl;
			}
		}
	}
}
posted @ 2026-01-29 12:01  ptlks  阅读(16)  评论(0)    收藏  举报