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