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

邀請連結


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) 如下:
- 比較 100 分數量
- 相同比較最後得分時間
北市賽
- 北市賽題目不會要求很多知識,題目大致偏簡單
- 想到解法
- 實作/debug 能力
- 錯了不要去看錯的測資,練習自己 debug
- 比賽策略
- 在時間內拿到最大的分數
北市賽
- 以 111 題目為例
- A
- 30分 二的冪次暴力枚舉
- 25分 簡單貪心
- 100分 flow(全場沒人會做)
- B
- 18分 暴力
- 29 冷靜的觀察
- 100分 冷靜的觀察+資料結構
-
- 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初選
- 剩下請自行參考 wiwihorz的簡報

校內賽題解
校內賽
- 題目難度自認為:
- 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