一大堆解題報告

可以用來監督建電學術水了多久

前情提要

有個叫蘇昱亘的人,為了考上嚮往已久的建電學術,在 IG 中發誓,要每天 AC 一題算法題。

 

在茶會執秘、寒訓執秘、雙暑訓摧殘、備課、學業壓力等種種種因素下,毫無懸念的失敗了。

 

同時,他發現在 IG 發現動,告訴大家自己 AC 了什麼題目,除了顯現自己有多弱外,在眾電神前根本是關公面前耍大刀。

 

Legend Never Dies.

雖然他不見得有時間再每天刷題了

不過他會把遇到的有趣題目

全部放到這邊來

我能在此找到什麼

首先,會有人特地來翻我主頁,並找到這裡,我還滿開心的,祝你打扣愉快。

 

基本上,這邊會有 AC 扣、題目連結、心得、分類、嘴砲,偶而摻雜一些毫無相關的東西,講更白就是我直接把這裡當日記寫。

 

我之所以沒公開分享這份簡報,就是因為裡面的內容非常不成熟,而且我根本沒要認真寫題解的意思,更像是自己打自己爽。所以當成小丑觀察就好,不用太期待能從中獲得什麼收獲。

DP、LIS、sort

2021 Jan. APCS #4

簡單來說

你有一張 n * n 的圖

圖上有 y 個點,位置不重複

以左下角為 (0, 0),上、右為正向

 

今從 (0, 0) 出發,只能往右或上走

求最多可經過多少個點

 

1 <= n <= 1e7

1 <= y <=  2e5

#include <bits/stdc++.h>
#define gura ios_base::sync_with_stdio(false), cin.tie(0);
#define pii pair <int, int>
#define F first
#define S second
using namespace std;

vector <pii> location;
const int MAXN = 2e5 + 17;

bool cmp (pii a, pii b){
  if (a.F < b.F) return true;
  if (a.F > b.F) return false;
  if (a.S < b.S) return true;
  return false;
}

vector <int> lis;

void solve (){
  int border_size; cin >> border_size;
  while (border_size--){
    int x, y; cin >> x >> y;
    location.push_back({x, y});
  }
  sort(location.begin(), location.end(), cmp);

  lis.push_back(location[0].S);
  for (int i = 1; i < location.size(); i++){
    if (location[i].S >= lis.back()) lis.push_back(location[i].S);
    else{
      auto to = upper_bound(lis.begin(), lis.end(), location[i].S);
      *to = location[i].S;
    }
  }

  cout << lis.size() << '\n';
}

int main (){
  gura
  solve();
}

很垃圾的心得

  • 被騙滿慘的,我本來一直以為是要建立一張圖,並且對做DP,由左下至右上找出答案。此做法可行,但 N 的極限是 1e7,整張圖就 1e14 了,顯然只是個 20% 部分分解
  • 接下來,我承襲上面建圖的邏輯,既然點的數量只有 2e5 個,把他們做離散化再建圖。worse case 是 4e10,顯然並非 AC 解
  • APCS 只會考 Greedy 跟 DP 啊,沒道理這麼難吧
  • 最後投降去找題解,大概只看了前 30 秒,他一講出"LIS"這個詞我就明白了。接著直接關掉題解開始自己寫,LIS 也算古董了,寫起來卡卡的要想很久。

實作、vector、pointer

2016 Oct. APCS #4

這題無法簡單說

指令有以下幾種:

nB,n 壘打,所有人移動 n 格

HR,全壘打,就是四壘打

others,全部都是出局,誰鳥他

 

-每有一人回到本壘則得 1 分

-每累計 3 個 out,壘包清空

-求總計 r 個 out 時的得分

 

-輸入有 10 行,最後一行為 r,其  它是打擊資料,要直的讀

#include <bits/stdc++.h>
#define gura ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define vecs vector <string>
#define hit source[index_m][index_h]
using namespace std;

int tar, total_out = 0, tmp_out = 0;
int score = 0;
vector <vecs> source(10);
vector <int> field;

void update(){
  sort(field.begin(), field.end());
  while(!field.empty()){
    if (field.back() >= 4){
      score++;
      field.pop_back();
    }else{
      break;
    }
  }

  // for (auto to:field) cout << to << ' ';
  // cout << "score = " << score << '\n';
}

void solve(){
  for (int i = 1; i <= 9; i++){
    int tmp; cin >> tmp;
    while(tmp--){
      string str; cin >> str;
      source[i].push_back(str);
    }
  }
  cin >> tar;

  int index_m = 1, index_h = 0;
  while (total_out < tar){
    int run = 0;
    if (hit == "1B") run = 1;
    else if (hit == "2B") run = 2;
    else if (hit == "3B") run = 3;
    else if (hit == "HR") run = 4;
    else{
      total_out++;
      tmp_out++;
    }

    if (run != 0){
      for (int i = 0; i < field.size(); i++){
        int *to = &field[i];
        *to += run;
      }
      field.push_back(run);
      update();
    }else if (tmp_out >= 3){
      // cout << "clear field.\n";
      field.clear();
      tmp_out = 0;
    }

    if (index_m < 9) index_m++;
    else{
      index_m = 1;
      index_h++;
    }
  }
  cout << score << '\n';
}

int main (){
  gura
  solve();
}

這是水題。沒什麼心得

  • 不過好久沒寫實作題了喔,不用被演算法虐的感覺特別舒服。雖然浪費我半小時,但本體上難度不高
  • 少數可以讓我用到指標的地方
  • 幹,為什麼可以那麼可愛(對跟題目完全無關)
  • 我好想去騎車喔
  • 我不知道要打什麼了,然後現在半夜 1:04,來不及Valorant 了,我要去打 APEX

BST、二元樹遍歷、sort

簡單來說

在 worst case 下

二元搜尋樹做 N 次 insert

最高複雜度為 O ( N 2 )

 

倘若你已經知道所有數字

要被 insert 的順序

求這棵 BST 中序遍歷之結果

 

1 <= N <= 1e6

- 1e30 <= num <= 1e30

#include <bits/stdc++.h>
#define gura ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int

using namespace std;

vector <ll> tmp;
int main (){
  gura
  int N; cin >> N;
  while(N--){
    ll temp; cin >> temp;
    tmp.push_back(temp);
  }
  sort(tmp.begin(), tmp.end());
  for (auto to:tmp) cout << to << ' ';
  cout << '\n';
}

純觀念 超級水

BST 的中序遍歷

就是所有數字的遞增數列

BFS、dijkstra algorithm

這題到現在還是爛的,有路人要幫我 de 嗎

Bruh

你有一張二維的圖 邊長上限 1e3

上面有個起點與終點

另外有 n 格是道路

 

每格道路都有自己的難度

當玩家經過難度為 x 的道路時

若本身等級為 y

則須將等級提升至 x

 

求從起點走到終點

所需的最小等級

#include <bits/stdc++.h>
using namespace std;
#define gura ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define INF 1e9 + 17
#define pq priority_queue
#define pii pair <int, int>
#define pipii pair <int, pii>
#define ll long long int
#define F first
#define S second

const int MAXN = 1e3 + 17;

int tmp_ans[MAXN][MAXN];
bool enter[MAXN][MAXN] = {false};
pq <pipii, vector <pipii>, greater<pipii>> waiting;

void solve (){
  int length, width; cin >> length >> width;
  for (int i = 0; i <= length; i++)
    for (int j = 0; j <= width; j++)
      tmp_ans[i][j] = INF;

  int srt_x, srt_y, tar_x, tar_y; cin >> srt_y >> srt_x >> tar_y >> tar_x;
  enter[srt_y][srt_x] = true; enter[tar_y][tar_x] = true;
  tmp_ans[srt_y][srt_x] = 1; tmp_ans[tar_y][tar_x] = 1;

  int walkable_count; cin >> walkable_count;
  while (walkable_count--){
    int x, y, level; cin >> y >> x >> level;
    enter[y][x] = true;
    tmp_ans[y][x] = level;
  }
  waiting.push({0, {srt_y, srt_x}});

  while (!waiting.empty()){
    auto wt = waiting.top();
    int w = wt.F, y = wt.S.F, x = wt.S.S;

    //printf("location = {%d, {%d, %d}}\n", w, y, x);
    if (y+1 < length && enter[y+1][x]){
      if (y+1 == tar_y && x == tar_x){
        cout << w << '\n';
        return;
      }
      if (tmp_ans[y+1][x] > w) waiting.push({tmp_ans[y+1][x], {y+1, x}});
      else waiting.push({w, {y+1, x}});
      enter[y+1][x] = false;
    }
    if (y-1 > -1 && enter[y-1][x]){
      if (y-1 == tar_y && x == tar_x){
        cout << w << '\n';
        return;
      }
      if (tmp_ans[y-1][x] > w) waiting.push({tmp_ans[y-1][x], {y-1, x}});
      else waiting.push({w, {y-1, x}});
      enter[y-1][x] = false;
    }
    if (x+1 < width && enter[y][x+1]){
      if (y == tar_y && x+1 == tar_x){
        cout << w << '\n';
        return;
      }
      if (tmp_ans[y][x+1] > w) waiting.push({tmp_ans[y][x+1], {y, x+1}});
      else waiting.push({w, {y, x+1}});
      enter[y][x+1] = false;
    }
    if (x-1 > -1 && enter[y][x-1]){
      if (y == tar_y && x-1 == tar_x){
        cout << w << '\n';
        return;
      }
      if (tmp_ans[y][x-1] > w) waiting.push({tmp_ans[y][x-1], {y, x-1}});
      else waiting.push({w, {y, x-1}});
      enter[y][x-1] = false;
    }

    waiting.pop();
  }
}

int main (){
  gura
  solve();
}

這還不是 AC CODE

神奇的是我可以先打心得

  • 其實我有在 CF 上寫過這個的類題,但我後來就忘得一乾二淨了。這貌似是 BFS 很常見的用法說
  • 所以有沒有人要幫我 de,我會很愛你 ~(>_<。)\
  • 我後來是有找到其他電神的 AC 解,但在邏輯上我看不出我的扣跟他的有什麼差別
  • 我決定叫這種作法為 dijkstra_BFS 

unordered_map、recursion

AtCoder Beginner Contest Round 275 pD

真的假的啦

定義一個數列如下

#include <bits/stdc++.h>
#define gura ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define unmap unordered_map
#define usll unsigned long long int
using namespace std;

unmap <usll, usll> dic;

usll f(usll num){
  if (num == 0) return 1;
  if (dic.count(num)) return dic[num];
  dic[num] = f(num / 2) + f(num / 3);
  return dic[num];
}

void solve (){
  usll num; cin >> num;
  cout << f(num) << '\n';
}

int main (){
  gura
  solve();
}
a_i = \left\{ \begin{matrix} 0 &if& i = 0\\ \lfloor \frac{n}{2} \rfloor + \lfloor \frac{n}{3} \rfloor &if& i \geq 1 \end{matrix} \right.

求數列第 i 項

 

\forall\;0 \leq i \leq 10^{18}

吐血

  • 身為 pD,還給了個這麼可愛的名字,我還很單純地懷疑怎麼可能會出遞迴題
  • 然後...驚訝地發現 MAXN = 1e18,換言之,複雜度需要 log_3 (N) 才有機會過
  • 當時有個大腦卡死的人,完全忘記 map 這個東西
  • 所以我的 400 分隨風而去了,像那河流般 QAQ

unordered_set、vector

AtCoder Beginner Contest Round 274 pD

來自 pD 的詛咒

你有一個長度為 N 的向量集合 A

其中定義點 \(P_1\) 位於 (0, 0)

點 \(P_2\) 位於 (\(A_1\) , 0)

而終點是 \(P_{N+1}\) 

 

從 \(P_2\) 開始

每個點 \(P_{i+1}\) 與上點距離

均為 \(A_{i}\)

且與上點的連線夾角須為 90 度

求終點是否可能落在

指定的點 (x, y)?

#include <bits/stdc++.h>
#define ic ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define pii pair <int, int>
#define ll long long int
#define F first
#define S second
#define pq priority_queue
#define usll unsigned long long int
using namespace std;

bool judge (int tar, vector <int> num){
  unordered_set <int> exist;
  exist.insert(0);
  for (auto to:num){
    unordered_set <int> tmp;
    swap(exist, tmp);
    for (auto au:tmp){
      exist.insert(au + to);
      exist.insert(au - to);
    }
  }

  return exist.count(tar);
}

void solve (){
  int N, tar_x, tar_y; cin >> N >> tar_x >> tar_y;
  vector <int> num_x, num_y;
  int first; cin >> first;
  tar_x -= first;
  for (int i = 2; i <= N; i++){
    int tmp; cin >> tmp;
    if (i&1) num_x.push_back(tmp);
    else num_y.push_back(tmp);
  }

  if (judge(tar_x, num_x) && judge(tar_y, num_y)) cout << "Yes\n";
  else cout << "No\n";
}

int main (){
  ic
  solve();
}

我怎麼又沒寫出來

  • 最大失誤是眼瞎,我沒看到那句"The Segments \(P_i P_{i+1}\) and \(P_{i+1} P_{i+2}\) form a 90 degree angle

linked_list

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
using namespace std;

struct node{ //linked list 的節點
  int index = -1;
  int group = -1; // 此點屬於哪個組別
  node *next = nullptr;
};

const int MAXN = 1e6 + 17;
int self_group[MAXN] = {}; // self_group[index] = 第 index 個人所在組別,預設為 0
vector <node*> location(1017, nullptr); // 隊伍中的最後一個第 [group] 組的人在哪
vector <int> clear; // 拿來優化用的,我下回合初始化時要把誰歸零

struct linked_list{
  node *root = nullptr, *end = nullptr;
  int length = 0;

  void dequeue (){
    cout << root->index << '\n';
    if (location[root->group] != nullptr){ // 若隊伍中有與 root 同組的人(廢話)
      if (root->index == location[root->group]->index) // 若 root 自己就是該組的最後一個
        location[root->group] = nullptr; // 那現在隊伍中沒有這組的人了
    }
    if (length > 1){
      root = root->next;
    }else{
      root = nullptr;
      end = nullptr;
    }
    length--;
    //cout << "dequeue succeed\n";
  }

  void enqueue (int tar, int group){
    node *tmp = new node;
    tmp->index = tar;
    tmp->group = group;
    if (length == 0){
      root = tmp;
      end = tmp;
    }
    else if (location[group] == nullptr || location[group]->index == end->index){
      // (up) 隊伍中不存在該隊伍的人 或 該隊伍的最後一個人就是排尾
      end->next = tmp;
      end = tmp;
    }else{
      node *last = location[group];
      tmp->next = last->next;
      last->next = tmp;
    }
    if (group != 0) location[group] = tmp; // 如果這個人是有組別的
    length++;
    //cout << "enqueue succeed\n";
  }
};

void solve (){
  int N; cin >> N;
  for (int i = 1; i <= N; i++){
    for (int j = 0; j < 1017; j++) location[j] = nullptr;
    for (auto to:clear) self_group[to] = 0;
    clear.clear(); //這行有夠微妙 XDD
    linked_list list;
    cout << "Line #" << i << '\n';

    int group_count; cin >> group_count;
    for (int j = 1; j <= group_count; j++){
      int count; cin >> count;
      for (int k = 0; k < count; k++){
        int tmp; cin >> tmp;
        self_group[tmp] = j; //沒被設定到的就預設為 0
        clear.push_back(tmp);
      }
    }

    int query_count; cin >> query_count;
    while (query_count--){
      string opr; cin >> opr;
      if (opr == "DEQUEUE"){
        list.dequeue();
      }else{
        int index; cin >> index;
        list.enqueue(index, self_group[index]);
      }
    }
  }
}
int main (){
  icisc
  solve();
}

這題我 DE 好久

最大測資有 20 筆,在第一行吃進。

每筆測資中會有 N 個 組別

​組別中有若干個人

人數共有 M 個,都有自己的 index

 

現在對 list 有兩種操作,進行 Q 次:

(1) 把最前面的人 pop 掉,輸出其 index

(2) 加入 index = x 的人進隊伍,若隊伍中已有與

   他同組者,他直接插隊隊伍中最後一個同組者 

   至後方,否則排到最後面

 

\(\forall\) N \(\leq\) 2000,M \(\leq\) 1000,Q \(\leq 2e5\)

這題有給學弟妹,所以我要認真寫題解

  • 基本上看到類似"從中間插進去"的題目,八九不離十都是要你砸自由度最高的 linked_list,不要懷疑
  • 這邊你要記錄,第幾個人是屬於哪個組別。我一開始是用 set 做,但後來發現沒必要花這個 O(N log N),直接一條 array 開下去就好,至此是我做的第一層優化
  • 如果用一般模板照刻的話,會發現 TLE,還有更快的方法嗎?我們是要實作從中間插進去,如果每次都從 root 往後找插入點當然可行,但太慢了。既然插入點永遠只有那幾個,何不開個 array 直接紀錄插入點?反正我們是用指標做,這應該小功夫而已。至此是我做的第二層優化
  • 最後,MAXN = 1e6,每次都要整個初始化有點燒時,所以我又做了一個 vector 紀錄誰需要初始化。嗯,三重優化

vector、set

AtCoder Beginner Contest Round 278 pD

終於解出 pD 了

你有一個數列

然後現在有 3 種操作

 

1:把整個數列重製為 x

2:把第 t 個數字 + x

3:輸出第 t 個數字

 

對,就這樣,把操作做好

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define pii pair <int, int>
#define ll long long int
#define F first
#define S second
#define pq priority_queue
#define usll unsigned long long int
using namespace std;

const int MAXN = 2e5;
ll judge[MAXN];

void solve (){
  for (int i = 0; i < MAXN; i++) judge[i] = -1;
  usll asi = -1;

  vector <usll> num;
  int N; cin >> N;
  for (int i = 0; i < N; i++){
    int tmp; cin >> tmp;
    num.push_back(tmp);
  }
  int Q; cin >> Q;
  while (Q--){
    int opr; cin >> opr;
    if (opr == 1){
      int tmp; cin >> tmp;
      asi = tmp;
    }else if (opr == 2){
      int tar, plus; cin >> tar >> plus;
      if (asi != -1){
        if (judge[tar-1] != asi){
          num[tar-1] = asi;
          judge[tar-1] = asi;
        }
      }
      num[tar-1] += plus;
    }else{
      int tar; cin >> tar;
      if (asi != -1){
        if (judge[tar-1] != asi){
          num[tar-1] = asi;
          judge[tar-1] = asi;
        }
      }
      cout << num[tar-1] << '\n';
    }
  }
}
int main (){
  icisc
  solve();
}

其實這題很水

  • 只是因為他是 pD,所以我不放上來不行,對就這樣

三分搜

AtCoder Beginner Contest Round 279 pD

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll unsigned long long
using namespace std;

ll A, B;
double f (ll take){
  return (double) A/sqrt(1 + take) + (double) B*take;
}

void solve (){
  cin >> A >> B;
  ll l = 0, r = A / B;
  while (r - l > 2){
    ll ml = (l*2 + r) / 3;
    ll mr = (l + r*2) / 3;
    if (f(ml) < f(mr)) r = mr;
    else l = ml;
  }

  cout << fixed << setprecision(8) << min({f(l), f(r), f((l+r)/2)}) << '\n';
}

int main (){
  icisc
  solve();
}

初見但沒有殺

給定一函式

\(f(x) = \frac{A}{\sqrt{x+1}} + B\,x\)

求其最小值,誤差不得超過 \(10^{-6}\)

 

\(1 \leq A \leq 10^{18}\)

\(1 \leq B \leq 10^{18}\)

三分搜

  • 用於逼近連續函數的極值
  • 具體作法大概長這樣
int f(int n){
  //...
}

void find_(){
  int l = 0, r = 1e9; // r 視情況調整
  for (int i = 0; i < 2e5; i++){ //可選擇要做 n 次、或左右界差距小於某值停下之類
    int ml = (2*l + r) / 3;
    int mr = (l + 2*r) / 3;
    if (f(ml) < f(mr)) l = ml; //令其是凸函數
    else r = mr;
  }
}
  • 也有人 ml、mr 的取法是 \(\frac{l+r}{2}、\frac{l+mr}{2}\),但在這題會爛掉

題解

  • 首先要先觀察出這是一個連續凸函數
  • 令 x 為執行動作的次數,左界設為 0由於 \(\frac{A}{\sqrt{1+x}} + B\,x\) ,在 x \(\geq\) A 時,整體必 > A,故右界定為 A / B
  • 之後砸三分搜,我之前真的沒遇過這種題目

Vector

AtCoder Beginner Contest Round 289 pC

對,就是那麼暴力

你有 \(M\) 個集合

每個集合中可能會有 \(K\) 個數字

數字範圍從 1 到 \(N\)

 

若每個集合

只有"使用"與"不使用"

則想得到一個全集合(1 到 \(N\))

共有多少種組合?

 

\(1 \leq N、M、K \leq 10\)

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define pii pair <int, int>
#define ll long long int
#define F first
#define S second
#define pq priority_queue
#define usll unsigned long long int
#define pb push_back
using namespace std;
 
int N, M;
vector <set <int>> sets;
int ans = 0;
 
void dfs (vector <bool> exist, int ptr = 0, int exist_count = 0){
  // printf("ptr = %d, exist count = %d\n", ptr, exist_count);
  if (ptr == M){
    // cout << "exist count = " << exist_count << '\n';
    if (exist_count == N) ans++;
    return;
  }
  dfs(exist, ptr+1, exist_count); // without me
  for (auto to:sets[ptr]){
    if (!exist[to]){
      exist[to] = true;
      exist_count++;
    }
  }
  dfs(exist, ptr+1, exist_count); // with me
}
 
void solve (){
cin >> N >> M;
  sets.resize(M);
  for (int i = 0; i < M; i++){
    int k; cin >> k;
    while (k--){
      int tmp; cin >> tmp;
      sets[i].insert(tmp);
    }
  }
 
  dfs(vector <bool> (M+17, false));
  cout << ans << '\n';
}
 
int main (){
  icisc
  solve();
}

真的就暴力做下去

  • 我本來想說,這樣組合數會是 \(2^{10}\) 種,會爛掉卻完全忘記其值僅有 1024 而已

Data Structure、Greedy、sort、Difference Array Algorithm

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int
#define usll unsigned long long int
#define pii pair <int, int>
#define F first
#define S second
#define pq priority_queue
#define pb push_back
using namespace std;

void solve (){
  int N, M; cin >> N >> M;
  vector <usll> vec(N);
  for (auto &to:vec) cin >> to;
  sort(vec.begin(), vec.end(), greater<int>());

  vector <usll> predif(N, 0); // 計算出現次數
  while (M--){
    int l, r; cin >> l >> r;
    l--;r--; // cuz input is 1-based
    predif[l]++;
    if (r+1 < N) predif[r+1]--;
  }

  vector <usll> tmp = predif; //差分做前綴和來還原
  for (int i = 1; i < N; i++) tmp[i] += tmp[i-1];
  // for (auto to:tmp) cout << to << ' ';
  sort(tmp.begin(), tmp.end(), greater<int>());

  usll count = 0;
  for (int i = 0; i < N; i++) count += vec[i]*tmp[i];
  cout << count << '\n';
}

int main (){
  icisc
  solve();
}

一發入魂!!!

首先給你一個序列

接下來會有 M 次操作

每次操作選定範圍 [l, r]

將裡面的數字全部加起來

 

剛開始的序列,順序可以自由調整

aka 隨便你亂排

目的是盡可能讓做完所有操作後

得到的 總和 是最大值

 

求該值

我還以為論壇在唬爛

  • 會與這題相遇,本來只是我在備課,需要找差分的題目,論壇上剛好提到此題會用到。點進來後看到的標籤是 Greedy,我以為他在唬爛,但因為好玩就先存下來想說晚點寫
  • 既然順序可以完全讓我排,我先計算每個格子出現幾次針對區間做加減操作...這不就是差分嗎!!!
  • 順帶一提,usll 讓我多吃了一次 WA ==

binary exponential、mathematics

不錯啊這題真的有難度

共有 T 筆測資,\(1 \leq T \leq 10^5\)

每筆測資包含一個正整數 \(N\)

\(N\) 之極值是驚人的 \(10^{12}\)

你可以自由地把 N 分成很多段

長度 \(\geq 1\) 的小段

令此筆測資的價值

是各小段長度的相乘結果

求每筆測資的最大價值

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int
using namespace std;

const int MOD = 1e9 + 7;

ll Mod (ll n, ll m){
  return (n+m) % m;
}

ll Mult (ll a, ll b, ll m){
  return Mod(Mod(a, m) * Mod(b, m), m);
}

ll bipow (ll a, ll b, ll m){
  if (b == 0) return 1;
  if (b == 1) return a;
  if (b&1) return Mult(a, bipow(a, b-1, m), m);
  ll tmp = bipow(a, b/2, m);
  return Mult(tmp, tmp, m);
}

void solve (){
  int N; cin >> N;
  while (N--){
    ll num; cin >> num;
    if (num == 1) cout << "1\n";
    else if (num == 2) cout << "2\n";
    else if (num == 3) cout << "3\n";
    else if (num%3 == 0) cout << bipow(3, num/3, MOD) << '\n';
    else if (num%3 == 1) cout << Mult(4, bipow(3, (num-4)/3, MOD), MOD) << '\n';
    else cout << Mult(2, bipow(3, (num-2)/3, MOD), MOD) << '\n';
  }
}

int main (){
  icisc
  solve();
}
  • 最一開始的想法是,直接剖半會得到最優解,但這想法顯然是錯的
  • 接著我嘗試拿 DP 的邏輯去解,仍然是用剖半的邏輯,但會一直遞迴到本身等於 2 或 3 為止。答案當然是爛的
  • 最後我想到,如果把 6 分解,可得 2×2×2 或 3×3,而後者價值大於前者,所以基本上我只要盡可能拿 3 就夠了。餘數處理其實滿直覺的,那邊沒卡到我

區間 DP

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int
#define usll unsigned long long int
#define pii pair <int, int>
#define F first
#define S second
#define pq priority_queue
#define pb push_back
using namespace std;

const int INF = 1e9 + 17;

void solve (){
  int N; cin >> N;
  vector <int> vec(N+17);
  int dp[N+17][N+17];
  for (int i = 0; i <= N; i++)
    for (int j = 0; j <= N; j++)
      dp[i][j] = 0;

  for (int i = 1; i <= N; i++) cin >> vec[i];

  //initialization
  for (int i = 1; i+2 <= N; i++) dp[i][i+2] = vec[i]*vec[i+1]*vec[i+2];

  for (int i = 3; i < N; i++){ //length - 1
    for (int j = 1; j+i <= N; j++){
      dp[j][j+i] = INF;
      for (int k = j+1; k < j+i; k++){
        dp[j][j+i] = min(dp[j][j+i],
                         dp[j][k] + dp[k][j+i] + vec[j]*vec[k]*vec[j+i]);
      }
    }
  }

  cout << dp[1][N] << '\n';
}

int main (){
  icisc
  solve();
}

好熟悉的題型

你有 N 個湖泊,\(2 \leq N \leq 50\)

每個湖泊都有自己的大小

是一個不超過 100 的正整數

 

每次選擇 3 個相鄰湖泊

並且 吞噬 中間那個

此舉造成的 汙染值 乃三者大小相乘

 

你必須吞噬掉除端點外的所有湖泊

求造成的汙染最小值

  • 我怎麼記得邱品諭問過我類似的題目,什麼鬼史萊姆的
  • 我大腦的確有閃過那題,但還是先往錯誤方向走了 XDD
  • 我一開始想法是,令 dp[i][j] 為 [i, j] 的最佳解。每次把 i 與 j 同時往前推一格,而他們的間距在每隔結束後增加。dp 轉移式 dp[i][j] = min( dp[i-1][j] + vec[i]*vec[i-1]*vec[j], dp[i][j-1] + vec[i]*vec[j-1]*vec[j] )
  • 後來發現,每增加一個其實就要重算,而且 N 如此小肯定有原因吧?
  • 好爽喔我現在覺得我是 DP Master(才怪)

greedy、two pointers

在酒精催化下莫名 AC 

共有 T 筆測資,\(1 \leq T \leq 10^4\)

每筆測資包含

\(1 \leq n \leq 10^5\)

長度為 \(n\) 的 0/1 字串

你可以使用以下操作

每次為一組:

( 1 ) 選擇一個數字,將其加到另一個數字上

( 2 ) 移除原數字

 

求最少需要幾組操作

可使此字串不嚴格遞增

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int
#define usll unsigned long long int
#define pii pair <int, int>
#define F first
#define S second
#define pq priority_queue
#define pb push_back
using namespace std;

void solve (){
  int T; cin >> T;
  while (T--){
    int N; cin >> N;
    vector <int> vec(N);
    for (auto &to:vec) cin >> to;
    int front = 0, back = N-1;
    int count = 0;
    while (true){
      while (vec[front] == 0 && front < N) front++;
      while (vec[back] == 1 && back >= 0) back--;
      if (front >= back) break;
      count++;
      front++; back--;
    }
    cout << count << '\n';
  }
}

int main (){
  icisc
  solve();
}
  • 其實有點小作弊,因為我是用 tag 找到這題的所以我知道它考雙指標
  • "前面碰到 1 會爛掉,後面碰到 0 會爛掉"
  • 那我就讓兩個指標,一個從頭開始一個從尾開始。從頭的只要遇到 1 就會停下來,從尾的只要遇到 0 就會停下來。雙方都停下來時,只要兩者不曾相遇,操作組數 + 1,兩者皆跳一格
  • 順帶一提我這題是在青旅 AC 的,還喝了一點酒,說實話有點混沌,然後莫名其妙就過,舒服。AC 後去跑去 discord 自己的伺服器 emo ,說真的我還是好痛苦

Dynamic Programming

AtCoder Beginner Contest Round 291 pD

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int
#define usll unsigned long long int
#define pll pair <ll, ll>
#define F first
#define S second
#define pq priority_queue
#define pb push_back
using namespace std;

ll Mod (ll n, ll m){
  return (n+m) % m;
}

ll Plus (ll a, ll b, ll m){
  return Mod(Mod(a, m) + Mod(b, m), m);
}

const int MOD = 998244353;

void solve (){
  int N; cin >> N;
  vector <pll> vec(N);
  for (auto &to:vec){
    cin >> to.F;
    cin >> to.S;
  }

  vector <vector <ll>> dp(N, vector <ll>(2, 0));
  dp[0][0] = 1; dp[0][1] = 1;
  for (int i = 1; i < N; i++){
    if (vec[i].F != vec[i-1].F) dp[i][0] = Plus(dp[i][0], dp[i-1][0], MOD);
    if (vec[i].F != vec[i-1].S) dp[i][0] = Plus(dp[i][0], dp[i-1][1], MOD);
    if (vec[i].S != vec[i-1].F) dp[i][1] = Plus(dp[i][1], dp[i-1][0], MOD);
    if (vec[i].S != vec[i-1].S) dp[i][1] = Plus(dp[i][1], dp[i-1][1], MOD);
  }

  cout << Plus(dp[N-1][0], dp[N-1][1], MOD) << '\n';
}

int main (){
  icisc
  solve();
}

有人英文太爛啦

給定 \(N\) 張卡片,排成一維直線

\(1 \leq N \leq 2×10^5\)

分別給予從 1 至 \(N\) 的編號

每張卡片的正面與背面都有數字

值域從 1 至 \(10^9\)

剛開始時每張卡均正面朝上

 

求共有幾種可能性

使得檯面上所有向上的數字

兩兩相鄰 皆不同

將答案模上 \(998244353\)

  • Bruh,我當時沒看懂 adjacent,似乎被我直覺忽略了導致我誤解題目是,所有檯面上向上的數字都不同
  • 等事後發現,大概就想到 DP 解了,後面卡了幾次 RE最後找到原因出在,我初始化有一格打錯,會開成 N×N 的 DP 陣列,話說這應該是 MLE 吧?????
  • 誒我突然想到,如果有考幹的學弟妹找到這份簡報其實還滿賺的誒,我是不是該暫時隱藏掉啊
  • 但類似題也一卡車,好像又沒必要,什麼局

DFS

AtCoder Beginner Contest Round 292 pD

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int
#define usll unsigned long long int
#define pii pair <int, int>
#define F first
#define S second
#define pq priority_queue
#define pb push_back
using namespace std;

vector <vector <int>> graph;
vector <bool> visited;
vector <pii> path;
vector <bool> visited_path;
int node_count = 0;
int edge_count = 0;

bool dfs(int pos){
  // printf("In dfs, pos = %d\n", pos);
  visited[pos] = true;
  node_count++;
  for (auto to:graph[pos]){
    if (!visited_path[to]){
      visited_path[to] = true;
      edge_count++;
    }
    if (pos == path[to].F){
      if (!visited[path[to].S]) dfs(path[to].S);
    }else{
      if (!visited[path[to].F]) dfs(path[to].F);
    }
  }
  if (node_count == edge_count) return true;
  return false;
}

void solve (){
  int N, M; cin >> N >> M;

  if (N != M){
    cout << "No\n";
    return;
  }

  graph.resize(N+17);
  visited.resize(N+17, false);
  path.resize(M+17);
  visited_path.resize(M+17, false);

  for (int i = 0; i < M; i++){
    int a, b; cin >> a >> b;
    path[i] = {a, b};
    graph[a].pb(i);
    graph[b].pb(i);
  }

  for (int i = 1; i <= N; i++){
    if (!visited[i]){
      node_count = 0;
      edge_count = 0;
      if (!dfs(i)){
        cout << "No\n";
        return;
      }
    }
  }

  cout << "Yes\n";
}

int main (){
  icisc
  solve();
}

ㄟ我這次有分

給定 \(N\) 個點與 \(M\) 條邊

\(1 \leq N \leq 2×10^5,0 \leq M \leq 2×10^5\)

可能包含自環、不保證圖連通

 

求每個連通分量中

邊的數量是否雨點的數量相等

若全數符合輸出 "Yes"

否則輸出"No"

  • 我最一開始的作法是,直接把邊存成單向,就可以避免一條路被探兩次。但這樣最後兩筆測資會爛,真的只有最後兩筆:)
  • 後來想說,不然我給每條路一個 ID,儲存每條路是否被探勘過。只能說,作法可行,但我眼瞎,所以又多吃了一次 unsuccessful submission:)
  • Aaw:你幹嘛不直接硬做,等最後再把道路數量 ÷ 2 就好。對...耶,我當初為何沒想到 ==

Union-find Algorithm

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int
#define usll unsigned long long int
#define pii pair <int, int>
#define pipii pair <int, pii>
#define F first
#define S second
#define pq priority_queue
#define pb push_back
using namespace std;

vector <pipii> graph;
vector <int> endpos;

bool cmp (pipii a, pipii b){
  return (a.F < b.F);
}

int findend (int a){
  if (endpos[a] == -1) return a;
  endpos[a] = findend(endpos[a]);
  return endpos[a];
}

bool connect (int a, int b){
  int tmp_1 = findend(a), tmp_2 = findend(b);
  if (tmp_1 == tmp_2) return false;
  endpos[tmp_1] = tmp_2;
  return true;
}

void solve (){
  int N, M; cin >> N >> M;
  endpos.resize(N+17, -1);

  for (int i = 0; i < M; i++){
    int from, to, cost; cin >> from >> to >> cost;
    if (from == to) continue;
    graph.pb({cost, {from, to}});
  }

  sort(graph.begin(), graph.end(), cmp);
  usll ans = 0;
  int count = 0;
  int index = 0;
  // for (auto to:graph) cout << to.F << ' ' << to.S.F << to.S.S << '\n';

  while (count < N-1 && index < M){ // 成立的道路恰是 N-1 條
    if (connect(graph[index].S.F, graph[index].S.S)){
      ans += graph[index].F;
      count++;
    }
    index++;
  }

  cout << ans << '\n';

}

int main (){
  icisc
  solve();
}

通靈師謝謝

給定 \(N\) 個點與 \(M\) 條邊

每條邊連結兩個點

且其花費 \(K\),K 為 int 內的正整數

 

求在任兩點都是連通(允許間接)

的前提下,最小花費為何

 

\(1 \leq N \leq 4000\)

\(1 \leq M \leq \frac{N\;(N-1)}{2}\)

稍微記錄一下 最小生成樹 在幹嘛

  • 最大目的是,要從圖中找出一棵樹,aka 無環,邊數 = N-1
  • 先建立一個 array,定義 a[i] 代表點 i 最遠能連到的點
  • findend():不斷遞迴,直到無法再走,尋找該點的最遠到達處
  • connect():如果有兩點的最遠到達處相同,表示這肯定是一條環,無法連結。否則把其中一點的 最遠到達處的最遠到達處 設為 另一點的最遠到達處
  • 利用上面兩個函式來維護 array,掃過每一條路就好了
  • 我之前完全沒學過 MST,知道有這東西存在,但不知其內容
  • 通靈著就出來了,舒服,雖然我刷了一堆 WA
  • 最最最一開始以為是 dijkstra 的概念,很快就發現,它不適合拿來求這種題目,因為不見得會遍歷每點
  • "那我把每條路徑都掃一次,若存在自環就 continue,若起點與終點皆 visit 過也 continue",這是我後來持續很久的想法。在我提出一個很簡單的例子打臉自己前,我一直以為這是對的
  • "不然我來記錄大家最遠可以走到哪裡,嗎"接著再克服實作上的失誤後就 AC 了

two pointers

AtCoder Beginner Contest Round 294 pE

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int
#define usll unsigned long long int
#define pii pair <int, int>
#define pulul pair <usll, usll>
#define F first
#define S second
#define pq priority_queue
#define pb push_back
using namespace std;

vector <pulul> vec_1, vec_2;

void solve (){
  usll L, N, M; cin >> L >> N >> M;
  vec_1.resize(N);
  vec_2.resize(M);
  for (auto &to:vec_1) cin >> to.F >> to.S;
  for (auto &to:vec_2) cin >> to.F >> to.S;

  usll ans = 0;
  auto itr_1 = vec_1.begin(), itr_2 = vec_2.begin();
  usll a = (*itr_1).S, b = (*itr_2).S;

  while (itr_1 < vec_1.end() && itr_2 < vec_2.end()){
    if ((*itr_1).F == (*itr_2).F) ans += min(a, b);

    if (a < b){
      b -= a;
      itr_1++;
      a = (*itr_1).S;
    }else if (a > b){
      a -= b;
      itr_2++;
      b = (*itr_2).S;
    }else {
      itr_1++;
      a = (*itr_1).S;
      itr_2++;
      b = (*itr_2).S;
    }
  }

  cout << ans << '\n';
}

int main (){
  icisc
  solve();
}

欸我快哭出來ㄌ

你有一個 \(2×L\) 的表格

\(1 \leq L \leq 10^{12}\),皆由 int 內正整數組成

求有多少組 同行之兩數字相同 的狀況

 

由於數字過大,輸入的方式比較特別

輸入共兩組,大小分別為 \(N_1、N_2\)

\(1 \leq N_1、N_2 \leq 10^5\)

代表第一與第二列的數字

 

每筆輸入包含 \(x、y\) 兩數字

代表 後面 \(y\) 個格子的數字皆為 \(x\)

  • 我本來以為這題很 ISSC,輸入根本吃不完,還好他的輸入有做壓縮
  • \(1 \leq L \leq 10^{12}\) 的話,很明顯不能一行一行 \(O(L)\) 地掃。而它又指定輸入共有兩組,去對照範圍能大概抓到,正解複雜度必須壓在 \(O(N_1)\),就能很直覺想到雙指標了

Dynamic Programming

#include <bits/stdc++.h>
#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long int
#define usll unsigned long long int
#define pll pair <ll, ll>
#define plpll pair <ll, pair <ll, ll>>
#define F first
#define S second
#define pq priority_queue
#define pb push_back
using namespace std;

const int MOD = 1e8+7;
ll Mod (ll a){
  return (a+MOD) % MOD;
}

ll Plus (ll a, ll b){
  return Mod(Mod(a) + Mod(b));
}
ll Minus (ll a, ll b){
  return Mod(Mod(a) - Mod(b));
}

vector <vector <pll>> dp;
vector <vector <bool>> barrier;
vector <vector <bool>> gate;

void update (int i, int j){
  if (i == 1 && j == 1) return;
  dp[i][j].S = Minus(Plus(dp[i-1][j].S, dp[i][j-1].S), dp[i-1][j-1].S);
  /*
  if (gate[i-1][j]) dp[i][j].S = Plus(dp[i][j].S, dp[i-1][j].F);
  if (gate[i][j-1]) dp[i][j].S = Plus(dp[i][j].S, dp[i][j-1].F);
  if (gate[i-1][j-1]) dp[i][j].S = Minus(dp[i][j].S, dp[i-1][j-1].F);
  */
  if (!barrier[i][j]){
    dp[i][j].F = Plus(dp[i][j].F, Plus(dp[i-1][j].F, dp[i][j-1].F));
    dp[i][j].F = Plus(dp[i][j].F, dp[i][j].S);
  }
  if (gate[i][j]) dp[i][j].S = Plus(dp[i][j].S, dp[i][j].F);
  // cout << i << ' ' << j << ' ' << dp[i][j].F << ' ' << dp[i][j].S << '\n';
}

void solve (){
  int N, M; cin >> N >> M;
  dp.resize(N+17, vector <pll>(M+17, {0, 0}));
  barrier.resize(N+17, vector <bool>(M+17, false));
  gate.resize(N+17, vector <bool>(M+17, false));

  for (int i = 1; i <= N; i++){
    for (int j = 1; j <= M; j++){
      char tmp; cin >> tmp;
      if (tmp == 'x') barrier[i][j] = true;
      else if (tmp == 'p') gate[i][j] = true;
    }
  }

  dp[1][1].F = 1;
  for (int i = 1; i <= N; i++){
    for (int j = 1; j <= M; j++){
      update(i, j);
    }
  }

  cout << dp[N][M].F % MOD << '\n';
}

int main (){
  icisc
  solve();
}

我 de 到頭快破

存在一大小為 \(N × M\) 的棋盤

令左上為起點右下為終點

永遠都只能往右邊或下面走

 

另外棋盤上可能存在一些傳送門

可以利用傳送門,到達其右邊或下面

任何一個格子上

就算走到傳送門也可以選擇不進去

這樣算為兩種不同走法

 

求從起點到終點有多少種不同走法

  • 我一開始出發點是錯的,我拿組合的角度去想:倘若我右邊與上面共有 \(N\) 個傳送門,則會有 \(\Sigma_{i=1}^N(C^N_i)\) 種直接傳送到的方式,根據二項式定理,上式可以被整理成 \(2^N - 1\)
  • 上面那種做法從測資 9 會開始 WA,我最後去找 Aaw 求救
  • 經過他給的想法,其實問題沒如此複雜。我只要將其分成兩種狀況:步行過來直接傳送過來

步行過來:顯然直接上加左就好,無論他是用什麼鬼方法到那邊

     的,No body cares

直接傳送:對於每個在其左邊與上面的傳送門,若有 \(K\) 種方法   

     可以到達該格,表示它也有 \(K\) 種路徑來直接傳送到目的地

  • 承上,有了這個想法,拿前綴和維護就可以了。雖然還能再 BIT 優化,但這題還不用做到那種程度
  • 話說我後來發現我實作方法跟 Aaw 其實不太相同,我這邊是想到二維區塊前綴和,他那邊是直接拆成每列要加上去的值,不過兩個人的解論都是好的。但他那種做法似乎無法應付,N = 1 而 M = MAXN 的情況?

prefix sum algorithm

AtCoder Beginner Contest Round 295 pD

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef long long int usll;
typedef pair <int, int> pii;
typedef priority_queue<int, vector <int>, less<int>> pqis;
typedef priority_queue<int, vector <int>, greater<int>> pqig;
typedef vector <vector <int>> vecii;
typedef set<int>::iterator setitr;

#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define F first
#define S second
#define pb push_back

void solve (){
  string str; cin >> str;
  vector <int> num;
  for (int i = 0; i < str.size(); i++) num.pb(int(str[i] - '0'));
  int record = 0;
  usll ans = 0;
  vector <int> exist(1e5 + 17, 0);
  exist[0]++;
  for (auto to:num){
    int tmp = (1<<to);
    record ^= tmp;
    ans += exist[record];
    exist[record]++;
  }
  cout << ans << '\n';
}

int main (){
  icisc
  solve();
}

參考官方正解

給定一個由數字組成的字串

大小可達 \(5 × 10^5\)

 

如果一個字串中的數字經過重組後

能夠像 20230322 -> 20232023

這樣形成兩個,完全相同的數字

那我們就稱此數字是 快樂的

 

求整個字串中,你可以自由分割

惟在切割前不得變更順序

最多可形成多少個快樂的數字?

這真的很強,我想記錄一下他想法

  • 顯而易見地,要形成一個快樂的數字,所有數字的出現次數必須是偶數
  • 令 \(R_{i, j}\) 代表從頭開始考慮到第 \(i\) 個字元,數字 \(j\) 的出現次數 mod 2 後的結果
  • 當有任何一組 \(R_{i, j}\) 的值一模一樣時,表示 \(S_{i+1} \)到 \(S_{j}\) 是一組解(因為要讓它變回原本的樣子,肯定代表裡面所有數字的出現次數都是偶數)
  • 我們可以紀錄有多少組相同的 \(R_{i, j}\),最後 \(C^K_2\) 即為所求
  • 或像他這樣,直接加上之前出現過的次數也行得通
  • 然後他 Xor 用得很聰明,真的超值得學習

這真的很強,我想記錄一下他想法

  • 顯而易見地,要形成一個快樂的數字,所有數字的出現次數必須是偶數
  • 令 \(R_{i, j}\) 代表從頭開始考慮到第 \(i\) 個字元,數字 \(j\) 的出現次數 mod 2 後的結果
  • 當有任何一組 \(R_{i, j}\) 的值一模一樣時,表示 \(S_{i+1} \)到 \(S_{j}\) 是一組解(因為要讓它變回原本的樣子,肯定代表裡面所有數字的出現次數都是偶數)
  • 我們可以紀錄有多少組相同的 \(R_{i, j}\),最後 \(C^K_2\) 即為所求
  • 或像他這樣,直接加上之前出現過的次數也行得通
  • 然後他 Xor 用得很聰明,真的超值得學習

Knapsack

AtCoder Beginner Contest Round 317 pD

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef long long int usll;
typedef pair <int, int> pii;
typedef pair <ll, ll> pll;
typedef priority_queue<int, vector <int>, less<int>> pqis;
typedef priority_queue<int, vector <int>, greater<int>> pqig;
typedef vector <vector <int>> vecii;
typedef set<int>::iterator setitr;

#define icisc ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define F first
#define S second
#define pb push_back

vector <pll> vec; // seats, need
vector <ll> dp; // dp[i] = how many extra voter required to reach exactally i seats
ll INF = 1e17;

void solve (){
  ll N; cin >> N;
  vec.resize(N+17, {0, 0});

  ll seat_sum = 0;
  for (ll i = 1; i <= N; i++){
    ll a, b, c; cin >> a >> b >> c;
    ll need = max(1ll*0, (a+b)/2 + 1 - a);
    vec[i] = {c, need};
    seat_sum += c;
  }

  dp.resize(seat_sum + 17, INF);
  dp[0] = 0;

  for (ll i = 1; i <= N; i++){
    for (ll j = seat_sum; j >= vec[i].F; j--){
      // printf("i = %I64d, j = %I64d, vec[i].F = %I64d, vec[i].S = %I64d\n", i , j, vec[i].F, vec[i].S);
      dp[j] = min(dp[j], dp[j - vec[i].F] + vec[i].S);
      // printf("dp[%I64d] = %I64d\n", j, dp[j]);
    }
  }
  // cout << "Not crushed\n";
  ll ans = INF;
  for (ll i = seat_sum/2 + 1; i <= seat_sum; i++) ans = min(ans, dp[i]);
  cout << ans << '\n';
}

int main (){
  icisc
  solve();
}

我討厭讀學測

反正就是類似美國的選舉人團制度

這邊有 \(N\) 個選區

第 \(i\) 個選區有 \(Z\) 張選票

第 \(i\) 個選區中有 \(X\) 位投給 A 候選人

另外有 \(Y\) 位投給 B 候選人

在一個選區中

得票較高者可以拿走該選區的全部選票

 

最後獲得最多選票者將成為總統

求最少要有幾個原本投 B 的人轉投 A

才能讓 A 如願以償成為總統?

我是笨,我不會寫背包問題

結案。

Made with Slides.com