2021.10.7 NKOJ周賽總結
阿新 • • 發佈:2021-10-08
Ⅰ.自描述序列
問題描述:
序列 1,2,2,1,1,2,1,2,2,1,2,2,1,1,2,1,1,2,2,1,... 看似毫無規律,但若我們將相鄰的數字合併 : 1,22,11,2,1,22,1,22,11,2,11,22,1,... 再將每組替換為組內數字的個數,可以得到: 1,2,2,1,1,2,1,2,2,1,2,2,1,... 可以發現,這就是原序列,因此,這個序列可以無限生成下去。 現在你需要求這個序列的第 n 項(下標從 1 開始計算)。
輸入格式:
本題有多組測試資料,第一行輸入資料組數 T ,每組資料僅包含一行一個正整數 n 。
輸出格式:
對每組資料,輸出該序列的第 n 項。
資料範圍:
1≤T≤10 , 1≤n≤10e7
這個就是一道模擬題,沒有什麼可以多說的。
Code:
#pragma GCC optimize(2) #pragma GCC optimize(3) #include<bits/stdc++.h> using namespace std; int n,head,tail,lst,A[15],f[10000005]; #define gc (p1==p2&&(p2=(p1=buf)+fread(buf,1,65536,stdin),p1==p2)?EOF:*p1++) char buf[65536],*p1,*p2; inline自描述序列int read() { char ch;int x(0); while((ch=gc)<48); do x=x*10+ch-48;while((ch=gc)>=48); return x; } int main() { n=read(),f[1]=1,f[2]=f[3]=head=lst=2,tail=3; for(register int i=1;i<=n;++i) A[i]=read(),A[0]=max(A[0],A[i]); while(tail<A[0]) { int k=f[++head];if(lst&1) lst=2;else lst=1; for(register int i=1;i<=k;++i) f[++tail]=lst; } for(register int i=1;i<=n;++i) printf("%d\n",f[A[i]]); return 0; }
Ⅱ.極限
問題描述:
輸入格式:
第一行輸入 1 個整數 n 。 第二行輸入 n 個整數,f(1)、f(2)、...、f(n) 。
輸出格式:
輸出 n 個數 g(1)、g(2)、...、g(n) ,每個數一行。 可以證明在題目條件下,答案是有限的數值,且都能寫成 p/q 的形式,其中 gcd(p,q)=1。因此輸出格式為 p/q。
資料範圍:
1≤n≤10e5 , 1≤f(i)≤n
首先,很顯然,進行多次操作後 f( ) 是肯定會成周期的,即 成環,但會存在不在環上的點,由於 k 趨於無窮,所以不在環上的點形成的鏈是可以忽略其貢獻的,所以直接搜就可以了。
離譜的是,在考試時,搜尋居然沒有記憶化,等於打了個 n2的純暴力!
離譜的是,考完之後,聽見同學說是基環樹,才恍然發現這不基環樹嗎?
離譜的是,考前一天,
基環樹看這裡
Code:
#pragma GCC optimize(2) #pragma GCC optimize(3) #include<bits/stdc++.h> using namespace std; int n,lst,tot,A[100005],B[100005],Mark[100005]; long long C[100005],gcd,a,b,Ans1[100005],Ans2[100005]; #define gc (p1==p2&&(p2=(p1=buf)+fread(buf,1,65536,stdin),p1==p2)?EOF:*p1++) char buf[65536],*p1,*p2; inline int read() { char ch;int x(0); while((ch=gc)<48); do x=x*10+ch-48;while((ch=gc)>=48); return x; } inline long long Gcd(long long x,long long y) { long long z=x%y; if(!z) return y; return Gcd(y,z); } int main() { n=read(); for(register int i=1;i<=n;++i) A[i]=read(); for(register int i=1,cnt;i<=n;++i) { if(Mark[i]) {printf("%lld/%lld\n",Ans1[Mark[i]],Ans2[Mark[i]]);continue;} C[1]=lst=A[i],B[lst]=cnt=1,++tot; while(1) { lst=A[lst]; if(Mark[lst]==tot) {a=C[cnt]-C[B[lst]-1],b=cnt-B[lst]+1,gcd=Gcd(a,b),Ans1[tot]=a/gcd,Ans2[tot]=b/gcd;;break;} if(Mark[lst]) {Ans1[tot]=Ans1[Mark[lst]],Ans2[tot]=Ans2[Mark[lst]];break;} ++cnt,B[lst]=cnt,C[cnt]=C[cnt-1]+lst,Mark[lst]=tot; } printf("%lld/%lld\n",Ans1[tot],Ans2[tot]); } return 0; }極限
Ⅲ.最大三角形
問題描述:
果果有一些木棍,長度分別為 a1,a2,...,an。 果果想知道,僅使用 al,al+1,...,ar 這些木棍,每根木棍只能使用一次,能夠組成的三角形中周長最長是多少。
輸入格式:
第一行輸入 2 個整數 n、q。 第二行輸入 n 個整數 a1,a2,...,an 。 接下來 q 行,每行有兩個整數 li、ri 。
輸出格式:
對每組詢問,輸出最大的周長,一行一個。如果無法組成三角形,輸出 −1 。
資料範圍:
1≤n、q≤10e5 , 1≤ai≤10e9 , 1≤li≤ri≤n
對於三角形大家一定不陌生,小學生一定都知道:三角形 任意兩邊之和大於第三邊,任意兩邊之差小於第三邊。
當然,其實三角形只需滿足:最大邊小於另兩邊之和。
那麼,如果最大邊確定,要構成三角形的話,我們一定會選擇剩下的邊中最大的兩條,且同時滿足了周長最大。
由此觀之,對於一個區間,我們從最大的開始判斷,隨後,次大等等。
區間 k 大,這個明顯主席樹了。
最後發現一下複雜度:一段區間無法構成三角形,最長的區間一定是斐波那契數列,在該資料範圍下最多 44 個,即每次詢問在主席樹中最大查詢 44 次 ,所以是 O( 44 * n * log(n) ),並且,44次是遠遠跑不滿的。
Code:
#pragma GCC optimize(2) #pragma GCC optimize(3) #include<bits/stdc++.h> using namespace std; int n,m,k,tot,cnt,A[100005],B[100005]; struct node {int L,R,Sum,Id;}Tr[2000005]; #define gc (p1==p2&&(p2=(p1=buf)+fread(buf,1,65536,stdin),p1==p2)?EOF:*p1++) char buf[65536],*p1,*p2; inline int read() { char ch;int x(0); while((ch=gc)<48); do x=x*10+ch-48;while((ch=gc)>=48); return x; } inline void Update(int x,int &y,int L,int R,int pos) { y=++cnt,Tr[y].L=Tr[x].L,Tr[y].R=Tr[x].R,Tr[y].Sum=Tr[x].Sum+1; if(L==R) return; int M=(L+R)>>1; if(pos<=M) Update(Tr[x].L,Tr[y].L,L,M,pos); else Update(Tr[x].R,Tr[y].R,M+1,R,pos); } inline int Get(int x,int y,int L,int R) { if(L==R) return B[L]; int M=(L+R)>>1,Tmp=Tr[Tr[y].L].Sum-Tr[Tr[x].L].Sum; if(Tmp>=k) return Get(Tr[x].L,Tr[y].L,L,M); k-=Tmp;return Get(Tr[x].R,Tr[y].R,M+1,R); } int main() { n=read(),m=read(); for(register int i=1;i<=n;++i) A[i]=B[i]=read(); sort(B+1,B+n+1),tot=unique(B+1,B+n+1)-(B+1); for(register int i=1;i<=n;++i) A[i]=lower_bound(B+1,B+tot+1,A[i])-B; for(register int i=1;i<=n;++i) Update(Tr[i-1].Id,Tr[i].Id,1,tot,A[i]); for(register int i=1,kk,x,y;i<=m;++i) { x=read(),y=read();if(y-x<2) {printf("-1\n");continue;} int t1,t2,t3,t4(2); k=y-x+1,t1=Get(Tr[x-1].Id,Tr[y].Id,1,tot), k=y-x,t2=Get(Tr[x-1].Id,Tr[y].Id,1,tot), k=kk=y-x-1,t3=Get(Tr[x-1].Id,Tr[y].Id,1,tot); while(t3+t2<=t1&&kk) t1=t2,t2=t3,kk=k=y-x-t4,++t4,t3=Get(Tr[x-1].Id,Tr[y].Id,1,tot); if(!kk) printf("-1\n"); else printf("%lld\n",1LL*t3+t2+t1); } return 0; }最大三角形
Ⅳ.彩色的樹
問題描述:
輸入格式:第 1 行輸入 1 個整數 n 。 第 2 行輸入 n 個整數 c1、c2、...、cn 。 接下來 n−1 行,每行輸入 2 個整數 ui、vi,表示一條樹邊。
輸出格式:
輸出題目要求的答案。
資料範圍:
1≤n≤2×10e5 , 1≤ci≤n
美其名曰思維題,實則是道亂搞題。對於每一個點分別求貢獻,最後加起來。
(這道題實在不好講清楚,~qwq~)
Code:
#pragma GCC optimize(2) #pragma GCC optimize(3) #include<bits/stdc++.h> using namespace std; bool Mark[200005]; int n,tot,C[200005],Size[200005],Cnt,Head[200005],Next[400005],To[400005]; long long Sum[200005],Ans; #define gc (p1==p2&&(p2=(p1=buf)+fread(buf,1,65536,stdin),p1==p2)?EOF:*p1++) char buf[65536],*p1,*p2; inline int read() { char ch;int x(0); while((ch=gc)<48); do x=x*10+ch-48;while((ch=gc)>=48); return x; } inline void ADD(int x,int y) {Next[++Cnt]=Head[x],Head[x]=Cnt,To[Cnt]=y;} inline void DFS(int x,int fa) { Size[x]=1,++Sum[C[x]];long long Tmp=Sum[C[x]],Temp; for(register int i=Head[x],j;i;i=Next[i]) { j=To[i];if(j==fa) continue; DFS(j,x),Size[x]+=Size[j],Temp=Size[j]-Sum[C[x]]+Tmp,Ans+=(1LL*Temp*(Temp-1))/2,Sum[C[x]]+=Temp,Tmp=Sum[C[x]]; } } int main() { n=read(); for(register int i=1;i<=n;++i) { C[i]=read(); if(!Mark[C[i]]) ++tot,Mark[C[i]]=1; } for(register int i=1,x,y;i<n;++i) x=read(),y=read(),ADD(x,y),ADD(y,x); DFS(1,0);long long Tmp; for(register int i=1;i<=n;++i) if(Mark[i]) Tmp=n-Sum[i],Ans+=Tmp*(Tmp-1)/2; printf("%lld",1LL*tot*n*(n-1)/2-Ans); return 0; }彩色的樹