【HDU 6981】Rise in Price 搜索+剪枝
【题意】
给出一个 n × n n\times n n×n 的矩阵,每个位置有两个权值 a i j , b i j a_{ij},b_{ij} aij,bij ,从 ( 1 , 1 ) (1,1) (1,1) 出发,每次只能向右或者向下,问走到 ( n , n ) (n,n) (n,n) 的所有路径中, ( ∑ a i j ) × ( ∑ b i j ) (\sum a_{ij})\times (\sum b_{ij}) (∑aij)×(∑bij) 最大可能是多少。
n ≤ 100 n\le 100 n≤100 保证 a i j , b i j a_{ij},b_{ij} aij,bij 是在 [ 1 , 1 0 6 ] [1,10^6] [1,106] 里均匀随机生成的。
【吐槽】
比赛的时候没有想到标算的单调队列。
但是发现题目说了数据是随机的,于是试了一下搜索加剪枝,试了两个估价函数之后就搜过了。
而且貌似比其他队伍的标算都快(捂脸)
【分析】
首先容易想到一个大暴力, d f s dfs dfs 枚举每条路径。
然后思考最优性剪枝:如果在某个位置我们能估价未来的 ∑ a \sum a ∑a 与 ∑ b \sum b ∑b ,并且估计的值的贡献大于实际能达到的贡献,就能用估计值去检验,如果估计值还达不到已经搜到过的最优解了,直接剪枝回溯。
于是思考如何估价。
【估价函数1】
假设当前已经经过路径的 ∑ a = S a \sum a = S_a ∑a=Sa ,未来路径的 ∑ a = A \sum a =A ∑a=A。已经经过路径的 ∑ b = S b \sum b = S_b ∑b=Sb ,未来路径的 ∑ b = B \sum b =B ∑b=B。
那么总贡献应该是 S a S b + A S b + B S a + A B S_aS_b+AS_b+BS_a+AB SaSb+ASb+BSa+AB
如果我们用未来的 A m a x A_{max} Amax 和 B m a x B_{max} Bmax (尽管这两个值几乎不可能出现在同一条路径方案上)来估计这个未来的总贡献,这个估计值一定是不低于实际值的,用来剪枝是合理的。
所以分别预处理一下每个位置之后的路径的 A m a x A_{max} Amax 和 B m a x B_{max} Bmax ,就可以 O ( 1 ) O(1) O(1) 计算估价函数。
加上这个估价,大概能秒出 n = 30 n=30 n=30 的数据。
但是过不了这个题。
【估价函数2】
同样,假设当前已经经过路径的 ∑ a = S a \sum a = S_a ∑a=Sa ,未来路径的 ∑ a = A \sum a =A ∑a=A。已经经过路径的 ∑ b = S b \sum b = S_b ∑b=Sb ,未来路径的 ∑ b = B \sum b =B ∑b=B。
那么总贡献应该是 S a S b + A S b + B S a + A B S_aS_b+AS_b+BS_a+AB SaSb+ASb+BSa+AB
上一个估价函数不太行,是因为估价中用到的 A m a x A_{max} Amax 和 B m a x B_{max} Bmax 往往属于不同的路径,用它们估值,偏离实际情况较大。
我们看看能不能用一条路径上的 A , B A,B A,B 值进行估价。
我们令未来某条路径 i i i 的所有权值之和为 C i = A i + B i C_i=A_i+B_i Ci=Ai+Bi 。
我们不妨找到 C m a x C_{max} Cmax 这是未来路径的双权值和的最大值。
试想一下,如果我们可以自由分配未来路径上的 ∑ a i \sum a_i ∑ai 和 ∑ b i \sum b_i ∑bi ,怎么样才能取得最优值?
可以假设分配之后的
∑
a
i
=
A
,
∑
b
i
=
B
\sum a_i = A,\sum b_i=B
∑ai=A,∑bi=B ,
A
+
B
=
C
m
a
x
A+B = C_{max}
A+B=Cmax ,然后带入总贡献
S
a
S
b
+
A
S
b
+
B
S
a
+
A
B
S_aS_b+AS_b+BS_a+AB
SaSb+ASb+BSa+AB 计算,发现可以代出一个关于
B
B
B 的二次方程,找到当贡献最大时:
B
=
S
a
−
S
b
+
C
m
a
x
2
A
=
S
b
−
S
a
+
C
m
a
x
2
B= \frac{S_a-S_b+C_{max}}{2}\\ A= \frac{S_b-S_a+C_{max}}{2}
B=2Sa−Sb+CmaxA=2Sb−Sa+Cmax
那么我们就可以用这个
A
,
B
A,B
A,B 去估计未来值,而且这个估计值是我们在一条路径的限制之内调整到的最大值,因此也不低于实际值,用来剪枝是能保证正确性的。
而且目测这个估值会比上个方案更接近实际情况一点。
于是按这个写了一下,预处理出了每个位置的 C m a x C_{max} Cmax ,就能秒出 n = 100 n=100 n=100 的情况了,似乎踩了好多标算,至少在比赛里这个做法时间是最快的。
【代码】
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAXN 101
#define LL long long
using namespace std;
int n,m;
int a[MAXN][MAXN],b[MAXN][MAXN];
int dpc[MAXN][MAXN];
LL ans=0;
void dfs(int x,int y,int suma,int sumb)
{
if(x==n&&y==n)
{
ans=max(ans,1ll*suma*sumb);
return;
}
if(x<n && 1ll*(suma+dpc[x+1][y] - (suma-sumb+dpc[x+1][y])/2)*(sumb+(suma-sumb+dpc[x+1][y])/2) > ans)
dfs(x+1,y,suma+a[x+1][y],sumb+b[x+1][y]);
if(y<n && 1ll*(suma+dpc[x][y+1] - (suma-sumb+dpc[x][y+1])/2)*(sumb+(suma-sumb+dpc[x][y+1])/2) > ans)
dfs(x,y+1,suma+a[x][y+1],sumb+b[x][y+1]);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&b[i][j]);
for(int i=n;i>=1;i--)
for(int j=n;j>=1;j--)
{
dpc[i][j]=a[i][j]+b[i][j];
if(i<n)
dpc[i][j]=max(dpc[i][j],a[i][j]+b[i][j]+dpc[i+1][j]);
if(j<n)
dpc[i][j]=max(dpc[i][j],a[i][j]+b[i][j]+dpc[i][j+1]);
}
dfs(1,1,a[1][1],b[1][1]);
cout<<ans<<endl;
}
return 0;
}