【清北學堂國慶刷題班】 Day3
【清北學堂國慶刷題班】 Day3
T1 歡樂
【問題描述】
你是能看到第一題的friends呢。
——hja
眾所周知,小蔥同學擅長計算,尤其擅長計算組合數,但這個題和組合數沒什麼關係。
給定N,求一個K,使得\(K!\ge \frac {N!} {K!}\)並且K儘量小。
【輸入格式】
一行一個整數N。
【輸出格式】
一行一個整數代表答案。
【樣例輸入】
10
【樣例輸出】
7
【資料規模與約定】
對於30%的資料,\(N\le 10\)
對於60%的資料,\(N\le 100\)
對於80%的資料,\(N\le 1000\)
對於100%的資料,\(3\le N \le 10^6\)
思路
取對數
看到這道題目,我第一感覺是來模擬!PS:模擬多爽啊。
結果發現,這大於多少以上的寫不出來了,程式立刻就炸了,時間分分鐘超過了,整個人都崩了!
所以就有了三十分的程式碼
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <string> #include <cmath> using namespace std; long long n; unsigned long long a[100]; int main() { cin>>n; if(n==20) { cout<<13<<endl; return 0; } if(n>=21&&n<=22) { cout<<14<<endl; return 0; } if(n>=23&&n<=24) { cout<<15<<endl; return 0; } if(n>=25) { cout<<13+ceil((n-19)/2)<<endl; return 0; } a[1]=1; for(int i=2;i<=n;i++) a[i]=a[i-1]*i; for(int i=1;i<=n;i++) if(a[i]*a[i]>=a[n]) { cout<<i<<endl; return 0; } return 0; }
這是一份非常模擬的程式碼,而且有一些是自己找的規律,我就想當然地寫了上去,但是很不幸,還是隻有這麼一點分。我便陷入了沉思。
等到老師講的時候,才發現。世界如此簡單。
取一個對數不就可以了嗎?為什麼可以取對數呢?
(鄭重其事的說)原因有三,首先這個階乘有一個神奇的特點,就是越到後面,位數就越是不一樣,這樣我們就可以取一個對數,直接計算它的位數,然後對於這個位數直接進行計算,我們就可以取得正確答案了。其次,我們對於乘法,如果大於十位的話,實際上兩個數相乘,總的位數大概?就是兩個數的位數之和-1,所以我們就可以算對數來解決。最後一點原因就是,就是,我也不知道,應該也就兩點原因吧。
然後這個除法怎麼辦了,難不成換成乘法嗎,而且我們取得是10的對數,所以我們可以使用高中一年級就學過的對數換底公式:
這樣就可以把乘除法轉化為加減法了,這樣世界就非常簡單了
程式碼
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int n;
long double a[1000005];//保留一下精度,否則會出問題
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
a[i]=a[i-1]+log10(i);//遞推每個數的階乘的對數
for(int i=1;i<=n;i++)
{
if(a[i]>=a[n]-a[i])//運用了對數換底公式
{
cout<<i<<endl;
return 0;
}
}
}
T2 水題
【問題描述】
你是能看到第二題的friends呢。
——aoao
眾所周知,小蔥同學擅長計算,尤其擅長計算組合數,但這個題和組合數沒什麼關係。
現在有若干個任務,每個任務需要一定的執行時間,以及要求其依賴的所有任務執行完成後才能夠執行。你可以同時做多個任務,但是一個任務一旦開始就不能停下必須做完。如果當前有多個任務可以做,按照這些任務到來的時間作為第一關鍵字,任務的名字作為第二關鍵字選擇最小的任務執行。問執行完所有任務所需要的時間是多少。
【輸入格式】
一行一個字串。
對於每個任務,其格式為“任務名字:[依賴任務1,依賴任務2,……,依賴任務k]:執行時間”。不同任務與不同任務之間用分號分割。在輸入完所有任務之後,會用“/”隔開一個整數,該整數代表同一時間最多執行多少個任務。
【輸出格式】
一行一個整數代表答案。
【樣例輸入】
b:[a]:2;c:[a]:3;a:[]:1/2
【樣例輸出】
4
【資料規模與約定】
對於30%的資料,任務數量\(\le 10\)。
對於60%的資料,任務數量\(\le 100\)。
對於100%的資料,任務數量\(\le 1000\),任務的名字長度小於等於50,所需要的執行時間不超過100,總依賴數量不超過200000。
思路
模擬題, 先鴿一下,如果在2020.11.4之前還沒有看見這個更改的話,請從我的QQ2844938982告訴我,謝謝大家了
程式碼(std)
T3 模擬
【問題描述】
你是能看到第三題的friends呢。
——laekov
眾所周知,小蔥同學擅長計算,尤其擅長計算組合數,但這個題和組合數沒什麼關係。
給定N個長度不超過50的01字串,你可以將其不斷重複直到長度為50!。現在要求輸出從1∼50!中有多少個位置,在這個位置上所有字串1的個數加起來等於i。
【輸入格式】
第一行一個整數N代表字串個數。
接下來N行每行一個字串。
【輸出格式】
輸出N+1行,其中第i行代表1的個數為i−1的位置的個數對\(10^9+7\)取模之後的結果。
【樣例輸入】
1
1
【樣例輸出】
0
318608048
【資料規模與約定】
對於20%的資料,\(N=1\)。
對於40%的資料,$N\le 10 $。
對於另外20%的資料,所有字串長度均為質數。
對於另外20%的資料,所有字串長度小於等於10。
對於100%的資料,\(1\le N \le 100\),我覺得原來的第三題太簡單了,所以臨時換了這個題。
思路
這道題是一道典型的分類討論, 我們可以在考試中根據不同的資料點來給答案,也不失是一種好辦法
-
\(N=1\)
這樣只有一個字串,因此也就是說我們要統計0或1的個數,所以最終的答案就是第一個字串的0的個數a,1的個數b,以及字串的總長度x,是0的個數則為\(a*\frac {50!} {x}\),同理,是1的個數則為\(b*\frac {50!} {x}\),這樣就可以得到20%的分數了
-
字串的長度小於等於10
這種情況還是可以按照\(N=1\)的思路來寫的,因為我們可以求字串長度的最小公倍數,然後各自拓展到這個最小公倍數長度的字串,進行統計,接下來的就是和$N=1 $同理了
-
字串長度互質
我們可以討論得到,當兩個字串長度互質的時候,我們不關心這個字串長什麼樣子,因為每一位一定對應每一位,而且我們只需要知道每個字串有幾個0和幾個1,然後用類似於多項式乘法即可
-
其他
如果是其他情況的話,我們可以進行把不是互質的轉化為互質的,如
第一個字串長為4為1001,第二個字串長為6為010011。那我們就可以找到他們的最大公約數2,把這個分成長度為2的組合,如
10 01 10 01 10 01
01 00 11 01 00 11
這樣分完組之後我們可以發現對於一個長度的字串
10 01
01 00 11
可以將他們的首位取出,構成兩個新的字串
10
001
對這個進行統計,再將他們的下一位取出,再構成兩個新的字串,順理成章,我們就可以做出來其他的情況了。
程式碼
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define inc(a,b) {a+=b;if (a>=mo) a-=mo;}
const int maxn=1010;
const int mo=1000000007;
const int maxb=32*27*25*49;
const int prime[]={0,11,13,17,19,23,29,31,37,41,43,47,0};//提前定義質數
int n,num[maxb+10],res[maxn][12],resx[maxn][12],ans[maxn],l[maxn];
char s[maxn][maxn];
int main()
{
scanf("%d",&n);
for (int a=1;a<=n;a++)
{
scanf("%s",s[a]+1);
l[a]=strlen(s[a]+1);//讀取每一個字串的長度
}
for (int a=1;a<=n;a++)
if (maxb%l[a]==0)
for (int b=1,c=1;b<=maxb;b++,c++)
{
if (c>l[a]) c=1;
if (s[a][c]=='1') num[b]++;
}
for (int a=1;a<=maxb;a++)
inc(res[num[a]][a%12],1);
for (int a=1;prime[a];a++)
{
int v=prime[a];
memset(num,0,sizeof(num));
for (int b=1;b<=n;b++)
if (l[b]%v==0)
{
for (int c=1,d=1;c<=v*12;c++,d++)
{
if (d==l[b]+1) d=1;
if (s[b][d]=='1') num[c]++;
}
}
memset(resx,0,sizeof(resx));
for (int b=0;b<=n;b++)
for (int c=1;c<=v*12;c++)
inc(resx[b+num[c]][c%12],res[b][c%12]);
for (int b=0;b<=n;b++)
for (int c=0;c<12;c++)
res[b][c]=resx[b][c];
}
for (int a=0;a<=n;a++)
{
for (int b=0;b<12;b++)
inc(ans[a],res[a][b]);
ans[a]%=mo;
}
int mul=1;
for (int a=1;a<=50;a++)
{
if (a==32 || a==27 || a==25 || a==49) continue;
bool able=true;
for (int b=1;prime[b];b++)
if (prime[b]==a) able=false;
if (!able) continue;
mul=1ll*mul*a%mo;
}
for (int a=0;a<=n;a++)
printf("%d\n",(int)(1ll*ans[a]*mul%mo));
return 0;
}
T4 賽
【問題描述】
你是能看到第四題的friends呢。
——laekov
眾所周知,小蔥同學擅長計算,尤其擅長計算組合數,但這個題和組合數沒什麼關係。
定義函式ff為計算序列V字的數量的函式。定義V字為\(i<j<k,a_i>a_j,a_k>a_j\)的三元組\((i,j,k)\),現在給定n個數\(a_1,a_2……a_N\),求:
\[\sum_{l=1}^n \sum_{r=l}^{n}f(a_l,a_{l+1},…,a_r) \]【輸入格式】
第一行一個整數N。
接下來一行N個整數。
【輸出格式】
一行一個整數代表答案對\(10^9+7\)取模之後的答案。
【樣例輸入】
5
2 3 1 4 5
【樣例輸出】
9
【資料範圍與規定】。
對於30%的資料,\(N\le 20\)。
對於60%的資料,\(N\le 100\)。
對於80%的資料, \(N\le 1000\)。
對於100%的資料,\(1\le N\le 10^5,1\le a_i \le N\)
思路
對於這一道題目來說,我還是想到了思路的,暴力。這個暴力不得了,一旦成功,我已經得了60分了,所以這道題部分分給的很足,但是對於想拿省一的人來說,必須要精益求精,因此,我將闡述這道題的思路,首先看一下我60分的程式碼:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int Mod=1e9+7;
long long n;
long long a[100005];
long long f(long long l,long long r)//解決問題的函式
{
long long sol=0;
for(int i=l;i<=r;i++)
{
for(int j=i;j<=r;j++)
{
for(int k=j;k<=r;k++)
if(a[i]>a[j]&&a[k]>a[j])
sol=(sol+1)%Mod;//簡單的列舉
}
}
return sol;
}
int main()
{
long long ans=0;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int l=1;l<=n;l++)
{
for(int r=l;r<=n;r++)
ans+=f(l,r)%Mod;//計算答案
}
cout<<ans%Mod<<endl;//輸出結果
return 0;
}
因此,最後的四個點都載入失敗了,複雜度\(O(n^5)\)
接著我們改進一下
我們可以將題目轉化為問這個v字在多少個區間裡,當且僅當一個數對滿足\(1\le l\le i 且 k\le r\le n\)時,我們可以認為這個v字在這個區間,所以我們的答案就是
\(ans+=i*(n-k+1)\),時間複雜度\(O(n^3)\)
八十分程式碼
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int Mod=1e9+7;
int n;
int a[100005];
long long ans;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
for(int k=j;k<=n;k++)
{
if(a[i]>a[j]&&a[j]<a[k])
ans=(ans+i*(n-k+1)%Mod)%Mod;
}
}
}
cout<<ans<<endl;
return 0;
}
現在我們已經優化到\(O(n^3)\)了,所以接下來我們該怎麼繼續優化呢,我們可以發現,只要統計一個數的左邊比他大的數x個,右邊比他大的數y個,就可以了,所以對於每個i列舉一個k,則最終有式子:
\[ans=\sum _{b=1}^n i_b*\sum_{c=1}^nk_c \]這樣就可以分開來列舉,時間複雜度為\(O(n^2)\),然後加一個值域線段樹或者值域樹狀陣列就可以得到\(O(n\log n)\)的複雜度了
程式碼
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
#define lb(x) ((x)&(-(x)))
const int maxn=100010;
int n,z[maxn];
long long y[maxn],pre[maxn],suf[maxn];
long long query(int p)
{
long long ans=0;
for (;p;p-=lb(p))
ans+=y[p];
return ans;
}
void modify(int p,int v)
{
for (;p<=n;p+=lb(p))
y[p]+=v;
}
int main()
{
scanf("%d",&n);
for (int a=1;a<=n;a++)
scanf("%d",&z[a]);
memset(y,0,sizeof(y));
for (int a=1;a<=n;a++)
{
pre[a]=query(n)-query(z[a]);
modify(z[a],a);
}
memset(y,0,sizeof(y));
for (int a=n;a>=1;a--)
{
suf[a]=query(n)-query(z[a]);
modify(z[a],n-a+1);
}
long long ans=0;
for (int a=1;a<=n;a++)
ans+=pre[a]*suf[a];
printf("%lld\n",ans);
return 0;
}
總結
這套題目總體來說部分分給的很足,而且最重要的一點就是難度不是遞增的,因此以後我們在做題的時候不能一直幹一道題,而是先把題目看一遍,從簡單的入手才能得到更多的分數。這次的考點主要是:
數學變形,大模擬,部分分的分別解決,樹狀陣列||線段樹的運用以及不同角度的思考問題