省选集训 12 - DP 专题
[CF1810G] The Maximum Prefix
从后往前求最大前缀和是容易的,加一个数 \(a_i\),最大前缀和 \(s\leftarrow \max(0,s+a_i)\)。
所以可以从前往后定义 \(f_{i,j}\) 表示加到 \(i\),最大前缀和为 \(j\) 的概率。
当初值设为 \(f_{l+1,0}=1\) 时,长度 \(l\) 的答案即为 \(\sum_{i=0}^{l} f_{1,i}\times h_i\)。
然后发现对于不同的长度,除初值外转移都是一样的,所以可以反推答案贡献。
令 \(g_{1,i}=h_i\),则有转移 \(g_{i,j}=g_{i-1,j+1}\times p_i+g_{i-1,\max(j-1,0)}\times (1-p_i)\),\(g_{l+1,0}\) 即为长度 \(l\) 的答案。
另一种方法,我们如果令最大前缀和为 \(x\),去算最大前缀和为 \(x\) 的贡献也是容易的。
令 \(f_{i,j,0/1}\) 表示枚举到 \(i\),与目标差值为 \(j\),是否达到过目标的答案,发现对于所有 \(x\) 转移是相同的。
所以令 \(f_{0,j,0/1}=h_j\),就可以一并得出所有的答案了,代码写的是第一种方法。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5005,mod=1000000007;
int t,n,p[N],h[N],dp[N][N];
int quick_pow(int x,int y,int res=1){
for(;y;x=x*x%mod,y>>=1) if(y&1) res=res*x%mod;
return res;
}
void solve(){
cin>>n;
for(int i=1,x,y;i<=n;i++){
cin>>x>>y;
p[i]=x*quick_pow(y,mod-2)%mod;
}
for(int i=0;i<=n;i++) cin>>dp[1][i];
for(int i=2;i<=n+1;i++)
for(int j=0;j<=n-i+1;j++)
dp[i][j]=(dp[i-1][j+1]*p[i-1]+dp[i-1][max(j-1,0ll)]*(mod+1-p[i-1]))%mod;
for(int i=1;i<=n;i++) cout<<dp[i+1][0]<<(i==n?"\n":" ");
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>t;while(t--) solve();
}
[AGC061C] First Come First Serve
去重之后理论只有两种选择:选 \(a_i\),或者在 \((a_i,b_i)\) 中有数被选择时选 \(b_i\)。
考虑容斥,三种情况:选 \(a_i\) 和选 \(b_i\) 系数为 \(1\),选 \(b_i\) 但 \((a_i,b_i)\) 中没数被选择时系数为 \(-1\)。
令 \(dp_i\) 表示选完 \(a/b_{1,2,\cdots,i}\) 的答案,则系数为 \(1\) 的总贡献为 \(2\times dp_{i-1}\),系数 \(-1\) 的贡献双指针维护即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 500005
#define int long long
vector<int> v[N];
const int mod=998244353;
int n,a[N],b[N],l[N],r[N],dp[N];
signed main(){
scanf("%lld",&n),dp[0]=1;
for(int i=1;i<=n;i++) scanf("%lld%lld",a+i,b+i);
for(int i=1;i<=n;i++){
l[i]=l[i-1];
while(l[i]<n&&b[l[i]+1]<a[i]) l[i]++;
}
for(int i=1;i<=n;i++){
r[i]=r[i-1];
while(r[i]<n&&a[r[i]+1]<b[i]) r[i]++;
}
for(int i=1;i<=n;i++) v[r[i]].push_back(l[i]);
for(int i=1;i<=n;i++){
dp[i]=dp[i-1]*2%mod;
for(auto x:v[i]) dp[i]=(dp[i]-dp[x]+mod)%mod;
}
printf("%lld\n",dp[n]);
}
[ABC290Ex] Bow Meow Optimization
首先一定有序列左边的 \(\lfloor\frac{n}{2}\rfloor+\lfloor\frac{m}{2}\rfloor\) 只动物一定有 \(\lfloor\frac{n}{2}\rfloor\) 只狗和 \(\lfloor\frac{m}{2}\rfloor\) 只猫,右边同理。
如果 \(n\) 或 \(m\) 是奇数,就把权值最大的那只放中间。
正确性显然,如果左边多一只猫,将左半边最右边的猫和右半边最左边的狗交换位置一定不劣。
然后题目就变成将 \(n-(n\&1)\) 个红色的球和 \(m-(m\&1)\) 个蓝色的球填进两个长度相同的序列。
若序列中某个球前面有 \(x\) 个与之异色的球,则权值为 \(a_i/b_i\times [2x(+1)]\),是否 \(+1\) 由 \(n\) 和 \(m\) 的奇偶性决定。
然后我们将两种颜色的球混在一起按权值从大到小排序。
因为权值大的一定尽量往前放,所以令 \(f_{i,j,k}\) 表示前 \(i\) 个球在第一个序列放 \(j\) 的红球和 \(k\) 个蓝球,转移容易。
现在时间复杂度为 \(O(n^3)\),已经可以通过,但是仍然可以进行优化。
给出结论:将红色的前 \(\lfloor\frac{n}{2}\rfloor\) 个放在第一个序列,蓝色的前 \(\lfloor\frac{m}{2}\rfloor\) 个放在第二个序列,一定最优。
证明:考虑权值只有 \(0\) 和 \(1\) 的情况,如果红球有个 \(0\) 在左半边,有个 \(1\) 在右半边,交换显然不劣。
对于每个 \(x\),将大于 \(x\) 的数看成 \(1\),小于 \(x\) 的数看成 \(0\)。
原序列答案一定大于等于所有 \(x\) 的答案相加,但是对于每个 \(x\) 都有同样的策略,所以等号成立,即结论成立。
然后就可以直接排序后类归并计算结果了,复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 305
#define int long long
int n,m,ans,sua,sub,al,ar,bl,br,a[N],b[N];
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],sua+=a[i];
for(int i=1;i<=m;i++) cin>>b[i],sub+=b[i];
sort(a+1,a+n+1),sort(b+1,b+m+1);
ans+=(n&1)*sub,n-=(n&1),ans+=(m&1)*sua,m-=(m&1);
for(int k=1,i=n,j=m;k<=n+m;k++){
if(i&&a[i]>b[j]){
if(al<n/2) ans+=a[i--]*bl*2,al++;
else ans+=a[i--]*br*2,ar++;
}
else{
if(br<m/2) ans+=b[j--]*ar*2,br++;
else ans+=b[j--]*al*2,bl++;
}
}
cout<<ans<<"\n";
}

浙公网安备 33010602011771号