hdoj 3920 http://acm.hdu.edu.cn/showproblem.php?pid=3920
刚拿这道题同学告诉我能费用流 然后我看了他建图 然后我就推翻了他的图......
看到此题数据n==10 20个点 瞬间想到2^20==100万左右 然后就想到了位压缩动态规划
状态转移方程dp[i]=min(dp[i-(1<<a)-(1<<b)]+cost(a and b)); (i中二进制1(从右往左)表示消灭了该点敌人 0表示还没消灭)
不过做了一下午 这题始终没能A 一直time limit exceed
超时的不只我一个人 这题限时是1000MS 我想说的是你多开一两秒钟不行吗 好多人都超时啊你知道不?
我优化又优化都是超时啊 输入外挂都用上了还是返回绿色的字啊 其实也怪我对状态这个概念理解不深
位压缩更是只接触过2道小题
不说了 这个题根据状态转移方程 我最开始的做法是顺推从状态0到状态(1<<(n<<1))-1
经过潜入研究 要取两个点最优 必然是先取其中一个到起点最近的点 然后再取较远的点
所以我预先把所有点按照离起点距离从小到大排序
然后没遍历到一个状态i 就从低位到高位找1 找到第一个1记录位置 然后枚举另外一个1
dp[i]=min(dp[i-(1<<a)-(1<<b)]+dist(st,node[a])+dist(node[a],node[b])); (a表示低位1的位置 b有很多个 表示第二个1的位置
dist()是平面2点距离)
因为我事先队坐标排过序 这样转移一定是最优解了
而且我都预处理了很多不合法状态(1有奇数个 1<2个的状态都不合法)
最终我的时间复杂度最坏情况是O(50万*k) k<20
结果就是超时
我就纳闷了 我以前DP从来都是顺推的 怎么这次超时了 主要是测试cases太多了 100组
然后找到了原因 比如状态 1111 可以从 1100 和0011转移过来
其实只要求其中任意一个转移的耗费就行了 因为先去前2个1和后2个1对最终状态没影响
说白了就是当前这个状态不受前面取点的顺序影响
因为状态转移方程每次都取了最低为那个1 然后再枚举另一个1 就是枚举哪个1和这个组合
正推会遍历很多无效状态
后面倒推就避免了这个情况了 倒推的状态我现在还是有点没理解好
倒推说白了就是记忆化搜索 这个刘汝佳也专门提到过 动态规划的3种经典方向
记忆化搜索 否则还是 超时 总之我对这题很无语 多开一两秒又怎么了嘛 非要卡我正推的时间
400个提交就15个人过了 估计很多人也有和我相同的感受吧!
我先把我超时的正推代码放下 结果是正确的 只是超时
我个人还是不怎么喜欢深搜的 比较费栈
正常情况正推的DP都会快些的 不耗栈也无需判重
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define MAXN 1111111
#define INF 999999999
struct Point
{
double x,y;
}st,node[22];
double dp[MAXN];
int bit[MAXN];
inline double dist(const Point &a,const Point &b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
bool cmp(const Point a,const Point b)
{
return dist(st,a)<dist(st,b);
}
inline double min(double x,double y)
{
return x<y?x:y;
}
int countbit(int v)
{
int r=0;
for(;v;v&=v-1)
r++;
return r;
}
int main()
{
int t,casenum;
int i,j,n,top;
top=1<<20;
for(i=3,n=0;i<top;i++)
{
j=countbit(i);
if((j&1) || j<2)
continue;
bit[n++]=i;
}
bit[n]=INF;
printf("%d\n",n);
//for(i=0;i<n;i++)
//{
// printf("%d\n",bit[i]);
//}
scanf("%d",&t);
for(casenum=1;casenum<=t;casenum++)
{
scanf("%lf %lf",&st.x,&st.y);
scanf("%d",&n);
n<<=1;
for(i=1;i<=n;i++)
{
scanf("%lf %lf",&node[i].x,&node[i].y);
}
sort(node+1,node+1+n,cmp);
memset(dp,0,sizeof(dp));
top=1<<n;
int a,b,c;
for(i=0;bit[i]<top;i++)
{
int temp=bit[i];
for(a=-1,c=1;a==-1;temp>>=1,c++)
{
if(temp & 1)
a=c;
}
dp[bit[i]]=INF;
for(;temp;temp>>=1,c++)
{
if(temp & 1)
{
b=c;
dp[bit[i]]=min(dp[bit[i]],dp[bit[i]-(1<<(a-1))-(1<<(b-1))]
+dist(st,node[a])+dist(node[a],node[b]));
}
}
}
printf("Case #%d: ",casenum);
printf("%.2lf\n",dp[top-1]);
}
return 0;
}
然后是Ac代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 1111111
#define INF 0x3fffffff
struct Point
{
double x,y;
}node[22];
int n;
double hash[MAXN];
double dist(Point a,Point b)
{
return sqrt((a.x-b.x)*(a.x-b.x)
+(a.y-b.y)*(a.y-b.y));
}
bool nodecmp(const Point a,const Point b)
{
return dist(node[0],a)<dist(node[0],b);
}
double min(double x,double y)
{
return x<y?x:y;
}
double dfs(int sta,int cnum)
{
if(hash[sta]>=0)
return hash[sta];
if(cnum==n)
return 0;
int pos=-1;
int temp=sta;
hash[sta]=INF;
for(int c=1;temp;temp>>=1,c++)
{
if(temp & 1)
{
if(pos==-1)
pos=c;
else
hash[sta]=min(hash[sta],dfs(sta-(1<<(pos-1))-(1<<(c-1)),cnum+1)
+dist(node[0],node[pos])+dist(node[pos],node[c]));
}
}
return hash[sta];
}
int main()
{
int t,casenum;
int i,upper;
scanf("%d",&t);
for(casenum=1;casenum<=t;casenum++)
{
scanf("%lf %lf",&node[0].x,&node[0].y);
scanf("%d",&n);
upper=n<<1;
for(i=1;i<=upper;i++)
{
scanf("%lf %lf",&node[i].x,&node[i].y);
}
sort(node+1,node+1+upper,nodecmp);
memset(hash,-1,sizeof(hash));
double ans=dfs((1<<upper)-1,0);
printf("Case #%d: %.2lf\n",casenum,ans);
}
return 0;
}
有疑问可以留言 我会尽量解答的 顺便我也加深理解...