编程练习 美团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]范围内的方案数目是
n做Compostion且最大的和数是\(r(r\geq 1)\)的方案数目是
n做Compostion且最大的和数在\([Left,Right]\)的方案数目是
//直接出击,算那个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}\)的形式想到
//我拿广义斐波那契数又写了一份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\) 的形式想到
设dp[n][r]是把n做Composition且和数属于[1,r]范围的方案数目
转移方程(尽可能取合理值)
边界条件是
#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方案数目
#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]
那么状态转移方程是(尽可能取合理值)
代码如下
#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,很全,讲得很明白
【读书笔记】有序分拆和无序分拆的结论速览