洛谷 P6189 - [NOI Online #1 入門組]跑步(根號分治+揹包)
阿新 • • 發佈:2020-12-03
題意:
求有多少個數列 \(x\) 滿足:
- \(\sum x_i=n\)
- \(x_i\geq x_{i+1}\)
答案對 \(p\) 取模。
。。。你確定這叫“入門”組?
一眼完全揹包問題,然而 \(n^2\) 是根本過不了的,於是我便在那裡打表找規律,結果毛用也沒有(
考慮根號分治,令 \(m=\lfloor\sqrt{n}\rfloor\)。
對於 \(i\leq m\) 跑一遍完全揹包。
對於 \(i>m\),不難發現我們頂多會選 \(m\) 個這樣的 \(i\),所以我們採取另一種 \(dp\) 狀態。
我們記 \(g_{i,j}\) 為選擇了 \(i\) 個這樣的 \(i\)
那麼有轉移方程 \(g_{i,j}=g_{i-1,j-m-1}+g_{i,j-i}\)。
稍微解釋一下這個 \(dp\) 方程,\(g_{i-1,j-m-1}\) 表示在序列末尾新增添一個 \(m+1\),\(g_{i,j-i}\) 表示將序列中所有數 \(+1\),由於我們得到的序列是單調遞減的,所以一種方案一定恰好對於一種操作序列。
最後是計算答案,列舉 \(\leq m\) 的數和是多少,以及選擇了多少個 \(>m\) 的數,可以在 \(\mathcal O(n\sqrt{n})\) 的時間內計算出答案。
總時間複雜度 \(\mathcal O(n\sqrt{n})\)
感覺有點像 atcoder 風格的題。
#include <bits/stdc++.h> using namespace std; #define fi first #define se second #define fz(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++) #define fill0(a) memset(a,0,sizeof(a)) #define fill1(a) memset(a,-1,sizeof(a)) #define fillbig(a) memset(a,63,sizeof(a)) #define pb push_back #define ppb pop_back #define mp make_pair typedef pair<int,int> pii; typedef long long ll; const int MAXN=1e5+5; const int SQRT=320; int n,m,p; int dp[MAXN],f[SQRT][MAXN]; int main(){ scanf("%d%d",&n,&p);m=(int)(sqrt(n)); dp[0]=1; for(int i=1;i<=m;i++){ for(int j=i;j<=n;j++) dp[j]=(dp[j]+dp[j-i])%p; } f[0][0]=1; for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ if(j>=i) f[i][j]=f[i][j-i]; if(j>=m+1) f[i][j]=(f[i][j]+f[i-1][j-m-1])%p; } } int ans=0; for(int i=0;i<=n;i++) for(int j=0;j<=m;j++) ans=(ans+1ll*dp[i]*f[j][n-i])%p; printf("%d\n",ans); return 0; }