洛咕11月月賽部分題解 By cellur925
聽說是你谷史上最水月賽?我不聽我最菜
T1:終於結束的起點 月天歌名好評
給你一個模數 \(M\),請你求出最小的
\(n > 0\),使得\(fib(n)\) \(mod\) \(m=0\),\(fib(n+1)\) \(mod\) \(m=1\)。
數學題,開始還想打表驗證下,但是我不會告訴你我打表的時候沒有很及時地取膜,然後中間有結果溢位,耽誤了很長時間,企圖找了很久規律。結果發現暴力就能過。hhh。
這個故事告訴我們要及時取膜!
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; ll m; ll f[7000001]; int main() { scanf("%lld",&m); f[0]=0;f[1]=1; for(ll i=2;;i++) { (f[i]=f[i-1]+f[i-2])%=m; if(f[i-1]==0&&f[i]==1) { printf("%lld\n",i-1); return 0; } } return 0; }
T2:跳跳!
對於\(20\)%的資料,直接全排列列舉。
對於\(50\)%的資料,開始還想狀壓一下,結果交後發現超時,是我自己傻了!!企圖用\(O(2^n*n*2)\)過\(n<=20\)!!!其實是四億hhh。
本來還覺得自己想不出正解了,但是\(Mathison\)巨佬說他\(O(n)\)寫出了...然後自己又冷靜分析了下,發現是個非常簡單的貪心==。
可以重構一個序列:先對原序列排序,每次先選擇剩餘序列中的最大值,再選最小值直到原序列為空。這個序列就是最優排列。
最後交的時候求穩分別交了全排列/狀壓/yy的正解,結果狀壓被卡T掉了30分:(。可能臉太黑了
這個故事告訴我們,一定算好時間複雜度
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; int n,fake,tot; ll ans,f[1200000][25]; int h[1000],a[100],b[100]; int seq[1000],tmp[1000]; void review() { ll qwq=0; for(int i=1;i<=n;i++) qwq+=1ll*(h[a[i-1]]-h[a[i]])*(h[a[i-1]]-h[a[i]]); ans=max(ans,qwq); } bool cmp(int a,int b) { return a>b; } void dfs(int x) { if(x==n+1) { review(); return ; } for(int i=1;i<=n;i++) if(!b[i]) { b[i]=1; a[x]=i; dfs(x+1); b[i]=0; a[x]=0; } } int main() { scanf("%d",&n); if(n<=10) { for(int i=1;i<=n;i++) scanf("%d",&h[i]); dfs(1); printf("%lld\n",ans); return 0; } /* if(n<=20) { for(int i=0;i<n;i++) scanf("%d",&h[i]); fake=(1<<n)-1; for(int i=0;i<n;i++) f[(1<<i)][i]=1ll*h[i]*h[i]; for(int i=0;i<=fake;i++) for(int j=0;j<n;j++)//列舉當前在哪 { if(!(i&(1<<j))) continue; for(int k=0;k<n;k++)//列舉上一步在哪 { if(k==j||!(i&(1<<k))) continue; f[i][j]=max(f[i][j],f[i^(1<<j)][k]+1ll*(h[j]-h[k])*(h[j]-h[k])); } } for(int i=0;i<n;i++) ans=max(ans,f[fake][i]); printf("%lld\n",ans); return 0; }*/ else { for(int i=1;i<=n;i++) scanf("%d",&seq[i]); seq[++n]=0; sort(seq+1,seq+1+n,cmp); int i=1,j=n; while(tot<n) { tmp[++tot]=seq[j],j--; tmp[++tot]=seq[i],i++; } for(int i=2;i<=n;i++) ans+=1ll*(tmp[i]-tmp[i-1])*(tmp[i]-tmp[i-1]); printf("%lld\n",ans); } return 0; }
T3:咕咕咕
\(n<=20\)的資料範圍讓我從頭到尾都以為是狀壓,並在這個思路上死磕了很久...最後手碼前三個點搞到了30分hhh。
其實是可以想出一個\(O(4^n)\)的狀壓暴力的:列舉子集。首先我們確定對於狀態\(i\),它的子集\(j\)一定在\(0\)~\(i\)中,若\(i\)&\(j\)==\(j\),那麼可以確定\(j\)是\(i\)的子集。
沒有寫=w=,下面是lwz dalao的程式==
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define MAXN 2147483647
#define ll long long
using namespace std;
const int mod=998244353;
int n,m;
ll sum;
int a[2001000];
ll f[2001000],p[20010000];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
char s[30];
scanf("%s",s+1);
int ret=0;
for(int j=1;j<=n;j++)
ret+=(s[j]-'0')*(1<<n-j);
int x;
scanf("%d",&x);
a[ret]=x%mod;
}
f[0]=a[0];//f記錄到狀態的花費
p[0]=1;//p記錄此狀態出現的次數
for(int i=1;i<(1<<n);i++)
{
for(int j=0;j<i;j++)
if((i&j)==j){
p[i]+=p[j];//記錄自己的次數
(f[i]+=f[j])%=mod;//加上子集的花費
}
(f[i]+=a[i]%mod*p[i]%mod)%=mod;//加上自己
}
printf("%lld\n",f[(1<<n)-1]%mod);
return 0;
}
正解其實是..數學題?我們完全拋開狀壓dp的限制,考慮最後的答案其實是可以統計每個帶權狀態所有可能出現的次數,再乘上最後的權值就行了。
而如何算出現的次數?考慮乘法原理:把這些狀態看成中間狀態,分別算從0到它的次數、它到滿狀態的次數,二者相乘。
"具有相同1/0個數的狀態出現次數一樣”,開始並不能發現這個性質:(。其實出現01的位置並不重要重要的是數量,所以我們可以預處理出每個方案的出現次數。
設\(f[i]\)為填\(i\)個1的方案數,設最後一次填了\(j\)個,那麼\(f[i]+=f[i-j]*C(i,j)\)。其中\(j\)從1到\(i\)迴圈。
這個故事告訴我們,不要一條路走到黑,有時你覺得十分正確的演算法方向也可能不是正解,要靈活啊\(qwq\)。而且要多膜膜,沒壞處的。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll moder=998244353;
int n,m;
char op[100];
ll val,ans,C[50][50],f[50];
void prework()
{
C[0][0]=1;C[1][1]=1;
for(int i=1;i<=20;i++) C[i][0]=1,C[i][i]=1;
for(int i=1;i<=20;i++)
for(int j=1;j<=i;j++)
(C[i][j]=C[i-1][j]+C[i-1][j-1])%=moder;
f[0]=1;
for(int i=1;i<=20;i++)
for(int j=1;j<=i;j++)
(f[i]=f[i]+f[i-j]*C[i][j])%=moder;
}
int main()
{
scanf("%d%d",&n,&m);
prework();
for(int i=1;i<=m;i++)
{
scanf("%s",op);
int cnt=0;
for(int j=0;j<n;j++)
if(op[j]=='1') cnt++;
scanf("%lld",&val);
(ans+=val*f[cnt]%moder*f[n-cnt]%moder)%=moder;
}
printf("%lld\n",ans);
return 0;
}
T4:不圍棋
本想搞到10分的,我輸出的是1 1 -1 -1;其實應該直接輸出-1 -1.因為放到\((1,1)\)後,自己就沒有“氣”了。嚶。