E - The Journey of Geor Autumn
分析:
-
线性DP+前缀和
令dp[i]表示前i个数的方案数贡献
d p [ i ] = ∑ j = 1 k d p [ i − j ] ∗ ( j − 1 i − 1 ) ∗ f a c [ j − 1 ] dp[i]=\sum_{j=1}^k dp[i-j]*(_{j-1}^{i-1})*fac[j-1] dp[i]=j=1∑kdp[i−j]∗(j−1i−1)∗fac[j−1]
-
对于dp[i],首先找到最小的元素x,那么x必然会出现在前k个
枚举这k个位置,每个位置都是将len 分为了两部分,x在位置j,则前j-1个数的贡献为 ( j − 1 i − 1 ) ∗ f a c [ j − 1 ] (_{j-1}^{i-1})*fac[j-1] (j−1i−1)∗fac[j−1]
再乘上子状态(即后i-j个数)的贡献
-
若每次都枚举k次,必然超时,要用前缀和优化
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e7+5, mo=998244353;
int ksm(int a,int b,int p=mo)
{
int res=1;
while(b)
{
if(b&1) res=res*a%p;
b>>=1; a=a*a%p;
}
return res;
}
int fac[N], inv[N];
void init(int n)
{
fac[0]=inv[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mo;
inv[n]=ksm(fac[n],mo-2);
for(int i=n-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mo;
}
int f[N];
int C(int n,int m)
{
return fac[n]*inv[m]%mo*inv[n-m]%mo;
}
void solve()
{
int n,k;
cin>>n>>k;
init(n);
if(k>=n) { cout<<fac[n]<<"\n"; return; }
for(int i=1;i<=k;i++) f[i]=fac[i];
int s=k;
for(int i=k+1;i<=n;i++)
{
f[i]=s*fac[i-1]%mo;
s=(s-f[i-k]*inv[i-k]%mo+f[i]*inv[i]%mo+mo)%mo;
}
cout<<f[n]<<"\n";
}
signed main()
{
int T=1;
//cin>>T;
while(T--) solve();
return 0;
}
D - Progressions Covering
分析:
-
同上题 妙妙前缀和
-
开始从前考虑,发现行不通
要从后往前考虑,同第一题 无后效性
首先讨论b[n],贡献为: ( b [ n ] + k − 1 ) / k (b[n]+k-1)/k (b[n]+k−1)/k
然后,把 1~k-1 加到前面相对应的位置即可
-
若直接枚举加,显然超时,所以要用前缀和来维护
#include <bits/stdc++.h>
#define int long long
#define Pa pair<int,int>
using namespace std;
const int N=3e5+5;
int b[N],s[N],d[N];
void solve()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
reverse(b+1,b+1+n); //这样方便写
int s=0, cnt=0, ans=0;
for(int i=1;i<=n;i++)
{
s-=cnt; //cnt表示前k个数的贡献总合,s 表示前缀和
b[i]-=s;
if(b[i]>0)
{
if(i+k-1<=n)
{
d[i]=(b[i]+k-1)/k;
s+=d[i]*k;
cnt+=d[i];
ans+=d[i];
}
else
{
d[i]=(b[i]+(n-i+1)-1)/(n-i+1);
s+=d[i]*(n-i+1);
cnt+=d[i];
ans+=d[i];
}
}
if(i>k) cnt-=d[i-k];
}
cout<<ans<<endl;
}
signed main()
{
int T=1;
//cin>>T;
while(T--) solve();
}