# 2024/6/29 APCS 第六場模擬賽 題解
## 大智慧學苑-畢業旅行囉 (Trip)
> 出題者:Howard
> 難度:p1
> 考點:模擬
### Statement
已經到了六月,大智慧學苑正準備著學員們的畢業旅行,大智慧學苑院長 Chung 非常奇怪,和大部分學校不同的是他想要在畢業典禮後再舉辦畢業旅行,院長這次破例,想要幫所有學員出飛機票錢,但是他們要搭乘的航空公司有很多優惠,此優惠根據不同年齡層會有更動,給定 $n$ 個人,還有優惠的年齡區間 $L_i、R_i$ 優惠價,以及原價,請你寫程式告訴院長需要花多少錢。
若年齡未在所有優惠年齡區間,則以原價計算。
年齡層不可能會有重複。
### Input
第一行有兩個數字 $n,m\ (1\leq n\leq 1000,\ 10\leq m\leq 1000)$ 代表有 $n$ 位學員以及機票原價為 $m$
第二行有 $n$ 個數字 $a_1, a_2, a_3,...,a_n\ (1\leq a_i\leq 100)$ 代表每個人的年齡
第三行有一個數字 $k (1\leq k\leq 50)$ 代表有幾筆優惠
第四行開始總共有 $k$ 行,每行有三個數字 $L_i, R_i, P_i\ (1\leq L_i\leq R_i\leq 100,\ 1\leq P_i < m)$ 代表年齡從 $L_i$ (包含) 到 $R_i$ (包含) 的優惠價格為 $P_i$
### Output
輸出共一個數字代表院長需要付的所有機票錢。
### Testcase
- Input 1
```
5 100
7 10 5 20 15
3
5 10 50
1 3 10
20 30 60
```
- Output 1
```
310
```
### Subtask
- Subtask 1 (50%) - $k=1$
- Subtask 2 (50%) - 無其他限制
### Solution
- Subtask 1:由於只有一組 $L, R$ ,所以只要在全部輸入後再跑一次迴圈看看有哪些是符合優惠方案的,是的話就是用優惠方案的價錢,不是的話就用原機票價錢 $k$ 計算。
- Subtask 2:針對每個人都去試試看是否符合優惠方案的年齡區間,由於題目保證年齡區間不會重複,所以只要找到一個符合的區間就一定是那個答案。
### Code
- Subtask 1
```cpp=
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
int a[n];
for(int i = 0; i < n; i++) {
cin >> a[i];
}
int k;
cin >> k;
int L, R, P;
cin >> L >> R >> P;
int ans = 0;
for(int i = 0; i < n; i++) {
if(a[i] >= L && a[i] <= R) ans += P;
else ans += m;
}
cout << ans << '\n';
return 0;
}
```
- Subtask 2
```cpp=
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
int a[n];
for(int i = 0; i < n; i++) {
cin >> a[i];
}
int k;
cin >> k;
int L[k], R[k], P[k];
for(int i = 0; i < k; i++) {
cin >> L[i] >> R[i] >> P[i];
}
int ans = 0;
for(int i = 0; i < n; i++) {
bool used = 0;
for(int j = 0; j < k; j++) {
if(a[i] >= L[j] && a[i] <= R[j]) {
used = 1;
ans += P[j];
break;
}
}
if(!used) ans += m;
}
cout << ans << '\n';
return 0;
}
```
- Python by 橘子
```python=
n,m = map(int,input().split())
a = list(map(int,input().split()))
b = [m]*n
for _ in range(int(input())):
l,r,p = map(int,input().split())
for i in range(n):
if l<=a[i]<=r:
b[i] = min(b[i],p)
print(sum(b))
```
## 烤肉吧! (barbecue)
> 出題者:Cheng
> 難度:p2
> 考點:二維陣列操作
### Statement
<center>
<img src="https://hackmd.io/_uploads/rJ9I9V6UC.png">
</center>
香噴噴又油亮亮的烤肉是 Cheng 的最愛呢!
在美好的夜晚當中,明亮的火光在你眼前,香氣撲鼻而來,好想大喊一句「バーベキューは最高!」
今天又在烤肉的 Cheng 想要烤出超美味的烤肉,於是依照路邊撿到的黑暗聖典來烤出美味的烤肉!
<center>
<img src="https://hackmd.io/_uploads/SJs5cNpLC.png">
</center>
<br>
我們可以把烤肉網和烤爐皆視為一個大小相同的 $n \times m$ 矩陣,烤爐一開始在第 $i$ 列第 $j$ 行會有 $H_{i,j}$ 的焰度,每分鐘結束時焰度會增加 $y_{i,j}$,(注意到 $y_{i,j}$ 可能是負的,所以烤爐的焰度可能會越來越低,但若低於 $0$ 皆須視為 $0$)。
Cheng 在烤肉網上放上了 $n \times m$ 塊肉,每塊大小恰等於烤肉網上的每一格,我們可以簡單的將肉劃分為正反兩面,肉正反兩面每格的熟度以及溫度初始為 $0$,每分鐘每個格子上的肉的溫度會加上火爐該格當時的焰度,格子上的肉正(反)面每吸收 $k$ 度,該格子上的肉正(反)面熟度就會增加 $1$。若每塊肉正反兩面的熟度都大於或等於 $R$,就代表所有肉都熟了!Cheng 就會把它們拿下來享用!
黑暗聖典的指示有兩種,每分鐘聖典都會有下列其中一種指示:
* $S$:把每塊肉翻面
* $.$:放著繼續烤
黑暗聖典上面說,只要時間一到就要馬上做該時間要做的操作,且因為 Cheng 的手腳很快,所以每分鐘 Cheng 會先操作完,肉再吸收烤爐焰度,也就是說每分鐘會依序執行:聖典操作 -> 烤爐加熱烤肉 -> 烤肉熟度改變 -> 烤爐更新焰度。
另外,烤肉把肉烤焦也是一件很正常的事,若肉的正(反)面熟度嚴格大於 $R \times 2$,則這塊肉的正(反)面會燒焦 (燒焦只是過熟,所以依然算熟),為了驗證黑暗聖典的有用程度,Cheng 想要額外知道正面和反面總共有幾個格子上的肉燒焦 (正反面都要算一次,假如有 $3$ 個正面焦掉、有 $2$ 個反面焦掉,則總計為 $5$)。
Cheng 想知道在 $t$ 分鐘過後依照黑暗聖典的指示所有肉會不會熟,以及正面和反面總共有幾個格子上的肉燒焦了?(需要注意如果烤到一半就全熟了,則會直接被拿起來,不用等到 $t$ 分鐘結束)
大家一起開心的吃烤肉吧!
### Input
輸入第一行將有兩個整數 $n, m\ (1 \le n, m \le 10)$。
輸入第二行將有三個整數 $t\ (1 \le t \le 1000), k, R\ (1 \le k, R \le 500)$。
輸入第三行將有一個字串 $S\ (|S| = t,\ S$ 只會有 "S" 或 "." 兩種字元$)$ 代表黑暗聖典上說的操作 (字串第 $1$ 個字元代表第 $1$ 分鐘要做的操作)。
接著有 $n$ 行,每行會有 $m$ 個數字 $H_{i,j}\ (0 \le H_{i,j} \le 100)$,代表烤爐第 $i$ 列、第 $j$ 行初始的焰度。
輸入第一行將有兩個整數 $n, m\ (1 \le n, m \le 10)$。
輸入第二行將有三個整數 $t\ (1 \le t \le 1000), k, R\ (1 \le k, R \le 500)$。
輸入第三行將有一個字串 $S\ (|S| = t,\ S$ 只會有 "S" 或 "." 兩種字元$)$ 代表黑暗聖典上說的操作 (字串第 $1$ 個字元代表第 $1$ 分鐘要做的操作)。
接著有 $n$ 行,每行會有 $m$ 個數字 $H_{i,j}\ (0 \le H_{i,j} \le 100)$,代表烤爐第 $i$ 列、第 $j$ 行初始的焰度。
最後有 $n$ 行,每行會有 $m$ 個數字 $y_{i,j}\ (-10 \le y_{i,j} \le 10)$,代表第 $i$ 列、第 $j$ 行烤爐每分鐘會增加的焰度,請注意,烤爐焰度最低為 $0$,不會是負的。
### Output
輸出兩個數字,依序代表 $t$ 分鐘後所有烤肉有沒有都熟了 (輸出 $0$ 代表仍有肉沒熟、$1$ 代表所有肉正反每格都熟了)、正面和反面總共有幾個格子上的肉燒焦。(需要注意如果烤到一半就全熟了,則會直接被拿起來,不用等到 $t$ 分鐘結束)
### Testcase
- Input 1
```
2 2
3 5 5
...
70 35
80 2
8 10
8 6
```
- Output 1
```
0 3
```
- Input 1
```
2 2
3 5 5
.S.
5 80
12 17
6 5
0 -3
```
- Output 1
```
0 2
```
### Subtask
- Subtask 1 (50%) - $S$ 只由 "." 組成
- Subtask 2 (50%) - 無其他限制
### Note
每秒依序執行:聖典操作 -> 烤爐加熱烤肉 -> 烤肉熟度改變 -> 烤爐更新焰度
### Solution
可以發現這是個二維陣列操作的題目
我們要找到 $(i,j)$ 格到底累積了多少次 $k$ 溫度
我們可以將 $(i,j)$ 的溫度差除以 $k$ 來得知該時間點會増加多少熟度
(加上後整個會累積多少 $k$) $-$ (加上前累積了多少 $k$)
在每個時間點操作後都去檢查該塊肉是否熟了,熟了就直接拿下來計算燒焦區域有多少
其實 sub1 跟 sub2 的差別只是「記得將上層與下層的溫度做 swap 的動作 (翻面)」
### Code
- Subtask 1
```cpp=
void sol()
{
int n, m;
cin >> n >> m;
int t, k, R;
cin >> t >> k >> R;
vector<vector<vector<int>>> h(2, vector<vector<int>>(n, vector<int>(m, 0))), T(2, vector<vector<int>>(n, vector<int>(m, 0)));
int down = 0, top = 1;
vector<vector<int>> H(n, vector<int>(m)), y(n, vector<int>(m));
string s;
cin >> s;
for(auto &v : H) for(int &it : v) cin >> it;
for(auto &v : y) for(int &it : v) cin >> it;
int jiao = 0;
for(int time = 0; time < t; time++){
for(int i = 0; i < n; i++) for(int j = 0; j < m; j++){
int dis = (h[down][i][j] + H[i][j])/k - h[down][i][j]/k;
h[down][i][j]+=H[i][j];
if(H[i][j] && dis) T[down][i][j]+=dis;
H[i][j]+=y[i][j], H[i][j] = max(0, H[i][j]);
}
bool check = 1;
for(int dir = 0; dir < 2; dir++) for(int i = 0; i < n; i++) for(int j = 0; j < m; j++){
if(T[dir][i][j] < R) check = 0;
}
if(check){
for(int dir = 0; dir < 2; dir++) for(int i = 0; i < n; i++) for(int j = 0; j < m; j++){
if(T[dir][i][j] > 2*R) jiao++;
}
cout << 1 << ' ' << jiao << '\n';
return;
}
}
for(int dir = 0; dir < 2; dir++) for(int i = 0; i < n; i++) for(int j = 0; j < m; j++){
if(T[dir][i][j] > 2*R) jiao++;
}
cout << 0 << ' ' << jiao << '\n';
}
```
- Subtask 2
```cpp=
void sol()
{
int n, m;
cin >> n >> m;
int t, k, R;
cin >> t >> k >> R;
vector<vector<vector<int>>> h(2, vector<vector<int>>(n, vector<int>(m, 0))), T(2, vector<vector<int>>(n, vector<int>(m, 0)));
int down = 0, top = 1;
vector<vector<int>> H(n, vector<int>(m)), y(n, vector<int>(m));
string s;
cin >> s;
for(auto &v : H) for(int &it : v) cin >> it;
for(auto &v : y) for(int &it : v) cin >> it;
int jiao = 0;
for(int time = 0; time < t; time++){
if(s[time] == 'S') swap(down, top);
for(int i = 0; i < n; i++) for(int j = 0; j < m; j++){
int dis = (h[down][i][j] + H[i][j])/k - h[down][i][j]/k;
h[down][i][j]+=H[i][j];
if(H[i][j] && dis) T[down][i][j]+=dis;
H[i][j]+=y[i][j], H[i][j] = max(0, H[i][j]);
}
bool check = 1;
for(int dir = 0; dir < 2; dir++) for(int i = 0; i < n; i++) for(int j = 0; j < m; j++){
if(T[dir][i][j] < R) check = 0;
}
if(check){
for(int dir = 0; dir < 2; dir++) for(int i = 0; i < n; i++) for(int j = 0; j < m; j++){
if(T[dir][i][j] > 2*R) jiao++;
}
cout << 1 << ' ' << jiao << '\n';
return;
}
}
for(int dir = 0; dir < 2; dir++) for(int i = 0; i < n; i++) for(int j = 0; j < m; j++){
if(T[dir][i][j] > 2*R) jiao++;
}
cout << 0 << ' ' << jiao << '\n';
}
```
- Subtask 2 by Chung
```cpp=
#include<bits/stdc++.h>
using namespace std;
//declare
const int maxn = 15;
int n,m,t,k,R,cnt;
int H[maxn][maxn]; // 烤爐
int y[maxn][maxn]; // 焰度更新
int T[2][maxn][maxn]; // 兩面吸收的溫度
int M[2][maxn][maxn]; // 兩面熟度
string s;
//
int main(){
cin>>n>>m>>t>>k>>R>>s;
for(int i=0;i<n;i++) for(int j=0;j<m;j++) cin>>H[i][j];
for(int i=0;i<n;i++) for(int j=0;j<m;j++) cin>>y[i][j];
bool flag = 0;
for(int x=0;x<t;x++){
if(s[x] == 'S') flag ^= 1; // 聖典操作
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
// 烤爐加熱肉
T[flag][i][j] += H[i][j];
// 烤肉熟度改變
M[flag][i][j] = T[flag][i][j] / k;
// 烤爐更新焰度
H[i][j] += y[i][j];
H[i][j] = max(0, H[i][j]);
}
}
// 判斷是否全熟
bool ok = 1;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(M[0][i][j] < R) ok = 0;
if(M[1][i][j] < R) ok = 0;
}
}
// 計算焦掉的數量
cnt = 0; // 焦掉的數量
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cnt += (M[0][i][j] > 2*R);
cnt += (M[1][i][j] > 2*R);
}
}
if(ok){
cout<<1<<" "<<cnt<<"\n";
return 0;
}
}
cout<<0<<" "<<cnt<<"\n";
return 0;
}
```
- Python by 橘子
```python=
n, m = map(int,input().split())
t, k, r = map(int,input().split())
s = input()
h = [list(map(int,input().split())) for _ in range(n)]
d = [list(map(int,input().split())) for _ in range(n)]
down = [[0]*m for _ in range(n)]
up = [[0]*m for _ in range(n)]
ans1 = 0
for i in range(t):
if s[i]=='S':
down, up = up, down
ed = True
for x in range(n):
for y in range(m):
down[x][y] += h[x][y]
h[x][y] = max(0,h[x][y]+d[x][y])
if down[x][y]//k < r or up[x][y]//k < r:
ed = False
if ed:
ans1 = 1
break
ans2 = 0
for x in range(n):
for y in range(m):
ans2 += (down[x][y]//k>r*2)+(up[x][y]//k>r*2)
print(ans1,ans2)
```
## 蘋果樹 (Apple Tree)
> 出題者:Chung
> 難度:p3
> 考點:dfs/bfs、二分搜、前綴和
### Statement
Chung 家裡種了一棵蘋果樹,最近到了可以採收的季節,於是 Chung 決定來採收這些蘋果,為了方便,Chung 已經將樹枝用 $1 \sim n$ 進行編號 (其中樹幹編號必為 $1$),且已經將在同個樹枝上的蘋果分類為同一組,並提供給你兩個資訊:每個樹枝連接的樹枝編號,每個樹枝上的蘋果數量。
因為 Chung 很懶惰,所以即使他有很多個籃子,卻只想要選定一個籃子來採收,但這樣就面臨了一個問題:如果採收的蘋果太少,Chung 就會覺得吃不飽,反之如果採收的蘋果太多又會造成浪費,所以他想要預先知道使用每個籃子分別可以採收多少蘋果。
Chung 會從樹幹開始採收,先採收和樹幹連接的那些樹枝們上的蘋果,再採收和那些樹枝們連接的樹枝們上的蘋果,以此類推一層一層往上採收,但是,假如採收完這一層之後就會讓籃子裝不下,那麼 Chung 就會放棄採收這一層並停止採收。
Chung 想要知道,使用每個籃子分別最高可以採收到第幾層,你能寫個程式幫幫他嗎?(樹幹為第 $0$ 層)
### Input
輸入第一行將有兩個整數 $n,m\ (1 \le n,m \le 2 \times 10^5)$,分別代表蘋果樹的樹枝數量和 Chung 擁有的籃子數量。
接著一行有 $m$ 個整數 $c_1,c_2,\dots,c_m\ (0 \le c_i \le 5 \times 10^6)$,分別代表第 $i$ 個籃子的容量 (至多可以裝的蘋果數量)。
接著會有 $n$ 行,第 $i\ (1 \le i \le n)$ 行第一個整數為 $k_i\ (1 \le k_i < n)$,代表有 $k_i$ 個樹枝和編號為 $i$ 的樹枝連接,緊接著有 $k_i$ 個整數 $x_{i,1},x_{i,2},\dots,x_{i,k_i}\ (1 \le x_{i,j} \le n,\ 1 \le j \le k_i)$,分別代表和編號 $i$ 的樹枝相連的樹枝編號,彼此之間用空白隔開。
最後一行有 $n$ 個整數 $a_1,a_2,\dots,a_n\ (0 \le a_i \le 1000)$,彼此用空白隔開,$a_i$ 代表編號為 $i$ 的樹枝上有幾顆蘋果 ($a_1$ 保證為 $0$)。
### Output
請總共輸出 $m$ 行,第 $i$ 行請輸出在使用第 $i$ 個籃子的情況下,Chung 最高可以採收到第幾層。
### Input Format
$n\ m$
$c_1\ c_2\ \dots c_m$
$k_1\ x_{1,1},x_{1,2},\dots,x_{1,k_1}$
$k_2\ x_{2,1},x_{2,2},\dots,x_{2,k_2}$
$\dots$
$k_n\ x_{n,1},x_{n,2},\dots,x_{n,k_n}$
$a_1\ a_2\ \dots a_n$
### Output Format
$ans_1$
$ans_2$
$\dots$
$ans_m$
### Testcase
- Input 1
```
6 4
0 6 8 21
2 2 3
1 1
4 1 4 5 6
1 3
1 3
1 3
0 1 6 2 7 5
```
- Output 1
```
0
0
1
2
```
### Subtask
- Subtask 1 (30%) - $n,m \le 1000$
- Subtask 2 (70%) - 無其他限制
### Solution
- Subtask 1
- 我們可以對於每筆詢問都做一次 bfs,基於 bfs 的性質,每次走訪到的點都是層層遞增,剛好符合求深度總和的需求。
- 題解使用到的技巧是「一次處理完同一深度的節點」,具體實作方式就是每次都一次處理完當前 queue 裡的節點,再依序將新的一層推進 queue,這樣就可以保證每次在 queue 中都是同深度的節點,這樣一來在計算同一層的總和時就會很方便。
- 總時間複雜度:$O(nm)$
- Subtask 2
- 觀察到每次的操作所求的資訊其實可以先被儲存起來
- 我們只需要維護各深度蘋果總和的前綴和,就可以針對每筆詢問二分搜到總和不超過容量的最大值,$O(\log n)$ 解決
- 題解使用 dfs 實作,並開一個陣列維護深度總和
- 總時間複雜度:$O(n+m \log n)$
### Code
- Subtask 1
```cpp=
#include<bits/stdc++.h>
using namespace std;
//declare
const int maxn = 2e5+5;
int n,m,c[maxn],cnt[maxn];
bool vis[maxn];
vector<int> G[maxn];
//
int bfs(int cap){
for(int i=1;i<=n;i++) vis[i] = 0;
queue<int> q;
q.push(1);
vis[1] = 1;
int sum = 0, depth = 0;
while(q.size()){
int sz = q.size();
for(int i=0;i<sz;i++){
int cur = q.front(); q.pop();
for(int adj : G[cur]){
if(vis[adj]) continue;
vis[adj] = 1;
sum += cnt[adj];
q.push(adj);
}
}
if(sum > cap) return depth;
depth++;
}
return depth-1;
}
int main(){
cin>>n>>m;
for(int i=0;i<m;i++) cin>>c[i];
for(int i=1;i<=n;i++){
int k; cin>>k;
for(int j=0;j<k;j++){
int x; cin>>x;
G[i].push_back(x);
}
}
for(int i=1;i<=n;i++) cin>>cnt[i];
for(int i=0;i<m;i++){
cout<<bfs(c[i])<<"\n";
}
return 0;
}
```
- Subtask 2
```cpp=
#include<bits/stdc++.h>
using namespace std;
//declare
const int maxn = 2e5+5;
int n,m,c[maxn],depth[maxn],cnt[maxn],sum[maxn],max_depth;
bool vis[maxn];
vector<int> G[maxn], pre{0};
//
void dfs(int cur){
for(int adj : G[cur]){
if(vis[adj]) continue;
vis[adj] = 1;
depth[adj] = depth[cur] + 1;
dfs(adj);
}
sum[depth[cur]] += cnt[cur];
max_depth = max(max_depth, depth[cur]);
}
int main(){
cin>>n>>m;
for(int i=0;i<m;i++) cin>>c[i];
for(int i=1;i<=n;i++){
int k; cin>>k;
for(int j=0;j<k;j++){
int x; cin>>x;
G[i].push_back(x);
}
}
for(int i=1;i<=n;i++) cin>>cnt[i];
vis[1] = 1;
dfs(1);
for(int i=1;i<=max_depth;i++) pre.push_back(pre.back() + sum[i]);
for(int i=0;i<m;i++) cout<<upper_bound(pre.begin(),pre.end(),c[i]) - pre.begin() - 1<<"\n";
return 0;
}
```
- Python by 橘子
```python=
import bisect
n,m = map(int,input().split())
c = list(map(int,input().split()))
con = [[int(s)-1 for s in input().split()][1:] for _ in range(n)]
vis = [0]*n
a = list(map(int,input().split()))
v = []
x = [0]
y = []
vis[0] = 1
cur = 0
while x:
for i in x:
cur += a[i]
for j in con[i]:
if not vis[j]:
y.append(j)
vis[j] = 1
v.append(cur)
x = y
y = []
v.append(10**18)
for i in c:
print(bisect.bisect_left(v,i+1)-1)
```
## 活動 (Event)
> 出題者:C0DET1GER
> 難度:p4
> 考點:貪心、排序、動態規劃
### Statement
APCS 模擬測驗團隊雖然最初是為了舉辦模擬測驗而成立的,但後來因為大家的鼓勵、參與,模擬測驗團隊也開始有辦理其他活動的想法(例如:APCS Camp、實體 APCS 模擬測驗...)。
對於每一個活動,都會有一定的收益(因為 APCS 模擬測驗團隊是一個公益組織,收益有可能為 $0$,甚至是負的)和獲益人數。
APCS 模擬測驗團隊當然希望能夠讓越多人參與越好,但要考量到有些活動會帶來虧損,因此團隊想要在所有可能的舉辦方案中,選擇一個總收益 $\ge 0$ 的方案,且參與人數盡量大。
除此之外,因為時間上的問題,在 $N$ 個活動中至多只能舉辦 $K$ 個相互不重複的活動,否則團隊將會忙到焦頭爛額。
因此模擬測驗團隊希望你能夠幫幫忙,給定 $N$ 個活動的收益和參與人數,請求出在所有可能的舉辦方案中,在總收益 $\ge 0$ 的情況下參與人數總和的最大值(初始資金為 $0$)。
### Input
第一行輸入兩個正整數 $N$ $(1 \leq N \leq 35)$, $K$ $(1 \leq K \leq N)$
接下來輸入 $N$ 行,每一行包含兩個數字:$a_i$ $(-10^7 \leq a_i \leq 10^7)$和 $b_i$ $(0 \leq b_i \leq 100)$,分別代表第 $i$ 個活動的淨收益和獲益人數。
### Output
一個數字,代表 $K$ 個活動(按照任意順序進行)的最大可能收益人數總和。
### Testcase
- Input 1
```
5 3
-50 70
-70 80
100 10
-1 2
-100 100
```
- Output 1
```
110
```
### Subtask
- Subtask 1 (10%):$N \leq 8$
- Subtask 2 (10%):$N \leq 20$
- Subtask 3 (30%):$|a_i| \leq 100$
- Subtask 4 (50%):題目範圍限制
### Note
最佳解為:先取 3 再取 5。因為題目是說至多 $K$ 個活動,活動數量可以小於 $K$。
### Solution
(此處 $a_i$ 一律代表第 $i$ 個活動的淨收益,$b_i$ 代表第 $i$ 個活動的獲益人數
* Subtask 1:
* 先暴力枚舉要哪些活動再枚舉所有可能排列。
* 時間複雜度:$O(2^N \times N!)$
* Subtask 2:
* 先貪心的將活動按照 $a_i$ 排序再枚舉
* 時間複雜度:$O(N \log N + 2^N)$
* Subtask 3:
* 先貪心的將活動按照 $a_i$ 排序
* 這個 Subtask 開始要使用動態規劃。可以設計狀態 $dp[i][j][k]$,其中 $i$ 代表前 $N$ 個活動、$j$ 代表現在已經舉辦了幾個活動、$k$ 代表現在有多少錢、$dp[i][j][k]$ 代表在考慮前 $i$ 個活動之中辦了 $j$ 個活動、最後有 $k$ 塊錢的時候,參與人數最大可能值。
* 因為已經預先照 $a_i$ 排序,不會有因為順序錯誤而導致 DP 答案錯誤的問題
* 轉移:$dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - 1][k - a_i] + b_i)$
* 初始化:$dp[i][j][k] = 0$
* 答案:dp 陣列中的最大值
* 時間複雜度:$O(N^2 \times \sum{\max(a_i, 0)} )$
* Subtask 4:
* (人數至多跟 Subtask 3 的 profit 一樣大的提示應該夠大了吧)
* 先貪心的將活動按照 $a_i$ 排序
* 發現 $|a_i|$ 的範圍太大了,用原本的方法時間複雜度會炸開。但是可以改變狀態的定義。可以設計狀態 $dp[i][j][k]$,其中 $i$ 代表前 $N$ 個活動、$j$ 代表現在已經舉辦了幾個活動、$k$ 代表現在總共有多少人參與、$dp[i][j][k]$ 代表在考慮前 $i$ 個活動之中辦了 $j$ 個活動、來的人有 $k$ 個的時候,獲利的最大可能值。
* 轉移:$dp[i][j][k] = \max(dp[i - 1][j][k], dp[i - 1][j - 1][k - b_i] + a_i)$
* 要注意轉移中如果 $dp[i - 1][j - 1][k - b_i] < 0$,要直接把 $dp[i][j][k]$ 設為 $dp[i - 1][j][k]$
* 初始化:$dp[0][0][0] = 0$、$dp[i][j][k] = -1$
* 答案:在所有 $dp[i][j][k] \geq 0$ 中,$k$ 的最大可能值。
* 時間複雜度:$O(N^2 \times \sum{b_i})$
### Code
- Subtask 1
```cpp=
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
int N, K;
cin >> N >> K;
vector<pair<int, int>> event(N);
for (int i = 0; i < N; i++){
cin >> event[i].first >> event[i].second;
}
int maximum = 0;
for (int i = 0; i < (1LL << N); i++){
vector<int> chosen;
for (int j = 0; j < N; j++){
if (i & (1 << j)){
chosen.push_back(j);
}
}
if (chosen.size() <= K){
do{
int money = 0, cnt = 0;
for (int j = 0; j < chosen.size(); j++){
money += event[chosen[j]].first;
if (money < 0){
break;
}
cnt += event[chosen[j]].second;
}
maximum = max(maximum, cnt);
}while (next_permutation(chosen.begin(), chosen.end()));
}
}
cout << maximum << "\n";
}
```
- Subtask 2
```cpp=
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool cmp(pair<int, int> a, pair<int, int> b){
return a.first > b.first;
}
int main(){
int N, K;
cin >> N >> K;
vector<pair<int, int>> event(N);
for (int i = 0; i < N; i++){
cin >> event[i].first >> event[i].second;
}
sort(event.begin(), event.end(), cmp);
int maximum = 0;
for (int i = 0; i < (1LL << N); i++){
int money = 0, cnt = 0, used = 0;
for (int j = 0; j < N; j++){
if (i & (1 << j)){
money += event[j].first;
if (money < 0){
break;
}
cnt += event[j].second;
used++;
}
}
if (used <= K){
maximum = max(maximum, cnt);
}
}
cout << maximum << "\n";
}
```
- Subtask 3
```cpp=
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool cmp(pair<int, int> a, pair<int, int> b){
return a.first > b.first;
}
int main() {
int N, K;
cin >> N >> K;
int sum = 0;
vector<pair<int, int>> event(N);
for (int i = 0; i < N; i++){
cin >> event[i].first >> event[i].second;
sum += max(event[i].first, 0);
}
sort(event.begin(), event.end(), cmp);
vector<vector<vector<int>>> dp(N + 1, vector<vector<int>>(K + 1, vector<int>(sum + 1, -1)));
dp[0][0][0] = 0;
int maximum = 0;
for (int i = 1; i <= N; i++){
for (int j = 0; j <= K; j++){
for (int k = 0; k <= sum; k++){
dp[i][j][k] = dp[i - 1][j][k];
if (j != 0 && k >= event[i - 1].first && k <= (sum + event[i - 1].first) && dp[i - 1][j - 1][k - event[i - 1].first] >= 0){
dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - 1][k - event[i - 1].first] + event[i - 1].second);
}
maximum = max(maximum, dp[i][j][k]);
}
}
}
cout << maximum << "\n";
}
```
- Subtask 4
``` cpp=
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool cmp(pair<int, int> a, pair<int, int> b){
return a.first > b.first;
}
int main() {
int N, K;
cin >> N >> K;
int sum = 0;
vector<pair<int, int>> event(N);
for (int i = 0; i < N; i++){
cin >> event[i].first >> event[i].second;
sum += event[i].second;
}
sort(event.begin(), event.end(), cmp);
vector<vector<vector<int>>> dp(N + 1, vector<vector<int>>(K + 1, vector<int>(sum + 1, -1)));
dp[0][0][0] = 0;
int maximum = 0;
for (int i = 1; i <= N; i++){
for (int j = 0; j <= K; j++){
for (int k = 0; k <= sum; k++){
dp[i][j][k] = dp[i - 1][j][k];
if (j != 0 && k >= event[i - 1].second && dp[i - 1][j - 1][k - event[i - 1].second] >= 0){
dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - 1][k - event[i - 1].second] + event[i - 1].first);
}
if (dp[i][j][k] >= 0){
maximum = max(maximum, k);
}
}
}
}
cout << maximum << "\n";
}
```
- 折半枚舉 by 橘子
```python=
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll big = 1e18;
int main(){
ios::sync_with_stdio(0);cin.tie(0);
ll n,k;
cin >> n >> k;
vector<pair<ll,ll>> a(36,{0,0});
for(ll i = 0;i<n;i++) cin >> a[i].first >> a[i].second;
n = 36;
sort(a.begin(),a.end());
vector<pair<ll,ll>> b(1<<18,{0,0});
vector<vector<pair<ll,ll>>> c(19);
ll ans = 0;
for(ll i = 1;i<(1<<18);i++){
b[i] = b[i-(i&-i)];
ll j = __builtin_ctzll(i);
b[i].first += a[j].first;
b[i].second += a[j].second;
ll cnt = __builtin_popcountll(i);
if(cnt<=k&&b[i].first>=0) ans = max(ans,b[i].second);
if(cnt<k){
for(ll w = cnt;w<=18;w++)c[w].push_back(b[i]);
}
}
for(auto &o : c){
sort(o.begin(),o.end());
for(ll i = o.size()-2;i>=0;i--){
o[i].second = max(o[i].second,o[i+1].second);
}
}
vector<pair<ll,ll>> d(1<<18,{0,0});
for(ll i = 1;i<(1<<18);i++){
d[i] = d[i-(i&-i)];
ll j = __builtin_ctzll(i);
d[i].first += a[j+18].first;
d[i].second += a[j+18].second;
ll cnt = __builtin_popcountll(i);
if(cnt<=k&&d[i].first>=0) ans = max(ans,d[i].second);
if(cnt<k&&k-cnt<=18){
auto &o = c[k-cnt];
if(o.empty()) continue;
auto it = lower_bound(o.begin(),o.end(),make_pair(-d[i].first,0ll));
if (it!=o.end()){
ans = max(ans,d[i].second+(*it).second);
}
}
}
cout << ans << "\n";
}
```
- Python by 橘子
```python=
def main():
n, k = map(int,input().split())
a = [tuple(map(int,input().split())) for _ in range(n)]
a.sort(reverse=True)
dp = [[-10**10]*3505 for _ in range(k+1)]
dp[0][0] = 0
ed = [0]*(k+1)
for x, y in a:
for i in range(k,0,-1):
ed[i] = max(ed[i],ed[i-1]+y)
arr1 = dp[i]
arr2 = dp[i-1]
for j in range(y,3502):
arr1[j] = max(arr1[j],arr2[j-y]+x)
ans = 0
for j in range(k,0,-1):
arr = dp[j]
for i in range(ed[j],ans,-1):
if arr[i]>=0:
ans = max(ans,i)
print(ans)
main()
```