线段树优化dp
题目大意
有一个 n行m列的矩阵,每个格子有一花费Tij,要求在每行选出恰好一个格子,使得这n个格子的Tij 之和最小,每个格子还有一权值Wij.对于相邻两行选择的格子(i,j1)(i-1,j2)要求abs(j1-j2)<=W(i,j1)+W(i-1,j2),多组数据T(2.5S)
Sample Input
1
3 5
9 5 3 8 7
8 2 6 8 9
1 9 7 8 6
0 1 0 1 2
1 0 2 1 1
0 2 1 0 2
Sample Output
10
数据范围与约定
对于 20% 的数据,保证数据,保证 T = 1
对于另 30% 的数据,保证 m < = 500 ;
对于 100% 的数据,保证 T<= 5, 2≤n≤100 ,1 <=m<=5000,0<= Tij 、Wij≤100000
这个的dp方程恨好推啦,那么前50分就可以这样拿
#include<bits/stdc++.h> #define INF 2100000001 #define N 103 #define M 5003 #define re register using namespace std; int read() { int x=0,f=1;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } int c[N][M],w[N][M],dp[N][M]; int main() { // freopen("elect.in","r",stdin); // freopen("elect.out","w",stdout); int T=read(); int n=read(),m=read(); while(T--) { for(re int i=1;i<=n;++i) for(re int j=1;j<=m;++j) c[i][j]=read(); for(re int i=1;i<=n;++i) for(re int j=1;j<=m;++j) w[i][j]=read(); for(re int i=2;i<=n;++i) for(re int j=1;j<=m;++j) dp[i][j]=INF; for(re int i=1;i<=m;++i) dp[1][i]=c[1][i]; for(re int i=2;i<=n;++i) { for(re int j=1;j<=m;++j)//now { for(re int k=1;k<=m;++k)//last { if(w[i][j]+w[i-1][k]>=abs(j-k)) dp[i][j]=min(dp[i][j],dp[i-1][k]+c[i][j]); } } } int ans=INF; for(re int i=1;i<=m;++i) ans=min(ans,dp[n][i]); printf("%d\n",ans); } } /* 1 3 5 9 5 3 8 7 8 2 6 8 9 1 9 7 8 6 0 1 0 1 2 1 0 2 1 1 0 2 1 0 2 */
当然,50分的时间是O(T*m^2*n)的,肯定过不了,考虑优化。
我们发现,每一层的dp都是从上一层转移过来,而可以转移的条件又比较特殊,如果是一段连续的区间我们就可以考虑单调队列优化或者斜率优化。但不幸的是,区间不是连续的,而是与W值有关。
即|j-k|<=w[i][j]+w[i-1][k],我们可能看到绝对值,思考过分类讨论,但行不通。
但看到绝对值,我们还可以想到“距离”,那么把j左右都延伸w[i][j]的距离,把k左右都延伸w[i-1][k]的距离,若两者有交集则说明可以转移,那么对于每一个j,其都有覆盖的一段区间,若上一层的k和这一层覆盖的区间有交集,说明可以转移,而转移的也就是共同区间的最小值,也可以说就是j可以到的区间中的最小值(这个最小值是上一层的)用线段树维护一个区间最小值就好了。
#include<bits/stdc++.h> #define INF 2100000001 #define N 103 #define M 5003 #define re register using namespace std; int read() { int x=0,f=1;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } int c[N][M],dp[N][M],minn[M<<2],flagg[M<<2],l[N][M],r[N][M]; void build(int k,int l,int r) { if(l==r){minn[k]=INF;flagg[k]=INF;return;} int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); minn[k]=min(minn[k<<1],minn[k<<1|1]); flagg[k]=min(flagg[k<<1],flagg[k<<1|1]); } void pushdown(int k) { if(flagg[k]==INF) return; minn[k<<1]=min(minn[k<<1],flagg[k]); minn[k<<1|1]=min(minn[k<<1|1],flagg[k]); flagg[k<<1]=min(flagg[k<<1],flagg[k]); flagg[k<<1|1]=min(flagg[k<<1|1],flagg[k]); flagg[k]=INF; } void modify(int k,int L,int R,int l,int r,int v) { if(L>=l&&R<=r) { minn[k]=min(minn[k],v); flagg[k]=min(flagg[k],v); return ; } pushdown(k); int mid=(L+R)>>1; if(l<=mid) modify(k<<1,L,mid,l,r,v); if(r>mid) modify(k<<1|1,mid+1,R,l,r,v); minn[k]=min(minn[k<<1],minn[k<<1|1]); } int query(int k,int L,int R,int l,int r) { int ans=INF; if(L>=l&&R<=r) return minn[k]; int mid=(L+R)>>1; pushdown(k); if(l<=mid) ans=min(ans,query(k<<1,L,mid,l,r)); if(r>mid) ans=min(ans,query(k<<1|1,mid+1,R,l,r)); return ans; } int main() { // freopen("elect.in","r",stdin); // freopen("elect.out","w",stdout); int T=read(); int n=read(),m=read(); while(T--) { memset(l,0,sizeof(l)); memset(r,0,sizeof(r)); for(re int i=1;i<=n;++i) for(re int j=1;j<=m;++j) c[i][j]=read(); for(re int i=1;i<=n;++i) for(re int j=1;j<=m;++j) { int w=read(); l[i][j]=max(1,j-w); r[i][j]=min(m,j+w); } build(1,1,m); for(re int j=1;j<=m;++j) dp[1][j]=c[1][j],modify(1,1,m,l[1][j],r[1][j],dp[1][j]); for(re int i=2;i<=n;++i) { for(re int j=1;j<=m;++j)//now { int p=query(1,1,m,l[i][j],r[i][j]);//找到最小的 dp[i][j]=p+c[i][j]; } build(1,1,m);//每一层都有一个线段树,维护上一层的最小值,因为只有i-1对i有影响 for(int j=1;j<=m;++j) modify(1,1,m,l[i][j],r[i][j],dp[i][j]);//把这一层的值插入到线段树中 } int ans=INF; for(re int i=1;i<=m;++i) ans=min(ans,dp[n][i]); printf("%d\n",ans); } } /* 1 3 5 9 5 3 8 7 8 2 6 8 9 1 9 7 8 6 0 1 0 1 2 1 0 2 1 1 0 2 1 0 2 1 3 5 9 3 8 6 4 9 4 6 9 1 8 7 5 9 1 0 2 1 0 1 1 0 0 0 0 1 1 2 0 0 */
这个思想还是很重要的~