2020hdu多校第一場比賽及補題
第一場是朝鮮出題
1004Distinct Sub-palindromes
跟榜開的這題,一開始猜是26的n次方,直接交一發wa了, 後來和隊友想了半天,想到了abcabcabc,答案就是n<=3時是26的n次方, else ans = 26*25*24
1005Fibonacci Sum
每個斐波納契數的通項二項展開,再合併,是二項展開的套路。組合數要先預處理階乘和階乘逆元。
比賽時隊友過的這題。
但是賽後我怎麼寫都T,把隊友當時的AC程式碼交上去也T了,自閉了
1009 Leading Robots
隊友寫1005的時候我在想這題,想了挺久
n個機器人賽跑,每個機器人初速度為0,加速度為ai,初始位置在pi,問有多少個機器人當過第一 (第一指獨一無二的最前面的那個)
容易想到下面兩點
一:n*(n-1)/2的比較次數肯定是不行的,要想辦法減少比較次數
二:加速度越大的最終排名一定越靠前,加速度大的人不會被加速度小的人從後方超越
考慮3個人的情況,且這3個人編號越大,起點越靠後,加速度越大,
設2號追上1號的時間 t1 ,3號追上2號的時間 t2 ,和3號追上1號的時間 t3,
若t3 < t1,即3號比2號先超過1號,ans = 2; 若t2 > t1,即2號先超過1號,然後3號超過2號,ans = 3
這裡t1,t2,t3都用到了, 實際上只要用 t1和t2 或 t1和t3 就行了,
t1和t3是兩個人追上同一個人分別所用的時間,感覺很難有進一步的思路,下面思考t1和t2
若t1<t2,即2號追上1號之前 3號追上了2號,3號先追上了1號,當了第一名,之後2號也無法當第一名了, 因為3號加速度比2號大,2號的存在簡直像是可以被消除掉了(
若t1>t2,2號還是能當第一名的,但如果後面又來了個4號,在2號當第一之前超過了2號,2號的存在又可以被消除了,而且4號把2號消除之前,已經把3號消除了
於是可以想到一個單調棧做法,棧內儲存的是每個人當第一的時刻,且單調遞增, 每次加入一個加速度比棧內所有人都要大的新人,計算新人超越棧頂的時間,如果新人超越棧頂的時刻比棧頂人當第一的時刻小,就把棧頂人pop,棧頂人的存在就被消除了,如果新人超越棧頂的時刻比棧頂人當第一的時刻大,那麼目前棧頂人還是能當第一,新人當第一的時刻就是新人超越棧頂的時刻,計錄新人的這個時刻並把新人push入棧。如果新人在一開始的時候就是第一,那麼他當第一的時刻就是0,棧內元素個數就是當過答案。
至於多人同時保持當第一的情況,就特別處理。
#include<iostream> #include<algorithm> #include<cmath> #include<cstdio> using namespace std; const int MAXN = 5e4+7; const long long INF = 1e18+9; double eps = 1e-12; struct RBT{//機器人 long long p,a; double t = 0.0 ; }rbt[MAXN],st[MAXN]; bool cmp(RBT x,RBT y){ if(x.a!=y.a) return y.a>x.a; else return y.p>x.p; } int main() { int t,n,h; cin>>t; while(t--){ cin>>n; int l = 0,r =0; for(int i = 1;i<=n;i++){ scanf("%d%d",&rbt[i].p,&rbt[i].a); rbt[i].t = 0.0; //初始化,比賽的時候就是少了這個,wa了幾發 } sort(rbt+1,rbt+n+1,cmp); for(int i = 1;i<=n;i++){ if(r&&rbt[i].p==st[r].p&&rbt[i].a==st[r].a){//特別處理 rbt[i].t = st[r].t; } else while(r>l){ if(rbt[i].p>=st[r].p){ r--; continue; } long long da = rbt[i].a-st[r].a; long long dx = st[r].p-rbt[i].p; long long dvxdv = 2 * da * dx; double dv = sqrt((double)dvxdv); rbt[i].t = dv/da; if(rbt[i].t<=st[r].t+eps) { r--; } else break; } r++; st[r] = rbt[i]; } int ans = r; for(int i = 2;i <= r;i++){//把相同第一的同位同速人刪掉 if(st[i-1].a==st[i].a&&st[i-1].p==st[i].p){ while(st[i].a==st[i-1].a&&st[i].p==st[i-1].p){ ans--; i++; } ans--; } } cout<<ans<<endl; } return 0; }
1006 Finding a MEX
比賽時沒做出來
把度數大於350的節點稱為大節點,其他節點稱為小節點,每個大節點建一個對應線段樹。
求小節點的MEX就暴力求
求大節點的MEX就要用到線段樹,線段樹維護的是最小值,查詢MEX的時候,如果左半線段的最小值為0,就往左邊查詢,否則往右半段查詢。
每次修改值時,把該節點連線的所有大節點的線段樹進行修改,要預處理每個節點連線哪些大節點
#include<iostream> #include<algorithm> #include<cstdio> #include<vector> #include<time.h> using namespace std; struct NODE{ int l, r, mi; }tree[360][24000]; int val[360][6000];//第i個線段樹上某個值出現了幾次 int a[100007];//節點上的值 int d[100007];//節點的度數 int ant[100007];//節點對應第幾個線段樹 vector<int>graph[100007];//存圖 vector<int> gg[100007];//gg[i] bool vis[360];//小節點上的vis int tot; void build(int cnt,int pos,int l,int r){ tree[cnt][pos].l = l; tree[cnt][pos].r = r; if(l==r){ tree[cnt][pos].mi = min(val[cnt][tot],1); tot++; return; } int mid = l+r>>1; build(cnt,pos<<1,l,mid); build(cnt,pos<<1|1,mid+1,r); tree[cnt][pos].mi = min(tree[cnt][pos<<1].mi,tree[cnt][pos<<1|1].mi); } void modify(int cnt,int pos,int p,int v){ if(tree[cnt][pos].l==tree[cnt][pos].r){ tree[cnt][pos].mi = v; return; } int mid = tree[cnt][pos].l+tree[cnt][pos].r>>1; if(p<=mid) modify(cnt,pos<<1,p,v); else modify(cnt,pos<<1|1,p,v); tree[cnt][pos].mi = min(tree[cnt][pos<<1].mi,tree[cnt][pos<<1|1].mi); } int query(int cnt,int pos,int l,int r){ if(tree[cnt][pos].l==tree[cnt][pos].r){ return tree[cnt][pos].l; } int mid = tree[cnt][pos].l+tree[cnt][pos].r>>1; if(tree[cnt][pos<<1].mi==0) return query(cnt,pos<<1,l,mid); else return query(cnt,pos<<1|1,mid+1,r); } int main() { int t, n, m, q, u, v; cin>>t; while(t--){ cin>>n>>m; for(int i = 1;i<=n;i++){ scanf("%d",&a[i]); d[i] = 0; graph[i].clear(); gg[i].clear(); ant[i] = 0; } for(int i = 1;i<360;i++){ for(int j = 0;j<6000;j++){ val[i][j] = 0; } } for(int i = 1;i <= m;i++){ scanf("%d%d",&u,&v); graph[u].push_back(v); graph[v].push_back(u); d[u]++; d[v]++; } for(int i = 1;i<=n;i++){//存每個點連線的大節點 for(int j = 0;j<graph[i].size();j++){ int po = graph[i][j]; if(d[po]>350) gg[i].push_back(po); } } int cnt = 0; for(int i = 1;i <= n;i++){//找出度數>350的節點建樹 if(d[i]>350){ cnt++; ant[i] = cnt; tot = 0; for(int j = 0;j < d[i];j++){ int po = graph[i][j]; if(a[po]>d[i]){ val[cnt][d[i]]++; } else val[cnt][a[po]]++; } build(cnt,1,0,d[i]); } } cin>>q; int op,x; for(int i = 1;i <= q;i++){ scanf("%d",&op); if(op==1){ scanf("%d%d",&u,&x); //printf(" %d\n",gg[u].size()); for(int j = 0;j<gg[u].size();j++){ int po = gg[u][j]; int lo = a[u]; if(lo>d[po]) lo = d[po]; val[ant[po]][lo]--; if(!val[ant[po]][lo]){ modify(ant[po],1,lo,0); } lo = x; if(lo>d[po]) lo = d[po]; if(!val[ant[po]][lo]){ modify(ant[po],1,lo,1); } val[ant[po]][lo]++; } a[u] = x; } else{ scanf("%d",&u); if(d[u]>350){ printf("%d\n",query(ant[u],1,0,d[u])); } else{ for(int j = 0;j<=d[u];j++) vis[j] = false; for(int j = 0;j<graph[u].size();j++){ int po = graph[u][j]; int lo = a[po]; if(lo>d[u]) lo = d[u]; vis[lo] = true; } for(int j = 0;j<=d[u];j++){ if(!vis[j]){ printf("%d\n",j); break; } } } } } } return 0; }