P4767 [IOI2000]邮局 二维四边形不等式
P4767 [IOI2000]邮局 二维四边形不等式
二维的四边形不等式要比一维难证一些,事实上,在真正的考场上,一般都是打表找规律。
这个题的 dp 方程是 \(f_{i,j}=\min\limits_{i-1\le k\le j-1}\{f_{i-1,k}+w(k+1,j) \}\)
其中 \(f_{i,j}\) 为前 \(j\) 个村庄里设置 \(i\) 个邮局的最优解是多少。如果令 \(d_i\) 为村庄 \(i\) 的位置,\(sum_i\) 为 \(\sum_{j=1}^id_j\) ,那么 \(w(i,j)=(2mid-i+1-j)d_{mid}+(sum_{i-1}+sum_j-2sum_{mid})\) ,其中 \(mid=\left\lfloor \frac{i+j}2 \right\rfloor\)
这里给大家提供一个暴力打表程序:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 10010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int V,P,d[N],sum[N],f[N][N],g[N][N];
inline int w(int i,int j){
int mid=(i+j)>>1;
return (2*mid-i+1-j)*d[mid]+(sum[i-1]+sum[j]-2*sum[mid]);
}
int main(){
//freopen("my.in","r",stdin);
//freopen("my.out","w",stdout);
read(V);read(P);
for(int i=1;i<=V;i++) read(d[i]),sum[i]=sum[i-1]+d[i];
memset(f,INF,sizeof(f));f[0][0]=0;
for(int i=1;i<=P;i++){
for(int j=i;j<=V;j++){
for(int k=i-1;k<=j-1;k++){
// f[i][j]=min(f[i][j],f[i-1][k]+w(k+1,j));
if(f[i][j]>f[i-1][k]+w(k+1,j)){
f[i][j]=f[i-1][k]+w(k+1,j);
g[i][j]=k;
}
}
printf("i:%d j:%d f:%d\n",i,j,f[i][j]);
// printf("i:%d j:%d g:%d\n",i,j,g[i][j]);
}
}
for(int i=1;i<=P;i++){
for(int j=1;j<=V;j++) printf("%d ",g[i][j]);
printf("\n");
}
printf("%d",f[P][V]);
return 0;
}
正解代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 3010
#define M 310
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Max(T a,T b){
return a<b?b:a;
}
ll V,P,d[N],sum[N],f[M][N],g[M][N];
inline int w(int i,int j){
int mid=(i+j)>>1;
return (2*mid-i+1-j)*d[mid]+(sum[i-1]+sum[j]-2*sum[mid]);
}
int main(){
//freopen("my.in","r",stdin);
//freopen("my.out","w",stdout);
read(V);read(P);
for(int i=1;i<=V;i++) read(d[i]),sum[i]=sum[i-1]+d[i];
memset(f,INF,sizeof(f));f[0][0]=0;
for(int i=1;i<=P;i++){
g[i][V+1]=V-1;
for(int j=V;j>=i;j--){
for(int k=g[i-1][j];k<=g[i][j+1];k++){//g[i][j+1]
// f[i][j]=min(f[i][j],f[i-1][k]+w(k+1,j));
if(f[i][j]>f[i-1][k]+w(k+1,j)){
f[i][j]=f[i-1][k]+w(k+1,j);
g[i][j]=k;
}
}
// printf("i:%d j:%d f:%d\n",i,j,f[i][j]);
// printf("i:%d j:%d g:%d\n",i,j,g[i][j]);
}
}
// for(int i=1;i<=P;i++){
// for(int j=1;j<=V;j++) printf("%d ",g[i][j]);
// printf("\n");
// }
printf("%lld",f[P][V]);
return 0;
}
可以看出,正解和暴力没有什么太大区别,对于一维的决策单调性,虽然好证,但是打起来麻烦,二维的难证,但是打起来不麻烦,只需要注意一下是倒序枚举就可以了。
但是就实战而言,两者最好都打表证明。随意后者自然是更喜闻乐见的。