ZR 集训 Day14 - 动态规划与动态规划常见优化方法 前言 今天转 B 班了,题目比较神秘。 P2150 [NOI2015] 寿司晚宴 考虑枚举两个人所选数字的质因子集合。但是 500 以内的素数很多,这时想到根号分治,500 以内的数只会有一个大于 22 的质因数,所以我们把这个质因数单拎出来。22 以内的素数只有 8 个,很好状压。我们把大质因数相等的数放在一起,对于这些数做一个 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 二分? 我们设 g(x) 表示建立 x 个邮局所需的代价,发现该函数满足斜率单调,也就是 g(x)-g(x-1)>=g(x+1)-g(x)(这里可以感性理解一下,建 2 个邮局相较于建 1 个邮局代价的变化肯定不少于建 100 个邮局相较于建 99 个邮局代价的变化)。 这是我们先不考虑邮局数量的限制,并引入一个常数 C,表示新建一个邮局的代价,就可以得到新的一维 DP 式子: dpi=min{dpj+w(j+1,i)+C}dp_i=min{dp_j+w(j+1,i)+C} 其中 w(i,j) 表示在区间 [i,j] 内新建一个邮局的最小代价。 那此时对于一个 C 我们做 DP,会发现:当 C 越大时,最优解建的邮局越少;当 C 越小时,最优解建的邮局越多(这个感性理解一下还是很容易的)。此时我们不妨二分 C,找到最大的 C 使得最优策略刚好建了 m 个邮局,那答案就应该是 dp_n-C\times m。 以上就是 WQS 二分的过程了,相当于我们虚构了一个常数出来辅助我们。注意它的使用范围是在原函数是凸函数的情况下。 那么以上就是解决这道题的主要步骤了,对于二分的 check,我们需要对于当前的 C 写一个 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; }`
ZR 集训 Day14 - 动态规划与动态规划常见优化方法
笔记