欢迎访问yhm138的博客园博客, 你可以通过 [RSS] 的方式持续关注博客更新

MyAvatar

yhm138

HelloWorld!

编程练习 美团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)

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

\[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没有本质区别)

关键是由 \(\quad C_{A}(x)=\frac{1}{1-x-x^{2} \ldots-x^{\ell}}\quad\) 的形式想到

\[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,很全,讲得很明白
【读书笔记】有序分拆和无序分拆的结论速览

posted @ 2021-03-07 21:43  yhm138  阅读(716)  评论(0编辑  收藏  举报