編程練習 美團2021校招筆試-編程題(通用編程試題,第6場)


note 2021-03-08 23:52 全部施工完成
upd 2021-04-03 以后求Composition或者Partition的全部解還是FrobeniusSolve吧,人生苦短

試題地址

https://www.nowcoder.com/test/28665320/summary

解答

1

小團需要購買m樣裝飾物。商店出售n種裝飾物,按照從小到大的順序從左到右擺了一排。對於每一個裝飾物,小團都給予了一個美麗值 \(a_{i}\) 。 小團希望購買的裝飾物有着相似的大小,所以他要求購買的裝飾物在商店中擺放的位置是連續的一段。小團還認為,一個裝飾物的美麗值不能低於k,否則 會不好看。 現在, 請你計算小團有多少種不同的購頭方案。

// 基礎題
// 分成幾段 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,k;
ll a[100005];
ll lencnt;
ll summand;
int main(){
	cin>>n>>m>>k;
	ll ans=0;
	lencnt=0;
	for(ll i=1;i<=n+1;i++){
		if (i==n+1) a[i]=0;
		else  cin>>a[i];
		if(a[i]<k) {
		   summand=(lencnt>m-1)? lencnt-m+1:0;
		   ans+=summand; 
		   lencnt=0;
		}
		else lencnt++;
		//cout<<lencnt<<"lencnt"<<endl;
	}
	cout<<ans<<endl;
	return 0;
} 

2

給你\(n,k,d\)
讓你求帶限制的\(n\)的Composition個數,限制是
\(1\leq sum.\leq k\) && \(\text{max}\ sum.\geq d\)
由於答案可能很大,請將答案mod(998244353)后輸出。

思路1:(計算顯式公式)

n做Compostion且和數在[1,r]范圍內的方案數目是

\[C_{n}^{\{1, \ldots, r\}}= \sum_{j, k}(-1)^{k}\left(\begin{array}{l} j \\ k \end{array}\right)\left(\begin{array}{c} n-r k-1 \\ j-1 \end{array}\right) \]

n做Compostion且最大的和數是\(r(r\geq 1)\)的方案數目是

\[C_{n}^{\{\text{max}\ sum.=r\}}=C_{n}^{\{1, \ldots, r\}}-C_{n}^{\{1, \ldots, r-1\}} \]

n做Compostion且最大的和數在\([Left,Right]\)的方案數目是

\[C_{n}^{\{\text{max}\ sum.\in[Left,Right]\}}=C_{n}^{\{1, \ldots, Right\}}-C_{n}^{\{1, \ldots, Left-1\}} \]

//直接出擊,算那個doubel combinomial sum
//這題數據放水所以這份代碼AC了囧,別罵了別罵了
//感覺最有效的做法是dp

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mod 998244353
ll n,k,d;
ll b[10005][10005];
ll ans=0;
void GetBinomial(){
	for(int i=0;i<=1005;i++) b[i][0]=1;
	for(int  i=1;i<=1005;i++){
		for(int  j=1;j<=i;j++){
			b[i][j]=(b[i-1][j]+b[i-1][j-1])%mod;
		}
	}
} 

ll Comp_n_of_Range1R(ll n,ll r){//n做Compostion且和數在[1,r]范圍內的方案數目 
	if(r==0) return 0;
	ll sum=0;
	for(ll k=0;k<=(n-1)/r;k++){
		for(ll j=k>1?(k):1 ;j<=n-r*k;j++){
			if(k&1){
				sum=(sum+mod-b[j][k]*b[n-r*k-1][j-1]%mod)%mod;     
			}
			else {
				sum=(sum+b[j][k]*b[n-r*k-1][j-1]%mod)%mod;
			}
			//cout<<"sum"<<sum<<endl;
		}
	}
	return sum;
}





int main(){
	GetBinomial();
//	for(ll i=1;i<=10;i++){
//		for(ll j=0;j<=i;j++){
//			cout<<b[i][j]<<" ";
//		}
//		puts("");
//	}
    
	cin>>n>>k>>d;
	if(d<1||k<0||d>k) {
		ans=0;
		cout<<ans<<endl;
	}
	else{
		ll l=d;
		ll r=k;
	        ans=(ans+Comp_n_of_Range1R(n,r)-Comp_n_of_Range1R(n,l-1)+mod)%mod; 
		cout<<(ans+mod)%mod<<endl;
	} 
	
	return 0;
} 

思路2:(幾乎就是dp)

關鍵是由\(C_A(x)=\frac{1}{1-x-x^{2}-\cdots-x^{\ell}}\)的形式想到

\[C_A(x)=xC_A(x)+x^2C_A(x)+...+x^{\ell}C_A(x) \]

//我拿廣義斐波那契數又寫了一份AC代碼
//這題數據放水所以這份代碼AC了囧,別罵了別罵了
//感覺最有效的做法是dp 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k,d;
ll ans=0;
ll F[5010][5010];
#define mod 998244353

void  GetGeneralizedF(){
	ll window=1;
	memset(F,0,sizeof(F));
	for(ll i=1;i<=5005;i++) F[i][i-1]=1;
	for(ll v_l=1;v_l<=5005;v_l++){
		window=1;
		for(ll v_n=v_l;v_n<=5005;v_n++){
			if(v_n==v_l) ;
			else window=(window-F[v_l][v_n-v_l-1]+mod+F[v_l][v_n-1])%mod;
			F[v_l][v_n]=window;
		} 
	}	
}


ll Comp_n_of_Range1R(ll n,ll r){
	if(r<=0) return 0;
	else 	return F[r][n+r-1];
}



int main(){
	GetGeneralizedF();
        cin>>n>>k>>d;
	if(d<1||k<0||d>k) {
		ans=0;
		cout<<ans<<endl;
	}
	else{
		ll l=d;
		ll r=k;
	        ans=(ans+Comp_n_of_Range1R(n,r)-Comp_n_of_Range1R(n,l-1)+mod)%mod; 
		cout<<(ans+mod)%mod<<endl;
	} 
	
	return 0;
}
思路3:(dp,和思路2沒有本質區別)

關鍵是由 \(C_{A}(x)=\frac{1}{1-x-x^{2} \ldots-x}\) 的形式想到

\[C_{A}(x)=x C_{A}(x)+x^{2} C_{A}(x)+\ldots+x^{\ell} C_{A}(x) \]

設dp[n][r]是把n做Composition且和數屬於[1,r]范圍的方案數目
轉移方程(盡可能取合理值)

\[dp[n][r]=\sum_{j\in A}dp[n-j][r] \quad A=\{1,2,...r\} \]

邊界條件是

\[dp[0][r]=1 \]

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
#define mod 998244353
ll window=0;
ll dp[100005][105];
ll n,k,d;

void Calculatedp(ll n,ll k){
	dp[0][k]=1;
	window=0;
	for(ll i=1;i<=n;i++){
	
		if(i>=k+1) {
		  window=(window+dp[i-1][k]-dp[i-k-1][k]+mod)%mod;
		  dp[i][k]=window;
		}
		else {
		  window=(window+dp[i-1][k])%mod;	
		  dp[i][k]=window;
		}
	}
}

int main(){
	cin>>n>>k>>d;
	memset(dp,0,sizeof(dp));
	Calculatedp(n,k);
//	for(ll i=0;i<=n;i++) cout<<dp[i][k]<<" ";
//	puts("");
	Calculatedp(n,d-1);
//	for(ll i=0;i<=n;i++) cout<<dp[i][d-1]<<" ";
//	puts("");
	if(d<1||k<0||d>k) cout<<"0"<<endl;
	else cout<<(dp[n][k]+mod-dp[n][d-1])%mod<<endl;
	return 0;
	
}

3

小團有一個 \(n \times m\) 的矩陣A,\(\quad\) 他知道這是小美用一種特殊的方法生成的, 具體規則如下:
小美首先寫下一個 \(n^{\prime} \times m\) 的矩陣,然后小美每一次將這個矩陣上下翻轉后接到原矩陣的下方。小美重復這個過程若干次 (甚至可能是0次, 也就是沒有進 行過這一操作) , 然后將操作后的矩陣交給小團。 小團想知道, 小美一開始寫下的矩陣是什么。因為小美可能有多種一開始的矩陣,小團想得到最小的矩陣 (這里的最小指矩陣即 \(n^{\prime} \times m^{\text { })}\)的面積最小 。

//如果行數是奇數,那么一定是原矩陣
//如果行數是偶數,不斷除以2,做檢驗,找到最小的滿足要求的矩陣
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m;
ll a[100005][55];


bool judge(ll n){
	if(n&1) return false;
	ll midle=n>>1;
	for(ll i=1;i<=midle;i++){
		for(ll j=1;j<=m;j++){
			if(a[i][j]!=a[2*midle-i+1][j]) return false;
		}
	}
	return true;
}


int main(){
	cin>>n>>m;
	for(ll i=1;i<=n;i++){
		for(ll j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	if(n&1) ;
	else {
	  while(n){
		if(judge(n)) n>>=1;
		else break;
	  }	
	}
	
	for(ll i=1;i<=n;i++){
		for(ll j=1;j<=m;j++){
			if(j==1) ;else cout<<" ";
			cout<<a[i][j];
		}
		puts("");
	}
		
	return 0;
}

4

小團和小美正在密室中解密。他們現在來到了一個新的關卡面前。這一關是一個配合關卡,有n個巨大的齒輪擺成一排,每個齒輪上有兩個按鈕和按順時針排成一環的26個大寫字母。在齒輪的最上面有一個孔,透過孔可以看到齒輪最上方的字母。

小團發現,每次他可以按住一個齒輪的一個按鈕,小美就可以順時針移動這個齒輪,使得孔里看到的字母變為其對應的下一個字母(比如A變為B,Y變為Z),並且如果小團按下的第一個按鈕,則齒輪與上一個齒輪咬合,上一個齒輪的能看見的字母會變為其減1的字母(即B變為A,Z變為Y),進行這個操作的時候,不會影響上一個齒輪之前的齒輪。如果小團按下的第二個按鈕,則下一個齒輪能看見的字母會變為其減1的字母,同樣,這個操作不會影響下一個齒輪之后的齒輪。

如果這個齒輪是第一個齒輪,或者上一個齒輪的字母為A,小團按下第一個按鈕后小美將不能移動。同理,如果這個齒輪是最后一個齒輪,或者下一個齒輪的字母為A,小團按下第二個按鈕后小美將不能移動。

如果該齒輪上的字母是Z,該齒輪按下按鈕后也不能移動。這個齒輪組的某個狀態所組成的字符串將會是通關密碼。

現在,小團想計算出可以變化出多少種齒輪的組合,他會依據這個數字來計算是否可以暴力計算出密碼。請你幫助他。

如果該齒輪上的字母是Z,該齒輪按下按鈕后也不能移動。

這句話我認為應該去掉

思路1:(計算顯式公式)

思路:

先轉化,抽象出數學語言:
給你一個長度n的數組\(a,\quad 1\leq a_i\leq 26\),你每次可以進行如下2個操作,但得保證操作后的數組\(a,\quad 1\leq a_i\leq 26\)
(1)\(a_{i}++, a_{i+1}--\)
(2)\(a_{i}--, a_{i+1}++\)
你可以進行任意次合法操作,問你狀態數目

emm這其實演示的是由某一個解得到【total:=\sum{char-'A'+1}的n個和數且和數在[1,26]的Composition的全部解】的過程。

要求的就是total:=\sum{char-'A'+1}分解為n個和數且和數在[1,26]范圍內的Composition方案數目

下面的代碼利用了公式
n分解為k個和數且和數在[1,r]范圍內的Composition方案數目

\[\left[z^{n}\right]\left(z+z^{2}+\ldots z^{r}\right)^{k}=\left[z^{n}\right]\left(z \frac{1-z^{r}}{1-z}\right)^{k}=\sum_{j}(-1)^{j}\left(\begin{array}{c} k \\ j \end{array}\right)\left(\begin{array}{c} n-r j-1 \\ k-1 \end{array}\right) \]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mod 998244353
string s;
ll n;
ll b[6010][6010];
void GetBinomial(){
    for(int i=0;i<=6005;i++) b[i][0]=1;
    for(int  i=1;i<=6005;i++){
        for(int  j=1;j<=i;j++){
            b[i][j]=(b[i-1][j]+b[i-1][j-1])%mod;
        }
    }
} 
ll Comp_n_summands_EachRange_1R(ll n,ll k, ll r){//n的k個和數且和數在[1,r]的Composition的方案數目 
    ll sum=0;
    if(r<=0) return 0;
    if(k<=0) return 0;
    for(ll j=0;j<=k&&j<=(n-k)/r ;j++){
        if(j&1){
            sum=(sum+mod-b[k][j]*b[n-r*j-1][k-1]%mod)%mod;
        }
        else {
            sum=(sum+b[k][j]*b[n-r*j-1][k-1]%mod)%mod;
        }
    }
    return sum;
}
int main(){
    GetBinomial();
    while(cin>>n>>s){
        ll total=0;
        for(ll i=0;i<n;i++)  total+=(s[i]-'A'+1);
        //cout<<"total"<<total<<endl; 
        cout<<Comp_n_summands_EachRange_1R(total,n,26)<<endl;
    }
    return 0;
}
思路2: (容易想到的dp)

我還找到這個

只看紅框內的文字,講得很明白了
寫成dp就是:設dp_l[j][n]表示把n分解成j部分的Composition,每個和數屬於[1,l]
那么狀態轉移方程是(盡可能取合理值)

\[dp_l[j][n]=\sum_{k\in [1,l]}dp_l[j-1][n-k] \]

代碼如下

#include<bits/stdc++.h>
#define ll long long
ll dp[105][5005];
using namespace std;
ll SlideWindow=0;
int main(){
    int n;
    string s;
    ll mod=998244353;
    while(cin>>n>>s){
        memset(dp,0,sizeof(dp));
        int cnt=0;
        for(int i=0;i<n;i++) cnt+=(s[i]-'A'+1); 


        dp[0][0]=1;
        for(int i=1;i<=n;i++){
            SlideWindow=0;
            for(int j=i;j<=26*i;j++){
                if(j==i){
                    for(int k=0;k<=j-1;k++) SlideWindow=(SlideWindow+dp[i-1][k])%mod;
                    dp[i][j]=SlideWindow;
                }
                else{
                    if(j>=27){
                        SlideWindow=(SlideWindow+dp[i-1][j-1]-dp[i-1][j-26-1]+mod)%mod;
                        dp[i][j]=SlideWindow;
                    }
                    else{
                        SlideWindow=(SlideWindow+dp[i-1][j-1]+mod)%mod;
                        dp[i][j]=SlideWindow;
                    }

                }
            }
        }
        cout<<dp[n][cnt]<<endl;
    }
}

參考

Compositions of n with parts in a set,很全,講得很明白
【讀書筆記】有序分拆和無序分拆的結論速覽


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM