P7587 [COCI 2012/2013 #1] MARS题解
[COCI 2012/2013 #1] MARS
本篇题解的思路应该是效率最高的思路。
思路
这么复杂的题目,当然要用 DP 了。
首先,我们可以依照常规设出状态
可以发现,如果对编号后的序列按下标每两个划分为一组,那么编号为
故如果对编号后的序列按下标每两个划分为一组,那么编号为
如果对编号后序列的序列按下标每
有了以上结论,可以发现,如果第
至于怎么求出取值范围,结合代码以及注释理解更佳。
转移就很好写了:
其中
时间复杂度
::::info[时间复杂度证明]
自己证。
结合代码,发现复杂度主要分为两部分:
- 求取值范围:求取值范围时每次扩大一倍,故为
O(\log_{2}{2^K}) ,即O(K) 。在乘上外层循环即为O(2^{2K}K) 。 - 转移:
\sum_{i=0}^{2^K-1} 2^K(r_{i,j}-l_{i,j}+1)=2^K\sum_{i=0}^{2^K-1} (r_{i,j}-l_{i,j}+1)
手推一下,发现有\frac{1}{2} 的位置取值范围大小为1 ,有\frac{1}{4} 的位置取值范围大小为2 ……有\frac{1}{2^K} 的位置取值范围大小为2^{K-1} ,还有第一个点的取值范围大小为0 。
所以2^K\sum_{i=0}^{2^K-1} (r_{i,j}-l_{i,j}+1)=2^K\sum_{i=1}^{K} 2^K\frac{1}{2^i}2^{i-1} =2^KK2^{K-1}=2^{2K-1}K
转移时间复杂度为O(2^{2K-1}K) 。
相加得总复杂度为
代码
跑的还是蛮快的。
#include<bits/stdc++.h>
using namespace std;
int k,a[515][515];
int dp[515][515];
int main()
{
scanf("%d",&k);
k=(1<<k);
for(int i=1;i<=k;i++)
for(int j=1;j<=k;j++)
scanf("%d",&a[i][j]);
memset(dp,127,sizeof(dp));
for(int i=1;i<=k;i++)
dp[1][i]=0;
for(int i=1;i<k;i++)
for(int j=1;j<=k;j++)
{
int l=j,r=j;
int ll=i,rr=i;
//表示[ll,rr]区间里每个数的取值范围均为[l,r](可能有部分数取值范围更小,但是不影响)
//如果当前区间为第偶数个区间,那么可以知道其前面那个区间的范围
//否则,可以知道后面那个区间的范围
while((rr/((rr-ll+1)))%2==0)
{
ll=rr-2*(rr-ll+1)+1; //区间扩大一倍
if((r/((r-l+1)))%2==0) //同理
l=r-2*(r-l+1)+1;
else r=l+2*(r-l+1)-1;
}
int yl=l,yr=r;//记录原先的l,r
if((r/((r-l+1)))%2==0)
l=r-2*(r-l+1)+1,r=yl-1;
else r=l+2*(r-l+1)-1,l=yr+1;
for(int p=l;p<=r;p++)
dp[i+1][p]=min(dp[i+1][p],dp[i][j]+a[j][p]);
}
int ans=1e9;
for(int i=1;i<=k;i++)
ans=min(ans,dp[k][i]);
cout<<ans;
return 0;
}