課程內容

上課內容

  • 因為北市賽不太需要艱深的知識點
  • 內容範圍會和暑培類似
  • 會有幾場模擬賽給大家練習
  • 培訓有事不來也沒關係,不會影響代表資格

邀請連結

messenger

discord

北市賽

北市賽

  • 比賽時間: 11/5 9:00~12:00 在師大分部
  • 三小時大約五~六題
  • 題目不會按照難度排序
  • 每題有部分分
  • 因為是師大出題,可能有賽中 rejudge,測資爛、測資太差的情況。

北市賽

  • 一等獎 5 個(感謝去年建中電神幫台北市拿了兩個全國一等)
  • 二等獎 5 個
  • 一二等可以晉級全國賽 (段考免考 get)
  • 三等獎 10 個
  • 佳作 30 個

北市賽

  • 競賽環境:
  • OS: Ubuntu 20.04.6
  • Language: C11 and C++14
  • IDE:
    • Code::Blocks 20.03(容易當)
    • Visual Studio Code 1.78
  • Compiler:gcc/g++ 9.4.0
  • Editor:vim 8.1, gedit 3.36.2
  • CMS: 1.5Dev

北市賽

  • 去年記分板
  • 去年是一等 4 個 ,  二等 5 個
  • 每條線都很接近(甚至同分)

北市賽

同分時的比序條件(tie breaker) 如下: 

  1. 比較 100 分數量
  2. 相同比較最後得分時間

北市賽

  • 北市賽題目不會要求很多知識,題目大致偏簡單
  • 想到解法
  • 實作/debug 能力
    • 錯了不要去看錯的測資,練習自己 debug
  • 比賽策略
    • 在時間內拿到最大的分數

北市賽

  • 以 111 題目為例
  • A
    • 30分 二的冪次暴力枚舉
    • 25分 簡單貪心
    • 100分 flow(全場沒人會做)
  • B
    • 18分 暴力
    • 29 冷靜的觀察
    • 100分 冷靜的觀察+資料結構

    C
    • 11分 超水觀察
    • 19分 貪心
    • 28分 2的冪次暴力+樹
    • 100分 樹上背包

北市賽

  • 以 111 題目為例
  • D
    • 45分 暴力
    • 100分 字串 Hash/KMP+ stack
    • 100分 看錯題目暴力AC
  • E
    • 31分 暴力模擬
    • 100分 全場沒人會(現在我還是不會)
  • F
    • 11分 暴力 dp
    • 33分 貪心(單調對列)+dp
    • 100分 在一點思考+實作

北市賽

  • 以 111 題目為例
  • 如果只會基本的暴力、貪心
  • 55+18+58+45+31+11=218
  • 耐心觀察一下的話: B=>47
  • 總分 => 247 (Rnk23)
  • 會實作 linked_list B=>100
  • 總分 =>300  (Rnk15) 
  • 三等獎 🎉
  • 貪心+會單調隊列 F=>44
  • 總分 => 333 (Rnk11)
  • 會樹上背包 (Rnk8)
  • D 看錯題目暴力不小心過了 (Rnk7)

如何 debug

 

debug

debug

void debug(){
	cout<<"\n";
}
template<class T,class ...U>
void debug(T a,U ...b){
	cout<<a<<" ",debug(b...);
}

int main(){
  debug("hello","world",5)
}
  • 在選訓營學長教我的
  • 用法會和 python print 類似
  • 分隔輸出會等價多空格
  • 下方code 會輸出 hello world 5

assert

void debug(){
	cout<<"\n";
}
template<class T,class ...U>
void debug(T a,U ...b){
	cout<<a<<" ",debug(b...);
}

int main(){
  debug("hello","world",5)
}
  • 確認題目錯誤或是一種 debug手段
  • 當 assert() 內的是 False 會回報 RE

用指令編譯 code

  • 可以用 vim/文字編輯器 寫好 code然後用指令編譯

比賽策略

 

模板

  • 把一些東西化簡,提升編寫 code 的速度
  • 包含 debug 的程式碼
  • 會背好開賽先打好,當作每題的初始 code

模板

#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx,popcnt,sse4,abm")
#include<bits/stdc++.h>
#define int long long
#define quick ios::sync_with_stdio(0);cin.tie(0);
#define rep(x,a,b) for(int x=a;x<=b;x++)
#define repd(x,a,b) for(int x=a;x>=b;x--)
#define lowbit(x) (x&-x)
#define sz(x) (int)(x.size())
#define F first
#define S second
#define all(x) x.begin(),x.end()
#define mp make_pair
#define eb emplace_back
using namespace std;
typedef pair<int,int> pii;
void debug(){
    cout<<"\n";
}
template <class T,class ... U >
void debug(T a, U ... b){
    cout<<a<<" ",debug(b...);
}
const int N=1e6+7;
const int INF=1e18;
signed main(){
	quick
}

喇分

  • OI 制特別的是每題會切子題(部分分)
  • 可以透過子題驗證方法的正確性
  • 最好每題都有分數

時間管理

時間管理

  • 我的話是先打完模板
  • 把每題看過,如果超水就直接寫
  • 之後每題都寫一下預計可拿的分數 / 實作時間
  • 按照效益最大由大到小寫

線上 oj

  • 有很不錯的裸題、經典題可以練習
  • 之前準備北市賽/TOI 初選是刷這個
  • 有很多的線上練習賽,可以練手感
  • 不過是 icpc 制
  • 有OI制的練習賽
  • 培訓前兩次模競也是裡面的
  • 蠻推薦 virtual 準備北市賽 / TOI初選

校內賽題解

校內賽

  • 題目難度自認為: 
  • A<B<D<C<E<F

A. 黑暗火鍋

  • 要求一組 \((l,r)\)使得 \(r-l\) 盡量小
  • 且裡面有包含數列最大值,最小值

A. 黑暗火鍋

  • 很明顯兩端會剛好是 最大值 , 最小值
  • 可以先掃一遍陣列找到最大值、最小值之值
  • 之後依序往右掃,並記錄當前左邊最接近的最大值,最小值

A. 黑暗火鍋

void solve(int n){
    int mx=-INF,mn=INF;
    for(int i=1;i<=n;i++) mx=max(mx,a[i]),mn=min(mn,a[i]);
    int ansl=-INF,ansr=INF;
    int lmn=-INF,lmx=-INF;
    for(int i=1;i<=n;i++){
    	if(a[i]==mx&&ansr-ansl>i-lmn){
			ansl=lmn;
            ansr=i;
            lmx=i;
        }
        else if(a[i]==mn&&ansr-ansl>i-lmx){
			ansl=lmx;
            ansr=i;
            lmn=i;
        }
    }
}

B. 回音 (Echo)

給字串 \(S,T\) 要判斷可不可以把字串 \(S\) 切成 \(|T|\) 塊,且第 \(i\) 塊內的字元都相同且 \(=T_i\)

B. 回音 (Echo)

  • 把相同字元縮起來
  • 每種字元 \(cnt(S_c)\geq cnt(T_c)\)
  • eccchhoooo => e1c3h2o4
  • eccho=>e1c2h1o1

B. 回音 (Echo)

#include<bits/stdc++.h>
#define int long long
#define quick ios::sync_with_stdio(0);cin.tie(0);
#define rep(x,a,b) for(int x=a;x<=b;x++)
#define repd(x,a,b) for(int x=a;x>=b;x--)
#define lowbit(x) (x&-x)
#define sz(x) (int)(x.size())
#define F first
#define S second
#define all(x) x.begin(),x.end()
#define mp make_pair
#define eb emplace_back
using namespace std;
typedef pair<int,int> pii;
void debug(){
    cout<<"\n";
}
const int N=2e6+7;
const int INF=1e18;
int cnt[26];
vector<pair<char,int> > get(string s){
    vector<pair<char,int> > v;
    rep(i,0,sz(s)-1){
        int l=1;
        while(i+l<sz(s)&&s[i+l]==s[i]) l++;
        v.eb(mp(s[i],l));
        i=i+l-1;
    }
    return v;
}
signed main(){
	quick
    int n,m;
    cin>>n>>m;
    string s,t;
    cin>>s>>t;
    vector<pair<char,int>> v1,v2;
    v1=get(s);v2=get(t);
    if(sz(v1)!=sz(v2)) {
        cout<<"No\n";
        return 0;
    }
    rep(i,0,sz(v1)-1){
        if(v1[i].F!=v2[i].F||v1[i].S<v2[i].S){
            cout<<"No\n";
            return 0;
        }
    }
    cout<<"Yes\n";
}
收起
Echo.cpp
2 KB
#include<bits/stdc++.h>
#define int long long
#define quick ios::sync_with_stdio(0);cin.tie(0);
#define rep(x,a,b) for(int x=a;x<=b;x++)
#define repd(x,a,b) for(int x=a;x>=b;x--)
#define lowbit(x) (x&-x)
#define sz(x) (int)(x.size())
#define F first
#define S second
#define all(x) x.begin(),x.end()
#define mp make_pair
#define eb emplace_back
using namespace std;
typedef pair<int,int> pii;
void debug(){
    cout<<"\n";
}
template<class T,class ...U> 
void debug(T a,U ...b){
    cout<<a<<" ",debug(b...);
}
const int N=2e6+7;
const int INF=1e18;
int dis[N];
int dis2[N];
vector<pii> v[N];
vector<pii> v2[N];
tuple<int,int,int> e[N];
signed main(){
	quick
    int n,m;
    cin>>n>>m;
    rep(i,1,m) {
        int a,b,w;
        cin>>a>>b>>w;
        e[i]=make_tuple(a,b,w);
        v[a].eb(b,w);
        v2[b].eb(a,w);
    }
    fill(dis+2,dis+n+1,INF);
    priority_queue<pii,vector<pii>,greater<pii> > pq;
    pq.push(mp(0,1));
    while(sz(pq)){
        pii now=pq.top();
        pq.pop();
        if(dis[now.S]<now.F) continue;
        for(auto [i,w]:v[now.S]){
            if(dis[i]>dis[now.S]+w){
                dis[i]=dis[now.S]+w;
                pq.push(mp(dis[i],i));
            }
        }
    }
    fill(dis2+1,dis2+n,INF);
    pq.push(mp(0,n));
    while(sz(pq)){
        pii now=pq.top();
        pq.pop();
        if(dis2[now.S]<now.F) continue;
        for(auto [i,w]:v2[now.S]){
            if(dis2[i]>dis2[now.S]+w){
                dis2[i]=dis2[now.S]+w;
                pq.push(mp(dis2[i],i));
            }
        }
    }
    rep(i,1,m){
        auto [a,b,w]=e[i];
        if(dis[a]+w+dis2[b]==dis[n]){
            cout<<'1';
        }
        else cout<<'0';
    }cout<<"\n";
}

C. 雨傘架 (Umbrella)

  • 總共有 \(N\) 個空位和 \(M\) 次人群
  • 第 \(i\) 次會是從左邊/右邊依序填入 \(x_i\) 空位
  • \(x_i<0\) 代表離開
  • 每次填入時順便問該次填入的最右邊 / 最左邊編號
  • \(1\leq N\leq 10^9\)
  • \(1\leq M \leq 10^6\)

C. 雨傘架 (Umbrella)

  • 因為實際陣列太大無法存,但人數是有限的
  • 可以用 deque 維護人狀態
  • 存 pair(x,k) 代表從位置 \(x\) 開始有連續 \(k\) 個

 

C. 雨傘架 (Umbrella)

  • 以左方為例
  • 當要新增 \(k\) 個人時,從頭開始看有沒有空位需要補齊
  • 如果位置補完還剩就繼續補
  • 最後再重新更新
int l=0;
while(sz(dq)&&dq.front().F-l-1<k){
     k-=(dq.front().F-l-1);
     l=dq.front().F+dq.front().S-1;
     dq.pop_front();
}
cout<<l+k<<"\n";
dq.push_front(mp(1,l+k));

C. 雨傘架 (Umbrella)

  • 以左方為例
  • 當要刪除 \(k\) 個人時,從頭開始依序刪除人,並記錄當前刪除中的最右邊元素
k=-k;
int l=0;
while(sz(dq)&&dq.front().S<=k) {
   l=dq.front().F+dq.front().S-1;
   k-=dq.front().S;
   dq.pop_front();
}
if(k) dq[0].F+=k,dq[0].S-=k,cout<<dq[0].F-1<<"\n";
else cout<<l<<"\n";

C. 雨傘架 (Umbrella)

  • 另一邊也類似,但為了維持一致性
  • 所以另一邊也會是 \((x,k) \rightarrow x,x+1,...,x+k\)
  • 實作上會比較麻煩

C. 雨傘架 (Umbrella)

#include<bits/stdc++.h>
#define int long long
#define quick ios::sync_with_stdio(0);cin.tie(0);
#define rep(x,a,b) for(int x=a;x<=b;x++)
#define repd(x,a,b) for(int x=a;x>=b;x--)
#define lowbit(x) (x&-x)
#define sz(x) (int)(x.size())
#define F first
#define S second
#define all(x) x.begin(),x.end()
#define mp make_pair
#define eb emplace_back
using namespace std;
typedef pair<int,int> pii;
void debug(){
    cout<<"\n";
}
template<class T,class ...U>
void debug(T a,U ...b){
    cout<<a<<" ",debug(b...);
}
const int N=2e6+7;
const int INF=1e18;
signed main(){
	quick
    deque<pii> dq;
    int n,m;
    cin>>n>>m;
    int ls,rs;
    ls=rs=0;
    rep(i,1,m){
        char ci;
        int k;
        cin>>ci>>k;
        if(ci=='l'){
            if(k>=0){
                int l=0;
                while(sz(dq)&&dq.front().F-l-1<k){
                    k-=(dq.front().F-l-1);
                    l=dq.front().F+dq.front().S-1;
                    dq.pop_front();
                }
                cout<<l+k<<"\n";
                dq.push_front(mp(1,l+k));
            }
            else{
                k=-k;
                int l=0;
                while(sz(dq)&&dq.front().S<=k) {
                    l=dq.front().F+dq.front().S-1;
                    k-=dq.front().S;
                    dq.pop_front();
                }
                if(k) dq[0].F+=k,dq[0].S-=k,cout<<dq[0].F-1<<"\n";
                else cout<<l<<"\n";
            }
        }
        else{
            if(k>=0){
                int Lft;
                int l=n+1;
                while(sz(dq)&&l-(dq.back().F+dq.back().S-1)-1<k){
                    k-=l-(dq.back().F+dq.back().S-1)-1;
                    l=dq.back().F;
                    dq.pop_back();
                }
                cout<<l-k<<"\n";
                dq.push_back(mp(l-k,n-(l-k)+1));
            }
            else{
                k=-k;
                int l=0;
                while(sz(dq)&&dq.back().S<=k) {
                    l=dq.back().F;
                    k-=dq.back().S;
                    dq.pop_back();
                }
                if(k) dq[sz(dq)-1].S-=k,cout<<dq.back().F+dq.back().S<<"\n";
                else cout<<l<<"\n";
            }
        }
    }    
}

D. 咖哩採買 (Shopping)

  • 給定 \(n\) 點 \(m\) 邊有向帶權圖
  • 輸出每條邊是不是在最短路上的邊
  • \(2 \leq n \leq 5\cdot 10^5\)
  • \(1 \leq m \leq 5\cdot 10^5\)

D. 咖哩採買 (Shopping)

  • 一條邊 \((a,b)\) 出現在最短路上,
  • 代表 \(dis(1\sim a)+w(a,b)+dis(b\sim n)=dis(1\sim n))\)
  • 只要知道 \(mindis(1 \sim) , mindis(n \sim 1)\) 即可判斷
  • 從 \(1,n\) 各做一次 Dijkstra 即可

D. 咖哩採買 (Shopping)

#include<bits/stdc++.h>
#define int long long
#define quick ios::sync_with_stdio(0);cin.tie(0);
#define rep(x,a,b) for(int x=a;x<=b;x++)
#define repd(x,a,b) for(int x=a;x>=b;x--)
#define lowbit(x) (x&-x)
#define sz(x) (int)(x.size())
#define F first
#define S second
#define all(x) x.begin(),x.end()
#define mp make_pair
#define eb emplace_back
using namespace std;
typedef pair<int,int> pii;
void debug(){
    cout<<"\n";
}
template<class T,class ...U> 
void debug(T a,U ...b){
    cout<<a<<" ",debug(b...);
}
const int N=2e6+7;
const int INF=1e18;
int dis[N];
int dis2[N];
vector<pii> v[N];
vector<pii> v2[N];
tuple<int,int,int> e[N];
signed main(){
	quick
    int n,m;
    cin>>n>>m;
    rep(i,1,m) {
        int a,b,w;
        cin>>a>>b>>w;
        e[i]=make_tuple(a,b,w);
        v[a].eb(b,w);
        v2[b].eb(a,w);
    }
    fill(dis+2,dis+n+1,INF);
    priority_queue<pii,vector<pii>,greater<pii> > pq;
    pq.push(mp(0,1));
    while(sz(pq)){
        pii now=pq.top();
        pq.pop();
        if(dis[now.S]<now.F) continue;
        for(auto [i,w]:v[now.S]){
            if(dis[i]>dis[now.S]+w){
                dis[i]=dis[now.S]+w;
                pq.push(mp(dis[i],i));
            }
        }
    }
    fill(dis2+1,dis2+n,INF);
    pq.push(mp(0,n));
    while(sz(pq)){
        pii now=pq.top();
        pq.pop();
        if(dis2[now.S]<now.F) continue;
        for(auto [i,w]:v2[now.S]){
            if(dis2[i]>dis2[now.S]+w){
                dis2[i]=dis2[now.S]+w;
                pq.push(mp(dis2[i],i));
            }
        }
    }
    rep(i,1,m){
        auto [a,b,w]=e[i];
        if(dis[a]+w+dis2[b]==dis[n]){
            cout<<'1';
        }
        else cout<<'0';
    }cout<<"\n";
}

E. 爐心融解 (Meltdown)

  • 有一 \(N\times N\) 方格,和 \(M\) 個位置有其危險程度 \(c_{ij}\)
  • 每一直行可以選擇重上面或是下面遮住連續數量,這些位置的危險程度就不用被計算
  • 但遮住的相鄰位置都不能被遮住
  • 要讓剩下的危險總和最小

E. 爐心融解 (Meltdown)

  • 既然不知道怎麼表示狀態,就新增狀態吧
  • dp[i,j][0] 代表該位置同時是從上到下遮住
  • dp[i,j][1] 代表該位置同時是從下到上遮住
  • d2[j] 代表該行全遮
  • d3[j] 代表該行全不遮
  • 若前一列是上到下,下一列會是相反或是空...
  • 因為只能做 \(O(M)\) , 把這些點的位置離散化

E. 爐心融解 (Meltdown)

  • 先把行列互換,之後按照上面的定義
  • \(dp[i,j][0]=\min(dp[i-1,j'][1])_{j'>j+1} +cost(i,j+1\sim n)\)
  • \(dp[i,j][1]=\min(dp[i-1,j'][0])_{j'<j-1} +cost(i,1\sim j)\)
  • \(d2[j]=d3[j-1]\)
  • \(d3[j]=\min(d2[j-1],d3[j-1]) + cost(i,1\sim n)\)
  • 但要注意這裡是因為 i,i-1 相鄰,但如果離散化前不相鄰就不需考慮限制

E. 爐心融解 (Meltdown)

#include<bits/stdc++.h>
#define int long long
#define quick ios::sync_with_stdio(0);cin.tie(0);
#define rep(x,a,b) for(int x=a;x<=b;x++)
#define repd(x,a,b) for(int x=a;x>=b;x--)
#define lowbit(x) (x&-x)
#define sz(x) (int)(x.size())
#define F first
#define S second
#define all(x) x.begin(),x.end()
#define mp make_pair
#define eb emplace_back
using namespace std;
typedef pair<int,int> pii;
void debug(){
    cout<<"\n";
}
template<class T,class ...U>
void debug(T a,U ...b){
    cout<<a<<" ",debug(b...);
}
const int N=2e6+7;
const int INF=1e18;
int r[N],c[N],d[N];
int d2[N];
int d3[N];
vector<pii> v[N];
vector<vector<int> > dp[N];
signed main(){
	quick
    int n,m,k;
    cin>>n>>m>>k;
    vector<int> vx;
    rep(i,1,k){
        cin>>r[i]>>c[i]>>d[i];
        vx.eb(c[i]);
    }
    sort(all(vx));
    vx.erase(unique(all(vx)),vx.end());
    rep(i,1,k){
        c[i]=lower_bound(all(vx),c[i])-vx.begin();
        v[c[i]].eb(mp(r[i],d[i]));
    }
    rep(i,0,sz(vx)-1){
		dp[i].assign(sz(v[i]),vector<int>(2));
        sort(all(v[i]));
        vector<int> pref(sz(v[i]));
        rep(j,0,sz(v[i])-1){
            pref[j]=v[i][j].S;
            if(j) pref[j]+=pref[j-1];
        }
		//debug(i);
		//for(pii p2:v[i]) cout<<p2.F<<" "<<p2.S<<"\n";cout<<"\n";
       int mn=INF;
       for(pii p:v[i]) mn=min(mn,p.S);
        int l=sz(v[i]);
        if(!i||vx[i]-vx[i-1]>1){
            int Sx=0;
			
            if(i){
                Sx=INF;
                rep(j,0,sz(v[i-1])-1){
                    Sx=min(Sx,min({dp[i-1][j][0],dp[i-1][j][1],d2[i-1],d3[i-1]}));
                }
            }
            d2[i]=Sx;
            d3[i]=Sx+pref.back();
            rep(j,0,sz(v[i])-1){
			 		dp[i][j][0]=pref[l-1]-pref[j]+Sx;
                dp[i][j][1]=(j>0?pref[j-1]:0LL)+Sx;
            }
            if(sz(v[i])==n){
                d2[i]+=mn;
			}
        }
        else{
		
            int l0=sz(v[i-1]);
            vector<int> idx(l0);
            vector<int> prefmn(l0);
            vector<int> suffmn(l0);
            rep(j,0,l0-1){
                idx[j]=v[i-1][j].F;
                prefmn[j]=dp[i-1][j][0];
                if(j) prefmn[j]=min(prefmn[j],prefmn[j-1]);
            }
            repd(j,l0-1,0){
                suffmn[j]=dp[i-1][j][1];
                if(j+1<l0) suffmn[j]=min(suffmn[j],suffmn[j+1]);
            }
			
            rep(j,0,sz(v[i])-1){
            		dp[i][j][0]=dp[i][j][1]=d3[i-1];
					int p=upper_bound(all(idx),v[i][j].F+1)-idx.begin();
	   		         if(p<l0) dp[i][j][0]=min(dp[i][j][0],suffmn[p]);
					dp[i][j][0]+=pref.back()-pref[j];
					p=lower_bound(all(idx),v[i][j].F-1)-idx.begin()-1;
					if(p>=0) dp[i][j][1]=min(dp[i][j][1],prefmn[p]);
					//if(i==4) debug("lower",prefmn[p]);
					dp[i][j][1]+=(j>0?pref[j-1]:0);;
            }
			d2[i]=d3[i-1]+((sz(v[i])==n)?mn:0);
			d3[i]=min(d3[i-1],d2[i-1])+pref.back();
			rep(j,0,l0-1) d3[i]=min(d3[i],min(dp[i-1][j][0],dp[i-1][j][1])+pref.back());
        }
		 //debug(dp[i][0][0],dp[i][0][1],d2[i],d3[i]);
		//if(i>=3) return 0;
    }
	int L=sz(vx);
	int ans=min(d2[L-1],d3[L-1]);
	rep(j,0,sz(v[L-1])-1){
		ans=min(ans,min(dp[L-1][j][0],dp[L-1][j][1]));
	}
	cout<<ans<<"\n";
}

/*
0 -> 上
1 -> 下
2 -> 上+下
3 -> 都不放
dp[i][j][0]=min(min(dp[i-1][j'][1],dp[i-1][j'][3]) ) +Cost(i,j+1~n)) for j'>j+1
dp[i][j][1]=min(min(dp[i-1][j'][0],dp[i-1][j'][3]))+Cost(i,1~j-1)) for j' < j-1
dp[i][j][2]=dp[i-1][j][3]
dp[i][j][0]=min(dp[i-1][j][0],dp[i-1][j][1],dp[i-1][j][2],dp[i-1][j][3]);
*/

F. 裏表次元 (Dimension)

  • 給定 \(n\) 點 \(m\) 關係
  • \(i\) 次關係會給你 \(p_i,k_i\) 代表強度 \(p_i\), 有 \(k_i\) 個人是同一群
  • 之後有 \(Q\) 次詢問,每次問 \(a,b\) 需要至少經過多少強度才能變成同一塊
  • \(2\leq N,Q \leq 5 \cdot 10^5\)
  • \(\sum_{k_i} \leq 2.5\cdot 10^5\)

F. 裏表次元 (Dimension)

  • 既然要問至少要用多少,所以可以掃描線
  • 從強度 \(1\) 開始依序合併
  • 那問題剩下要怎麼知道 \(a,b\) 在何時何再一起
  • 對 \((a_i,b_i)\) , 在 \(a_i,b_j\) 都加入標記 \(i\), 因此假如合併時兩個 \(i\) 遇到就結束
  • 合併時間複雜度會爆炸
  • \(=>啟發式合併\)

F. 裏表次元 (Dimension)

#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx,popcnt,sse4,abm")
#include<bits/stdc++.h>
//#define int long long
#define quick ios::sync_with_stdio(0);cin.tie(0);
#define rep(x,a,b) for(int x=a;x<=b;x++)
#define repd(x,a,b) for(int x=a;x>=b;x--)
#define lowbit(x) (x&-x)
#define sz(x) (int)(x.size())
#define F first
#define S second
#define all(x) x.begin(),x.end()
#define mp make_pair
#define eb emplace_back
using namespace std;
typedef pair<int,int> pii;
void debug(){
    cout<<"\n";
}
template<class T,class ...U>
void debug(T a,U ...b){
    cout<<a<<" ",debug(b...);
}
const int N=2e6+7;
vector<int> c[N];
int p[N];
vector<int> qry[N];
int ans[N];
int px[N];
int fp(int x){
  if(x!=px[x]) px[x]=fp(px[x]);
  return px[x];
}
int group;
int n,q;
void Union(int a,int b,int t){
    int a0=a,b0=b;
    a=fp(a);b=fp(b);
    if(a==b) {return ;}//debug(a0,b0,t,"T same");return ;}
    if(sz(qry[a])<sz(qry[b])) swap(a,b);
    px[b]=a;
    group--;
    //debug("Union",a0,b0);
    for(int i:qry[b]){
        if(ans[i]) continue;
        if(fp(i+n)==fp(i+n+q)){
          ans[i]=t;
        }
        else qry[a].eb(i);
    }
    qry[b].clear();
}
signed main(){
	quick
    int m;
    cin>>n>>m;
    assert(n<N&&m<N);
    group=n;
    rep(i,1,n) px[i]=i;
    vector<pii> v;
    rep(i,1,m){
        int k;
        cin>>p[i]>>k;
        v.eb(mp(p[i],i));
        c[i].resize(k);
        rep(j,0,k-1){
            cin>>c[i][j];
        }
    }
    cin>>q;
    rep(i,1,q){
        int a,b;
        cin>>a>>b;
        qry[a].eb(i);
        px[i+n]=a;
        px[i+n+q]=b;
        qry[b].eb(i);
    }
    sort(all(v));
    for(auto [t,i]:v){
        rep(j,1,sz(c[i])-1){
            Union(c[i][j],c[i][0],t);
            if(group==1) break;
        }
        if(group==1) break;
    }
    rep(i,1,q) cout<<ans[i]<<"\n";
}

111 北市賽 pB

北市賽

北市賽

  • 題目大意就是當你選擇相鄰的兩張牌時,代價會是比較大那張的值,並同時把小的丟掉
  • 持續這個過程最終會剩下一張牌
  • 可以決定選牌的順序,目標讓總代價最小

子題 \(1\) (\(N<20\))

子題 \(1\) (\(N<20\))

既然不知道順序,就暴力 dfs

直接隨便選相鄰位置並繼續做...

應該會過吧

子題 \(2\) (\(N\leq5000\))

子題 \(2\) (\(N\leq5000\))

  • 既然要求代價最小值,看一下代價的計算方式
  • 那會選擇相鄰牌 \(a,b\) 並花費 \(max(a,b)\), 並刪除 \(min(a,b)\)
  • 也就是說刪除 \(x\) 的代價是當時左右比他大的較小的
  • 觀察刪除的時間,會發現時間越晚時剩下的東西一定比較大
  • 也就是代價會越來越大

子題 \(2\) (\(N\leq5000\))

  • 也就是代價會越來越大
  • 要怎麼最小化刪除代價 ?
  • 照著東西由小到大刪就可以
  • 每次選當時左右中比較小的那個做為代價
  • 並把 \(x\) 刪除
  • 不會 linked list 作法就 \(O(N)\) 刪除

滿分解

  • 用 linked list 來優化刪除吧
  • 想法是同時維護左右指針,代表左右第一個存在的
  • 那就可以做到刪除了

北市賽簡介

By yuhung94

北市賽簡介

  • 272