2. 4 蓝桥练习5题
今天写的几个题都挺有意思的,码量不大,主要是思维。藕还得多练QAQ
1.[P8669 蓝桥杯 2018 省 B] 乘积最大
题意:给定 \(N\) 个整数 \(A_1, A_2,\cdots, A_N\)。请你从中选出 \(K\) 个数,使其乘积最大。
请你求出最大的乘积,由于乘积可能超出整型范围,你只需输出乘积除以 \(1000000009\)(即 \(10^9+9\))的余数。
注意,如果 \(X<0\), 我们定义 \(X\) 除以 \(1000000009\) 的余数是 \(0-((0-x)\bmod 1000000009)\)。
思路:贪心+分类讨论
先排个序。如果\(K\)是偶数的话,我们考虑2个2个取。我们肯定优先两个正的或两个负的(保证答案是正数),实在没办法了才会考虑一正一负的情况(\(N=K的情况\))。我们排好序之后两头取,选大的。如果是偶数的话,把ans初始化为当前的最大值,注意如果当前最大值都是<0的(即全为负),那么最后介个也是负数,用\(f\)标记一下。然后k变成偶数,按照偶数是做法写。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 9;
const int N = 2e5 + 10;
ll a[N];
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
int n,k; cin>>n>>k;
for(int i = 1;i <= n; i++)
cin>>a[i];
sort(a+1,a+1+n);
ll ans = 1;
int l = 1,r = n;
int f = 1;
if(k % 2){
ans *= a[r];
if(ans < 0)
f = -1;
r--;
k--;
}
while(k)
{
ll pre = a[l]*a[l+1];
ll suf = a[r]*a[r-1];
if(f * pre > f * suf)
ans *= pre%mod,ans %= mod,l += 2;
else ans *= suf%mod,ans %= mod,r -= 2;
k -= 2;
}
cout<<ans<<"\n";
return 0;
}
2.[P8625 蓝桥杯 2015 省 B] 生命之树
题意:对于一棵树,找到其点权和最大的一个连通分量(注意可以为空),输出这个连通分量的点权和。
思路:树形\(dp\),考虑每个点为根\(u\)的情况下它的子树\(v\)最大点权和。
\(dp[u] += max(dp[v],0)\)
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int n,a[N];
vector<int>e[N];
ll dp[N];
void dfs(int u,int fa)
{
//cout<<"u = "<<u<<"\n";
dp[u] = a[u];
for(auto v : e[u])
{
if(v == fa)continue;
dfs(v,u);
dp[u] += max(dp[v],0ll);
}
}
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n;
for(int i = 1;i <= n; i++)
cin>>a[i];
for(int i = 1;i < n; i++)
{
int u,v; cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1,0);
ll ans = 0;
for(int i = 1;i <= n; i++)
{
ans = max(ans,dp[i]);
}
cout<<ans<<"\n";
return 0;
}
3.[P8782 蓝桥杯 2022 省 B] X 进制减法
题意:两个\(X\)进制数,求差最小值。最小为2进制。
思路:\(X\)进制是怎么求的呢?举个例子:最低数位为二进制,第二数位为十进制,第三数位为八进制,则\(X\)进制数\(321\)转化为十进制是什么呢?
\(a[i]\)数值:3 2 1
\(c[i]\)进制:8 10 2
\(d[i]\)权值:\(1\times2\times10\) \(1\times2\) 1
通式就是:\(d[i] = a[i]\prod_{k = 1}^{i-1}c[k](c[0] = 1)\)
那么对于\(X\)进制\(321\)转为\(10\)进制就是:\(20\times 3 + 2\times 2+1\times 1 = 65\)
好的了解了以上,我们来考虑本题。本题是要求差值最小。因为题目保证了\(A\ge B\),想要\(A-B\)的值尽量小,每一位的权值也要尽量小。因为是指数基本增长的,\(A\)变大会比\(B\)更快。如果想让每一位的权值尽量小,每一位的进制也得尽量小。那么我们考虑贪心,每一位的进制取\(max(a[i],b[i])+1\),如果\(<2\)的话置为\(2\)。记得开$long $ \(long\)。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
ll n,la,lb,a[N],b[N],c[N],d[N];
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n;
cin>>la;
for(int i = la;i >= 1; i--)
cin>>a[i];
cin>>lb;
for(int i = lb;i >= 1; i--)
cin>>b[i];
for(int i = 1;i <= max(la,lb); i++)
{
c[i] = max(a[i],b[i])+1;
if(c[i]<2)c[i] = 2;
}
d[1] = 1;
for(int i = 2;i <= max(la,lb);i++)
d[i] = (d[i-1]*c[i-1])%mod;
ll t1 = 0,t2 = 0;
for(int i = 1;i <= la;i++)
t1 += (a[i]*d[i])%mod,t1 %= mod;
for(int i = 1;i <= lb;i++)
t2 += (b[i]*d[i])%mod,t2 %= mod;
cout<<(t1-t2+mod)%mod<<"\n";
return 0;
}
4.[P8683 蓝桥杯 2019 省 B] 后缀表达式
题意:给定 \(N\) 个加号、 \(M\) 个减号以及 \(N+M+1\) 个整数 \(A_1,A_2,\cdots,A_{N+M+1}\),小明想知道在所有由这 \(N\) 个加号、 \(M\) 个减号以及 \(N+M+1\) 个整数凑出的合法的后缀表达式中,结果最大的是哪一个。
请你输出这个最大的结果。
例如使用 1 2 3 + -
,则 2 3 + 1 -
这个后缀表达式结果是 \(4\),是最大的。
思路:这题很有意思,想了很久。后缀表达式是可以加括号滴。
比如
0 2
1 2 3
答案应该是4。因为3-(1-2) = 4。如果直接贪心的话求出来是3-2-1 = 0。
那么怎么办哩?
我们先考虑简单的,如果全是加号,那么答案一定是直接全部加起来就行了。
那么如果有减号存在呢?
一个负数通过加括号和减号来变成正数
对于负数\(a\)我们是不是可以\(-(a)\)变成正数。多个负数呢?比如\(a,b,c\)都是负数,那么\(-(a+b+c)\)就是正数了。
我们发现了什么?是不是只要有一个负号,我们就可以把若干个负数变成正数啦。我们考虑多加上大数,减去小数。我们考虑把max放首相,min放后面,构成以下表达式。
\(max-(min±①)±②\)
其他数:
- 正数 + :放在②
- 负数 - :放在②
- 正数 - : 放在①
- 负数 + :放在①
我们发现无论怎么放都是正数,完美解决。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
ll a[N];
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
int n,m; cin>>n>>m;
int k = n+m+1;
for(int i = 1;i <= k; i++)
cin>>a[i];
sort(a+1,a+1+k);
ll ans = 0;
if(m==0)
{
for(int i = 1;i <= k; i++)
ans += a[i];
}
else{
ans = a[k]-a[1];
for(int i = 2;i < k; i++)
ans += abs(a[i]);
}
cout<<ans<<"\n";
return 0;
}
5.[P8783 蓝桥杯 2022 省 B] 统计子矩阵
题意:给定一个 \(N \times M\) 的矩阵 \(A\),请你统计有多少个子矩阵 (最小 \(1 \times 1\), 最大 \(N \times M)\) 满足子矩阵中所有数的和不超过给定的整数 \(K\)。
思路:看见这个第一反应就是二维前缀和+枚举。枚举4维肯定是不行的,我就想枚举左上角和左下角的x再二分y。结果。。。还是TLE了hh。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
const int mod = 1e9 + 7;
const int N = 5e2 + 10;
ll n,m,k;
ll a[N][N],s[N][N];
bool judge(int x1,int y1,int x2,int y2)
{
ll sum = s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
return sum <= k;
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n>>m>>k;
for(int i = 1;i <= n; i++)
for(int j = 1;j <= m; j++)
cin>>a[i][j];
for(int i = 1;i <= n; i++)
for(int j = 1;j <= m; j++)
s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
ll ans = 0;
for(int i = 1;i <= n; i++)
{
for(int j = 1;j <= m; j++)
{
int last = m;
for(int k = i;k <= n; k++)
{
int x1 = i,y1 = j;
int x2 = k,y2;
int l = j, r = last;
while(l<=r)
{
int mid = (l+r)>>1;
if(judge(x1,y1,x2,mid))l = mid+1;
else r = mid-1;
}
y2 = l-1;
if(y2<y1)break;
last = y2;
ans += (y2-y1+1);
}
}
}
cout<<ans<<"\n";
return 0;
}
以上代码只有80分。那么得把log优化掉怎么办哩。我们考虑双指针。
枚举\(x_1,x_2\),用双指针维护\(y_1和y_2\)
\(l\)表示\(y_1\),\(r\)表示\(y_2\)。我们不断右移\(r\),直到第一次不合法。那么当前\(r\)也不用再继续往后了,因为后面就更不行了,我们右移左指针\(l\),直到第一个合法位置,同时保证\(l\le r\)。然后更新答案,加上\(r-l+1\)。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
const int mod = 1e9 + 7;
const int N = 5e2 + 10;
ll n,m,k;
ll a[N][N],s[N][N];
signed main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n>>m>>k;
for(int i = 1;i <= n; i++)
for(int j = 1;j <= m; j++)
cin>>a[i][j];
for(int i = 1;i <= n; i++)
for(int j = 1;j <= m; j++)
s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
ll ans = 0;
for(int i = 1;i <= n; i++)//x1
{
for(int j = i;j <= n; j++)//x2
{
for(int l = 1,r = 1;r <= m; r++)//y1,y2
{
while(l <= r &&s[j][r] - s[i - 1][r] - s[j][l - 1] + s[i - 1][l - 1] > k)l++;
ans += r-l+1;
}
}
}
cout<<ans<<"\n";
return 0;
}