註:不是我覺得ez…
另外分享一個我在寫這些題目查資料的時候覺得很有用的筆記=> C++競程筆記
打算暑假兩天至少一題,然後因為題目蠻雜的所以哪天太閒會記得分類w

數字龍捲風

思路

總之就是先找中間,因為陣列輸入從0開始=>中間index=N*N/2
case D=0(即往左) => index-=1;
case D=1(即往上) => index-=N;
case D=2(即往右) => index+=1;
case D=3(即往下) => index-=N;
跟一下龍捲風跑到的位置就可以知道移動幾個數字是有規律的:1、1、2、2、3、3、…、N-1、N-1、N-1
也可以知道總共讀的次數是2N-1,轉向的次數是2N-2
至於為什麼最後三個都是N-1,因為第三個N-1本來應該是N,但移到第N個數字會發現沒數字可以讀了

solve.cpp
#include<iostream>
#include<string>
using namespace std;

int main(){
    int N;cin>>N;
    int Direction;cin>>Direction;
    int num[2401] = {0};
    int j;
    for(int i=0; i<N*N;i++){
        cin>>num[i];
    }
    string str = "";
    int index=N*N/2;
    str+=to_string(num[index]);
    for (int i=0; i<(2*N-2); i++){
        j=((i/2)+1);
        switch(Direction){
            case 0:
                while(j--){
                    str+=to_string(num[index-1]);
                    index-=1;
                }
                Direction+=1;
                break;
            case 1:
                while(j--){
                    str+=to_string(num[index-N]);
                    index-=N;
                }
                Direction+=1;
                break;
            case 2:
                while(j--){
                    str+=to_string(num[index+1]);
                    index+=1;
                }
                Direction+=1;
                break;
            case 3:
                while(j--){
                    str+=to_string(num[index+N]);
                    index+=N;
                }
                Direction=0;
                break;
        }
    }
    j=(N-1);
    switch(Direction){
        case 0:
            while(j--){
                str+=to_string(num[index-1]);
                index-=1;
            }
            break;
        case 1:
            while(j--){
                str+=to_string(num[index-N]);
                index-=N;
            }
            break;
        case 2:
            while(j--){
                str+=to_string(num[index+1]);
                index+=1;
            }
            break;
        case 3:
            while(j--){
                str+=to_string(num[index+N]);
                index+=N;
            }
            break;
    }
    cout<<str<<"\n";
    return 0;
}

線段覆蓋長度

思路

開陣列暴力掃(X)
我的想法是讀完各線段的起始跟結束,最後去結合可以連一起的線段
不過資結超爛的我用陣列硬幹失敗了,上網找才想到pair

solve.cpp
#include<bits/stdc++.h>
using namespace std;

int main(){
    int N;cin>>N;
    pair<int, int> line[N];
    int count=0;
    for(int i=0; i<N; i++){
        cin>>line[i].first>>line[i].second;
    }
    sort(line, line+N);
    for(int i=1; i<N; i++){
        if(line[i].first<=line[0].second&&line[i].second>line[0].second){
            line[0].second = line[i].second;
        }
        else if(line[i].first<=line[0].second){
            continue;
        }
        else{
            count+=line[0].second-line[0].first;
            line[0].first = line[i].first;
            line[0].second = line[i].second;
        }
    }
    count+=line[0].second-line[0].first;
    cout<<count<<"\n";
}

其他方法

我的方法是合併線段算長度,指導老師有提供一個方法是:開陣列,在L的地方+1代表該長度索引新增的線段;R+1的地方-1代表該線段結束,這樣就不用再去判斷哪些線段可以合併哪些不行,總之就是差分,在遇到同個時間點人有多少的題目也不會因為碰到炸裂測資TLE,蠻妙的

連鎖反應

思路

花了一整個晚上研究 BFS 實作xD
BFS + 二分可以解決這題

solve.cpp
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int M, N, q;
int si, sj;
int nx, ny;
int R=0;
int xm[4] = {0, 1, 0, -1};
int ym[4] = {1, 0, -1, 0};
int cnt = 0;
vector<vector<int>> board(600, vector<int>(600, 0));
struct boom{
    int x, y, r;
};
bool bfs(int r){
    queue<boom> qu;
    qu.push({si, sj, r});
    vector<vector<int>> ans(600, vector<int>(600, -1));
    ans[si][sj] = r;
    cnt = 0;
    cnt+=1;
    while(!qu.empty()){
        auto[x, y, r] = qu.front();
        qu.pop();
        if(r<=0) continue;
        if(r<ans[x][y]){
            continue;
        }
        for(int i=0; i<4; i++){
            nx = x + xm[i];
            ny = y + ym[i];
            int nr = r-1;
            if(nx>=1&&nx<=M&&ny>=1&&ny<=N&&board[nx][ny]!=-1){
                if(board[nx][ny]>0){
                    if(board[nx][ny]>nr){
                        nr = board[nx][ny];
                    }
                }
                if(nr > ans[nx][ny]){
                    if(ans[nx][ny] == -1){
                        cnt++;
                    }
                    qu.push({nx, ny, nr});
                    ans[nx][ny] = nr;
                }
            }
        }
        if(cnt>=q) return true;
    }
    return false;
}
int main(){
    cin>>M>>N>>q;
    for(int i=1; i<=M; i++){
        for(int j=1; j<=N; j++){
            cin>>board[i][j];
            if(board[i][j]==-2){
                si=i;
                sj=j;
            }
        }
    }
    int left =0, right = N*M;
    while(left<right){
        int mid = (left+right)/2;
        if(bfs(mid)){
            right = mid;
        }
        else{
            left = mid+1;
        }
    }
    cout<<left<<endl;
    return 0;
}

切割費用

思路

用 map 存切割的位置,開 vector 存初始線段長度,再去計算切割費用
但 TLE 了:)
先來看一下我本來想的核心程式

    for (int i=1; i<=n; i++){
        for (int j=0; j<line.size(); j++){
            if (mp[i] < line[j]){
                cost += line[j] - line[j-1];
                line.insert(line.begin()+j, mp[i]);
                break;
            }
        }
    }

那麼問題來了:要怎麼把優化?後來發現是 vector 的 insert 在搞,所以換個資料結構
這邊我看大家解答都寫 set 所以我也用 set,如果有重複元素的可以用 multiset
我以前都跳過set不看orz…

solve.cpp
#include<bits/stdc++.h>
using namespace std;

int main(){
    int n;cin>>n;
    int L;cin>>L;
    long long cost = 0;
    int t1, t2;
    set<int> line = {0, L};
    map<int, int> mp;
    for (int i=0; i<n; i++){
        cin>>t1>>t2;
        mp[t2] = t1;
    }
    for (int i=1; i<=n; i++){
        line.insert(mp[i]);
        auto it = line.find(mp[i]);
        cost += *next(it) - *prev(it);
    }
    cout << cost << endl;
    return 0;
}

定時K彈

思路

開 vector,爆一個就 erase M的部分看成傳遞M-1次就炸 但我被TLE了,模擬又被TLE笑死

tle.cpp
#include<bits/stdc++.h>
using namespace std;

int main(){
    int N, M, K;cin>>N>>M>>K;
    int n = 0;
    vector<int> p(N);
    for (int i=1; i<=N; i++){
        p[i-1] = i;
    }
    while(K){
        n = (n+M-1)%p.size();
        p.erase(p.begin()+n);
        K-=1;
    }
    n = n%p.size();
    cout<<p[n]<<"\n";
    return 0;
}

後來認真看了一下發現需要逆推
正著推可以知道第一個爆炸後的幸運者能表示成 (member[0]+M)%N
在爆炸結束後會剩下 N-K 人,於是我們需要 +1 去回推出member[0]原本在哪,這題觀念大致是這樣
btw這題是約瑟夫環

solve.cpp
#include<bits/stdc++.h>
using namespace std;

int main(){
    int N, M, K;cin>>N>>M>>K;
    int l = 0;
    for (int i= N-K+1; i <= N; i++){
        l = (l+M)%i;
    }
    cout<<l+1<<"\n";
    return 0;
}

攤位危機

這是我們校內資訊學科能競的題目,總之就是給起始時間跟終止時間,讓你找出最多人的時候有幾人

思路

其實我在比賽當下頭腦挺混亂的,我那個時候連 vector 都可以忘記怎麼宣告笑死
我一開始的想法是陣列開好開滿,然後 RE 了只拿到子測資遺憾離場
後來進選手群寫了上面那些題目所以有想到差分
總之就是結合 map 讓它自己從小到大開始跑差分

solve.cpp
#include<bits/stdc++.h>
using namespace std;

int main(){
  int n;cin>>n;
  int s, f;
  int p=0;
  int cur = 0;
  map<int, int> mp;
  for(int i=0; i<n; i++){
    cin>>s>>f;
    mp[s]++;
    mp[f+1]--;
  }
  for(const auto& m:mp){
    cur+=m.second;
    if(p<cur){
      p = cur;
    }
  }
  cout<<p<<endl;
  return 0;
}

觀光旅遊

思路

一看前綴和,但我一開始沒想到把 map 跟 pair 搞在一起 總之是經典的變形題

solve.cpp
#include<bits/stdc++.h>
using namespace std;

int main(){
    int n, m;cin>>n>>m;
    long long sum=0;
    int cur=0;
    int t;
    int s, f;
    map<long long, pair<long long, long long>> mp;
    for(int i=0; i<n; i++){
        cin>>t;
        mp[t].first++;
    }
    for(int i=0; i<m; i++){
        cin>>s>>f;
        mp[s].second++;
        mp[f+1].second--;
    }
    for(auto & p : mp){
        cur+=p.second.second;
        if(p.second.first!=0){
            sum+=p.second.first*cur;
        }
    }
    cout<<sum<<endl;
}