CCF-NOIP-2018 提高組(複賽) 模擬試題(一)
T1 帽子戲法
問題描述
小 Y 有一個 \(n*n*n\) 的“帽子立方體” ,即一個 \(n\) 層的立方體,每層的帽子都
可以排成 \(n*n\) 的矩陣。 “帽子立方體”中的每一個帽子都有一個顏色,顏色共 26
種,用 26 個大寫字母來表示。
現在,小 Y 邀請小 F 來表演她的帽子戲法。小 F 會
\(2\)
種帽子戲法:
- 指定一個長方體形狀的區域,將指定區域內的所有帽子全部變成指定的
顏色。 -
指定一個長方體形狀的區域,將指定區域內所有指定顏色帽子全部變成
綠色(用大寫字母 \(G\) 表示) 。
小Y很喜歡綠色, 所以初始時立方體內的所有帽子都是綠色的。 不僅如此,
小Y還會時不時地提出問題:他會指定一個長方體形狀的區域,並詢問在這個
區域內有多少綠色的帽子。
小Y的帽子琳琅滿目,請你來幫他數一數吧!
輸入格式
第一行 2 個正整數 \(n,Q\) ,分別描述立方體的大小、以及小 F 表演帽子戲法和
小 Y 提問的總次數。
接下來Q行,每行第一個數 \(op(0 ≤ op ≤ 2)\) 表示這次詢問或帽子戲法的型別。
若 \(op = 0\) ,表示這是小 Y 的一個提問,接下來 6 個正整數描述詢問指定的
區域。
若 \(op = 1\) ,接下來 6 個正整數表示帽子戲法指定的區域,之後一個大寫字母
/%0表示小 F 會把指定區域內的所有帽子都變成 \(col\) 顏色。
若 \(op = 2\) , 接下來 6 個正整數表示帽子戲法指定的區域, 表示小 F 會把指定
區域內的所有帽子都變成綠色。
描述一個區域的方法為:用 6 個整數 \(x_0,y_0,z_0,x_1,y_1,z_1\) 表示從第 \(x_0\) 層至第 \(x_1\)
層,從第 \(y_0\) 行至第 \(y_1\) 行,從第 \(z_0\) 列至第 \(z_1\) 列的區域(層、行、列編號的範圍都是
1…n) 。
輸出格式
對於每個詢問,輸出一行一個整數表示答案。
樣例
樣例輸入 3 5 1 2 2 2 3 3 3 B 1 1 3 2 3 3 2 R 0 1 1 1 3 3 3 2 3 3 3 3 3 3 B 0 2 1 3 3 3 3
樣例輸出 |
---|
18 |
3 |
資料範圍
對於 10%的資料,保證 \(n=1\) 。
對於另外 10%的資料,保證只有詢問操作,即保證 \(op=0\) 。
對於 30%的資料,保證 \(n≤ 5,Q ≤ 10\) 。
對於 100%的資料,保證
\(n ≤ 40,Q ≤ 200\)
。
題解
看一下資料範圍就應該知道是暴力的吧。 \(maxn*maxn*maxn*maxQ=12800000\) ,排除每一次查詢、更改操作全部為最大操作的情況可知, \(O(n^3)\) 可解
貼出程式碼
#include<bits/stdc++.h> #define maxn 405 using namespace std; int n,q,ans; int x1,z1,x2,y2,z2; int y01,cmd; char cas; char a[maxn][maxn][maxn]; int main(){ //freopen("hat.in","r",stdin); //freopen("hat.out","w",stdout); ios::sync_with_stdio(false); cin.tie(0); cin>>n>>q; for(register int i=0;i<=n;i++){ for(register int j=0;j<=n;j++){ for(register int k=0;k<=n;k++)a[i][j][k]='G'; } } while(q--){ cin>>cmd; if(cmd==0){ ans=0; cin>>x1>>y01>>z1>>x2>>y2>>z2; //cout<<x1<<" "<<y01<<" "<<z1<<" "<<x2<<" "<<y2<<" "<<z2<<endl; for(register int i=x1;i<=x2;i++){ for(register int j=y01;j<=y2;j++){ for(register int k=z1;k<=z2;k++){ //cout<<"now:"<<i<<" "<<j<<" "<<k<<" "<<a[i][j][k]<<endl; if(a[i][j][k]=='G')ans++; } } } cout<<ans<<endl; } else if(cmd==1){ cin>>x1>>y01>>z1>>x2>>y2>>z2; //cout<<x1<<" "<<y01<<" "<<z1<<" "<<x2<<" "<<y2<<' '<<z2<<endl; cin>>cas; for(register int i=x1;i<=x2;i++){ for(register int j=y01;j<=y2;j++){ for(register int k=z1;k<=z2;k++){ a[i][j][k]=cas; } } } } else if(cmd==2){ cin>>x1>>y01>>z1>>x2>>y2>>z2; //cout<<x1<<" "<<y01<<" "<<z1<<" "<<x2<<" "<<y2<<' '<<z2<<endl; cin>>cas; for(register int i=x1;i<=x2;i++){ for(register int j=y01;j<=y2;j++){ for(register int k=z1;k<=z2;k++){ if(a[i][j][k]==cas){ a[i][j][k]='G'; //cout<<i<<" "<<j<<" "<<k<<" "<<a[i][j][k]<<endl; } } } } } } return 0; }
T2 最大佇列
問題描述
給定一個長度為 \(n\) 的排列(共包含 \(n\) 個整數,每個數取值範圍 \(1\) 和 \(n\) 之間,且每個正整數出現並只出現一次)。藉助一個棧,依次將這個排列的每個元素進棧,並在合適的時候出棧,可以得到不同的出棧序列。不同的操作會帶來不同的出棧序列,請你求出在所有可能的方案中,字典序最大的出棧序列。
輸入格式
輸入資料共包括兩行。
第一行包含一個正整數 \(n\) ,表示給定的排列的長度。
第二行包含
\(n\)
個正整數,描述給定的序列。
輸出格式
僅一行,共 \(n\) 個整數,表示你計算出的出棧序列。
樣例
樣例輸入1 |
---|
4 |
4 2 1 3 |
樣例輸出1 |
---|
4 3 1 2 |
樣例輸入2 |
---|
10 |
4 5 1 2 6 10 7 8 3 9 |
樣例輸出2 |
---|
10 9 3 8 7 6 2 1 5 4 |
資料範圍
題解
先說說自己的
思路。先將所有資料排序,然後為了讓字典序儘量大則每次選取當前沒有被選取的最大值。同時我們維護一個上一次選取的數的位置 \(lastp\) ,若 \(lastp\) 的值比當前我們要出棧的元素的位置的值要小我們則直接讓當前元素出棧,否則我們從將 \(lastp\) 到當前位置 \(i\) 的所有元素全部出棧。在一定程度上這樣的思路是正確的,但是我們如果這樣做就考慮漏了一種情況,即當我們先輸出元素比我們從 \(lastp\) 輸出到 \(i\) 時中間值的字典序要小,則整體字典序會比正確答案偏小。
上40分程式碼
#include<bits/stdc++.h> #define maxn 200005 using namespace std; inline int read(){ register char c=get();register int f=1,_=0; while(c>'9' || c<'0')f=(c=='-')?-1:1,c=get(); while(c<='9' && c>='0')_=(_<<3)+(_<<1)+(c^48),c=get(); return _*f; } int n; int a[maxn]; struct edge{ int a; int flag; }E[maxn]; bool cmp(edge a,edge b){ if(a.a!=b.a)return a.a>b.a; return a.flag<b.flag; } set<int> save; int lastp=-1; int out[maxn],note=1; //map<int,int> jump; int main(){ freopen("seq.in","r",stdin); freopen("seq.out","w",stdout); n=read(); //cout<<n<<endl; for(register int i=1;i<=n;i++){ a[i]=read(); //cout<<a[i]<<" "; E[i].a=a[i]; E[i].flag=i; } sort(E+1,E+1+n,cmp); for(register int i=1;i<=n;i++){ if(save.count(E[i].a))continue; if(lastp<E[i].flag){ out[note]=E[i].a; save.insert(E[i].a); note++; lastp=E[i].flag; } else{ for(register int j=lastp-1;j>=E[i].flag;j--){ if(save.count(a[j]))continue; out[note]=a[j]; note++; save.insert(a[j]); //jump[j]=max(jump[j],lastp-1); } lastp=E[i].flag; } } for(register int i=1;i<note;i++){ printf("%d",out[i]); if(i!=note-1)printf(" "); } return 0; }
事實上,為了將上述情況納入考慮範圍內,我們選擇使用線上的演算法。我們令當前棧頂的元素為 \(x\) ,並使所有還沒有入棧的元素中的最大值 \(y\) 。當 \(x\le y\) 時我們不斷入棧,直到情況不滿足為止。此時則將y直接扔出去即可。當沒有比棧頂元素更大的值時我們就不再入棧,而一直彈出棧頂元素直到滿足條件或沒有元素位置。由於每次查詢最大值時的複雜度為 \(O(n)\) ,遍歷一次的複雜度為 \(O(n)\) ,總複雜度則為 \(O(n^2)\) 。
樸素
\(O(n^2)\)
程式碼如下
#include<bits/stdc++.h> #define maxn 200005 using namespace std; inline char get(){ static char buf[30],*p1=buf,*p2=buf; return p1==p2 && (p2=(p1=buf)+fread(buf,1,30,stdin),p1==p2)?EOF:*p1++; } inline int read(){ register char c=get();register int f=1,_=0; while(c>'9' || c<'0')f=(c=='-')?-1:1,c=get(); while(c<='9' && c>='0')_=(_<<3)+(_<<1)+(c^48),c=get(); return _*f; } int n; int a[maxn]; int maxnow; int getmax(int now){ int cas=-1; for(register int i=now;i<=n;i++)cas=max(cas,a[i]); return cas; } stack<int> box; int out[maxn],note=1; int main(){ //freopen("1.txt","r",stdin); n=read(); for(register int i=1;i<=n;i++)a[i]=read(); for(register int i=1;i<=n;i++){ //cout<<box.top()<<endl; if(box.empty()){ box.push(a[i]); continue; } maxnow=getmax(i); if(box.top()>=maxnow){ out[note]=box.top(); //cout<<out[note]<<" "; box.pop(); note++; i--; continue; } box.push(a[i]); } while(!box.empty()){ out[note]=box.top(); //cout<<out[note]<<" "; box.pop(); note++; } //cout<<endl; for(register int i=1;i<note;i++)printf("%d ",out[i]); return 0; }
當然樸素演算法並不能滿足 \(n=200000\) 等大資料的要求,因此我們要對演算法進行優化。事實上我們可以減少取最大值的次數——我們只在開始時和每一次最大值被pop出棧時才重新取一次最大值
for(register int i=1;i<=n;i++)a[i]=read(),maxnow=max(maxnow,a[i]); for(register int i=1;i<=n;i++){ if(i==1){ box.push(a[i]); continue; } if(box.top()>=maxnow){ maxnow=getmax(i); out[note]=box.top(); box.pop(); note++; i--; continue; } box.push(a[i]); }
完整程式碼
#include<bits/stdc++.h> #define maxn 200005 using namespace std; inline char get(){ static char buf[30],*p1=buf,*p2=buf; return p1==p2 && (p2=(p1=buf)+fread(buf,1,30,stdin),p1==p2)?EOF:*p1++; } inline int read(){ register char c=get();register int f=1,_=0; while(c>'9' || c<'0')f=(c=='-')?-1:1,c=get(); while(c<='9' && c>='0')_=(_<<3)+(_<<1)+(c^48),c=get(); return _*f; } int n; int a[maxn]; int maxnow; int getmax(int now){ int cas=-1; for(register int i=now;i<=n;i++)cas=max(cas,a[i]); return cas; } stack<int> box; int out[maxn],note=1; int main(){ //freopen("1.txt","r",stdin); n=read(); for(register int i=1;i<=n;i++)a[i]=read(),maxnow=max(maxnow,a[i]); for(register int i=1;i<=n;i++){ if(box.empty()){ box.push(a[i]); continue; } if(box.top()>=maxnow){ maxnow=getmax(i); out[note]=box.top(); //cout<<box.top()<<endl; box.pop(); note++; i--; continue; } box.push(a[i]); } while(!box.empty()){ out[note]=box.top(); box.pop(); note++; } for(register int i=1;i<note;i++)printf("%d ",out[i]); return 0; }
T3 思考熊的馬拉松
題面描述
今年, \(n\) 只思考熊參加了校園馬拉松比賽。馬拉松的賽道是環形的,每圈的
長度是:,完成比賽需要跑;圈。
比賽中,甲領先乙很長距離,繞過一圈或多圈後從後面追上了乙的現象叫做
“套圈” 。 套圈現象非常常見, 例如: 跑得比誰都快的 \(S\) 熊可以套某些熊 \(L-1\) 圈;
\(U\) 熊經常進行日常耐力訓練,套圈次數和被套圈次數基本持平;而$ M$ 作為一隻
老年熊,則是被套 \(L-1\) 圈的那種。
與人不同的是, 思考熊在跑步時都是勻速運動。$ W$ 熊是這次比賽的計時員,
他統計了參賽的 \(n\) 只熊的速度 \(v_1 ,v_2 ,…,v_n\) (其中最大的一個是 \(S\) 熊的速度) 。現
在 \(W\) 熊希望你告訴他,當速度最快的 \(S\) 熊到達終點時,場上所有熊中總共發生
了多少次套圈現象。
注意:在 \(S\) 熊剛剛到達終點那一刻,如果甲恰好追上了乙,此時也算作甲將
乙套圈。
輸入格式
輸入的第一行包含兩個整數 \(T,C\) ,分別表示這個測試點的組數和這個測試點的編號。對於測試資料,保證 \(T=10\) 。
每組資料的第一行包含3個正整數 \(n,A,L\) ,分別表示思考熊的只數、跑道每圈的長度和完成比賽所需要的圈數。保證 \(A,L\le 10^8\) 。
第二行包含 \(n\)個正整數
\(v_1,v_2,...,v_n\)
表示每隻思考熊的速度。保證這些數互不相同。
輸出格式
輸出 \(T\) 行,分別表示每組資料中所有熊發生的套圈總次數。
樣例
樣例輸入 |
---|
1 0 |
3 2 6 |
1 2 3 |
樣例輸出 |
---|
8 |
資料範圍
題解
這是一道數學題,去隔壁請一個數競大佬來吧。
首先我們已知比賽結束時間 \(t_{end}\) 一定是 \(\frac{L*A}{v_{max}}\) ,則此時為了求出結束時間我們就先將所有熊的速度從大到小排序,然後求 \(t_{end}=\frac{A*L}{v_1}\) 即可知道能跑的最長時間。這時我們知道對於$ \forall v_1
\(<\) v_2 \(都一定有\) v_2 $ 套 $ v_1 k 圈的時間是$ t_k=\frac{A k}{v_2-v_1} \(,則我們可以取\) t_k\le t_{end} \(為答案。則因此聯立方程組後可知,對於我們排序後的數列中\) \forall v_i和v_j \(,一定有\) a_{ij}=\frac{Aabs(v_i-v_j)}{v_{end}}
\(。我們列舉每一個數即可得到答案。複雜度即為\)
O(n^2)$。
** 雖然$ \forall v $$ 都 $ \le 10^8 \(,但是\) \forall x=abs(v_1-v_2)L \(不一定滿足\) x \subseteq (1,10^8) $ ! 貼出該題的部分分解法 (50)**
#include<bits/stdc++.h> #define maxn 100005 using namespace std; inline char get(){ static char buf[30],*p1=buf,*p2=buf; return p1==p2 && (p2=(p1=buf)+fread(buf,1,30,stdin),p1==p2)?EOF:*p1++; } inline long long read(){ register char c=get();register long long f=1,_=0; while(c>'9' || c<'0')f=(c=='-')?-1:1,c=get(); while(c<='9' && c>='0')_=(_<<3)+(_<<1)+(c^48),c=get(); return _*f; } long long n,A,L; long long v[maxn]; bool cmp(long long a,long long b){ return a>b; } long long ans=0; int main(){ //freopen("2.txt","r",stdin); long long T,C; T=read();C=read(); //cout<<1<<endl; while(T--){ ans=0; n=read();A=read();L=read(); for(register long long i=1;i<=n;i++)v[i]=read(); if(n==1){ puts("0"); continue; } sort(v+1,v+1+n,cmp); for(register long long i=1;i<=n;i++){ for(register long long j=i+1;j<=n;j++){ ans+=(L*(v[i]-v[j]))/v[1]; } } printf("%lld\n",ans); } return 0; }
緊接著我們繼續考慮更加一般的情況。

程式碼如下
#include <bits/stdc++.h> using namespace std; typedef long long int64; const int MAXN = 100005; int n; int64 L,A; int64 a[MAXN], b[MAXN], c[MAXN]; inline int lowbit(int x) { return x & (-x); } void insert(int x) { for (int i = x; i <= n; i += lowbit(i)) c[i]++; } int getsum(int x) { int res = 0; for (int i = x; i; i -= lowbit(i)) res += c[i]; return res; } int main() { //freopen ("running.in","r",stdin); //freopen ("running.out","w",stdout); int T, case_num; scanf("%d%d", &T, &case_num); while (T--) { scanf("%d%lld%lld", &n, &A, &L); int64 ans = 0; int64 maxv = 0; for (int i = 1; i <= n; i++) { c[i] = 0; scanf("%lld", &a[i]); maxv = max(maxv, a[i]); a[i] *= L; } sort(a + 1, a + n + 1); reverse(a + 1, a + n + 1); for (int i = 1; i <= n; i++) { ans += (a[i] / maxv) * (int64) (n - 2 * i + 1); a[i] %= maxv; } for (int i = 1; i <= n; i++) b[i] = a[i]; sort(b + 1, b + n + 1); int m = int(unique(b + 1, b + n + 1) - (b + 1)); for (int i = 1; i <= n; i++) { a[i] = int(lower_bound(b + 1, b + m + 1, a[i]) - b); ans -= getsum(a[i] - 1); insert(a[i]); } printf("%lld\n", ans); } return 0; }