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)

關鍵是由\(C_A(x)=\frac{1}{1-x-x^{2}-\cdots-x^{\ell}}\)的形式想到
//我拿廣義斐波那契數又寫了一份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}\) 的形式想到
設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,很全,講得很明白
【讀書筆記】有序分拆和無序分拆的結論速覽
