1. 程式人生 > 實用技巧 >2020hdu多校第一場比賽及補題

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;
}