ZR 集训 Day14 - 动态规划与动态规划常见优化方法
前言
今天转 B 班了,题目比较神秘。
P2150 [NOI2015] 寿司晚宴
考虑枚举两个人所选数字的质因子集合。但是 $500$ 以内的素数很多,这时想到根号分治,$500$ 以内的数只会有一个大于 $22$ 的质因数,所以我们把这个质因数单拎出来。$22$ 以内的素数只有 $8$ 个,很好状压。我们把大质因数相等的数放在一起,对于这些数做一个 DP,用两个数组分别表示两个人谁选这些大素数。记得要减去两人都不选的情况。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=505,M=305;
int n,mod,ans;
int dp[M][M],f1[M][M],f2[M][M];
int prm[]={2,3,5,7,11,13,17,19};
struct node{
int p,k;
bool operator<(const node b)const{
return p<b.p;
}
}a[N];
signed main(){
cin>>n>>mod;
for(int i=2;i<=n;i++){
int x=i;
for(int j=0;j<8;j++){
if(x%prm[j]==0){
a[i].k+=(1<<j);
while(x%prm[j]==0) x/=prm[j];
}
}
a[i].p=x;
}
sort(a+2,a+1+n);
dp[0][0]=1;
for(int i=2;i<=n;i++){
if(a[i].p==1||a[i].p!=a[i-1].p||i==2){
memcpy(f1,dp,sizeof dp);
memcpy(f2,dp,sizeof dp);
}
for(int s1=(1<<8)-1;s1>=0;s1--) for(int s2=(1<<8)-1;s2>=0;s2--){
if(s1&s2) continue;
if((s1&a[i].k)==0) (f1[s1][s2|a[i].k]+=f1[s1][s2])%=mod;
if((s2&a[i].k)==0) (f2[s1|a[i].k][s2]+=f2[s1][s2])%=mod;
}
if(a[i].p==1||a[i].p!=a[i+1].p||i==n){
for(int s1=(1<<8)-1;s1>=0;s1--) for(int s2=(1<<8)-1;s2>=0;s2--){
if(s1&s2) continue;
(dp[s1][s2]=(f1[s1][s2]+f2[s1][s2]-dp[s1][s2])%mod+mod)%=mod;
}
}
}
for(int s1=(1<<8)-1;s1>=0;s1--) for(int s2=(1<<8)-1;s2>=0;s2--){
if(s1&s2) continue;
(ans+=dp[s1][s2])%=mod;
}
cout<<ans;
return 0;
}P6246 [IOI 2000] 邮局 加强版 加强版
WQS 二分+决策单调性优化。
什么是 WQS 二分?
我们设 $g(x)$ 表示建立 $x$ 个邮局所需的代价,发现该函数满足斜率单调,也就是 $g(x)-g(x-1)>=g(x+1)-g(x)$(这里可以感性理解一下,建 $2$ 个邮局相较于建 $1$ 个邮局代价的变化肯定不少于建 $100$ 个邮局相较于建 $99$ 个邮局代价的变化)。
这是我们先不考虑邮局数量的限制,并引入一个常数 $C$,表示新建一个邮局的代价,就可以得到新的一维 DP 式子:
其中 $w(i,j)$ 表示在区间 $[i,j]$ 内新建一个邮局的最小代价。
那此时对于一个 $C$ 我们做 DP,会发现:当 $C$ 越大时,最优解建的邮局越少;当 $C$ 越小时,最优解建的邮局越多(这个感性理解一下还是很容易的)。此时我们不妨二分 $C$,找到最大的 $C$ 使得最优策略刚好建了 $m$ 个邮局,那答案就应该是 $dp_n-C\times m$。
以上就是 WQS 二分的过程了,相当于我们虚构了一个常数出来辅助我们。注意它的使用范围是在原函数是凸函数的情况下。
那么以上就是解决这道题的主要步骤了,对于二分的 check,我们需要对于当前的 $C$ 写一个 DP,不难发现其满足决策单调性,用单调队列+二分优化即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+5;
int n,m;
int a[N],dp[N],sum[N],cnt[N],q[N],opt[N];
int w(int l,int r){
int mid=(l+r)>>1;
return (mid-l+1)*a[mid]-(sum[mid]-sum[l-1])+(sum[r]-sum[mid])-(r-mid)*a[mid];
}
int calc(int l,int r,int c){
return dp[l]+w(l+1,r)+c;
}
int bound(int x,int y,int c){
int l=y,r=n,res=y-1;
while(l<=r){
int mid=(l+r)>>1;
if(calc(x,mid,c)>=calc(y,mid,c)) r=mid-1;
else res=mid,l=mid+1;
}
return res;
}
int check(int c){
memset(dp,0,sizeof dp);
memset(q,0,sizeof q);
memset(opt,0,sizeof opt);
int l=1,r=1;
for(int i=1;i<=n;i++){
while(l<r&&opt[l]<i) l++;
dp[i]=calc(q[l],i,c),cnt[i]=cnt[q[l]]+1;
while(l<r&&opt[r-1]>=bound(q[r],i,c)) r--;
opt[r]=bound(q[r],i,c),q[++r]=i;
}
return cnt[n];
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",a+i);
sort(a+1,a+1+n);
for(int i=1;i<=n;i++) sum[i]=a[i]+sum[i-1];
int l=0,r=1e9,ans;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)>=m) l=mid+1,ans=dp[n]-m*mid;
else r=mid-1;
}
printf("%lld",ans);
return 0;
}


Comments NOTHING