1. 程式人生 > 實用技巧 >【清北學堂國慶刷題班】 Day3

【清北學堂國慶刷題班】 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的對數,所以我們可以使用高中一年級就學過的對數換底公式:

\[\forall a,c \in (0,1) \cup (1,+\infty),有 \log_a b=\frac {\log_c b}{\log_c a} \]

這樣就可以把乘除法轉化為加減法了,這樣世界就非常簡單了

程式碼

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

總結

這套題目總體來說部分分給的很足,而且最重要的一點就是難度不是遞增的,因此以後我們在做題的時候不能一直幹一道題,而是先把題目看一遍,從簡單的入手才能得到更多的分數。這次的考點主要是:

數學變形,大模擬,部分分的分別解決,樹狀陣列||線段樹的運用以及不同角度的思考問題