牛客NowCoder OI周赛普及组15题解
A咪咪游戏
A题略
B.三角形
题目:
思路
两种思路,堆和背包dp。
堆
先看堆的,维护一个含有K个元素的最大堆,逐个遍历每个箱子的宝物,每次只维护价值之和是前K小的,因为比这些元素大的必定不会出现在之后的结果里。
具体就是,先把堆中元素全部弹出,存入一个数组中,然后依次从大到小令其中的元素同当前宝箱中的宝物的价值相加,将重新得到元素加入堆中。
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int a[110], b[10010];
priority_queue<int> pq; // 维护一个大根堆
int main() {
int n, k;
cin >> n >> k;
pq.push(0); //细节 开始时堆不能为空。
while(n--) {
int m; cin >> m;
for (int i = 1; i <= m; i++) cin >> a[i];
sort(a + 1, a + 1 + m);
int cnt = 0;
while (!pq.empty()) {
b[++cnt] = pq.top(); pq.pop();
}
for (int i = 1 ; i <= m; i++) {
for (int j = cnt; j >= 1; j--) {
if (pq.size() < k) pq.push(a[i] + b[j]);
else if (pq.top() > a[i] + b[j]) {
pq.pop(); pq.push(a[i] + b[j]);
}
else break;
}
}
}
int cnt = 0;
while (!pq.empty()) {
b[++cnt] = pq.top(); pq.pop();
}
int ans = 0;
for (int i = 1; i <= k; i++) ans += b[i];
cout << ans << endl;
return 0;
}
dp
dp思路,转化成背包dp。
d p [ i ] [ j ] dp[i][j] dp[i][j]代表在前i个宝箱中取物品,价值和为j的方案数。
转移方程:
d
p
[
i
]
[
j
]
=
d
p
[
i
]
[
j
]
+
d
p
[
i
−
1
]
[
j
−
v
a
l
[
k
]
]
,
∀
k
∈
b
o
x
[
i
]
dp[i][j]=dp[i][j] + dp[i-1][j-val[k]], \forall\ k\in \ box[i]
dp[i][j]=dp[i][j]+dp[i−1][j−val[k]],∀ k∈ box[i]
可以用滚动数组优化。
最后统计答案的时候就是从从小到大遍历 j j j然后相加。
#include <iostream>
#include <cmath>
using namespace std;
#define N 105
int dp[N][N*N], a[N][N];
int main(){
int n, k, tot = 0;
cin>>n>>k;
for(int i = 1; i <= n; i++){
int num = 0;
cin>>a[i][0];
for(int j = 1; j <= a[i][0]; j++){
cin>>a[i][j];
num = max(num, a[i][j]);
}
tot += num;
}
dp[0][0] = 1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= a[i][0]; j++)
for(int m = tot; m >=a[i][j]; m--){
if(dp[i-1][m-a[i][j]])
dp[i][m] += dp[i-1][m-a[i][j]];
}
int ans = 0, cnt = 0;
for(int i = 1; i <=tot; i++){
while(dp[n][i]){
ans += i;
cnt++;
dp[n][i]--;
if(cnt == k){
cout<<ans<<endl;
return 0;
}
}
}
return 0;
}
C.区间加
题目
计数类问题,一般考虑dp。
首先做个差得到数组
a
a
a,
a
i
a_i
ai代表原始数组中第
i
i
i个元素同目标值的差距,根据题目的限制,我们可以得到下面的重要的结论:
对于数组中相邻的两个元素其差值不能超过1
那么我们对数组 a a a做个差分,得到数组 b b b,那么对于 b b b数组,两个相邻的数组元素差值也不可能超过1,根据这个我们可以做dp。
如何解决这个问题呢,我们把问题转化成在一段数字两边加括号的方案数,那么对于一个元素 i i i,他左边所有的左括号与右括号的差值,即为这个数被加1的次数(因为这就代表了他被包含在了多少个加1区间里面)。
令
d
p
[
i
]
dp[i]
dp[i]代表处理到第i个数时的方案数。有以下几种情况。
(注意
b
[
i
]
=
a
[
i
]
−
a
[
i
−
1
]
b[i]=a[i]-a[i-1]
b[i]=a[i]−a[i−1])
1)如果 b [ i ] = = − 1 b[i]==-1 b[i]==−1,说明第 i i i个元素比 i − 1 i-1 i−1个元素大一,那么他必定比他前一个元素要少加一次,所以此时一定要在 i i i的前面加一个右括号,那么加的这个右括号可能会和前面的任意一个括号匹配,我们令 u n M a t c h unMatch unMatch代表目前为止仍未被匹配的左括号的数目,那么 d p [ i ] = d p [ i − 1 ] ∗ ( u n M a t c h ) dp[i]=dp[i-1]*(unMatch) dp[i]=dp[i−1]∗(unMatch),然后被匹配的左括号多了1, u n M a t c h unMatch unMatch减1。
2)如果 b [ i ] = = 1 b[i]==1 b[i]==1,说明第 i i i个元素比第 i − 1 i-1 i−1个元素小一,那么他就要比前面一个元素多加一次,所以应该在他前面放一个左括号。则 d p [ i ] = d p [ i − 1 ] dp[i]=dp[i-1] dp[i]=dp[i−1], u n M a t c h unMatch unMatch加1。
3)如果 b [ i ] = = 0 b[i]==0 b[i]==0,说明两个元素相等,那么两种方案,什么都不做,和在 i i i前面分别放一个左括号和有括号,算上两种情况是 d p [ i ] = d p [ i − 1 ] ∗ ( u n M a t c h + 1 ) dp[i] =dp[i-1]*(unMatch+1) dp[i]=dp[i−1]∗(unMatch+1), u n M a t c h unMatch unMatch保持不变,因为两种操作均不会改变未被匹配的括号数。
在稍微判断下题目中的-1情况即可。
#include <iostream>
using namespace std;
const int mod = 998244355;
int a[2010], b[2010];
int n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i] = m - a[i];
}
for (int i = 1; i <= n; i++)
b[i] = a[i] - a[i-1];
long long ans = 1, unMatch = 0;
for (int i = 1; i <= n; i++) {
if (abs(b[i]) > 1) {
puts("0");
return 0;
}
if (b[i] == 1)
unMatch++;
else if (b[i] == -1)
ans = (ans * unMatch--) % mod;
else
ans = (ans * (unMatch + 1)) % mod;
}
cout << ans << endl;
return 0;
}
D.多元组
题目
思路
还是动态规划,挺明显的,可以用前
M
−
1
M-1
M−1元素来推
M
M
M元组,递推式:
d
p
[
i
]
[
k
]
=
∑
j
d
p
[
j
]
[
k
−
1
]
(
a
[
j
]
<
a
[
i
]
)
dp[i][k]=\sum\limits_{j}dp[j][k-1]\quad\ (a[j]<a[i])
dp[i][k]=j∑dp[j][k−1] (a[j]<a[i])
但是如果用直接写复杂度会达到
O
(
n
2
)
O(n^2)
O(n2),所以需要快速的得到这个和,那么考虑用k个树状数组去维护
a
[
i
]
a[i]
a[i]前面的元素的
k
−
1
k-1
k−1元素的和
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int max_n = 1e5+10;
ll a[max_n], b[max_n];;
ll bit[max_n][55], dp[max_n][55];
int n, m;
int lowbit(int i) {
return i & -i;
}
void add(int i, int val, int k) {
while (i <= n) {
bit[i][k] += val;
i += lowbit(i);
}
}
ll sum(int i, int k) {
ll s = 0;
while (i > 0) {
s += bit[i][k];
i -= lowbit(i);
}
return s;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i];
}
sort(b+1, b+1+n);
int len = unique(b+1, b+1+n)-b-1;
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(b+1, b+1+len, a[i])-b; // 离散化,但是为了避免出现0下标,减去b。
}
for (int i = 1; i <= n; i++) dp[i][1] = 1; // 每个数都是一个一元组。
for (int i = 1; i <= n; i++) {
add(a[i], 1, 1);
for (int k = 2; k <= m; k++) {
dp[i][k] = (dp[i][k] + sum(a[i]-1, k-1)) % mod;
add(a[i], dp[i][k], k);
}
}
ll ans = 0;
for (int i = 1; i <= n; i++)
ans = (ans + dp[i][m]) % mod;
cout << ans << endl;
return 0;
}