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;
}
}
}
}

浙公网安备 33010602011771号