CF8C Looking for Order
状压dp
令f[state]表示当前已放置状态为state时的最小代价
f[state|i|j]=min(f[state|i|j],f[state]+dis(0,i)+dis(i,j)+dis(j,0));
i,j可以相等
然后:
59640780 | Aug/29/2019 09:16UTC+8 | qyj060604 | 8C - Looking for Order | GNU C++11 | Time limit exceeded on test 12 | 4000 ms | 197000 KB |
怎么解决呢?
优化1:如果state包含i或者j,continue
这步很好理解,因为我们不会重复拿一个东西
优化2:只要找到一个可行方案就break
这是一个神优化
可以让你直接AC
我们考虑它的正确性
用state拓展时,如果存在一个j合法,那么至少有一个k合法(j可以等于k)
考虑我们的答案路径其实是有很多的,因为我们可以随意交换两个行程
举个例子:
0 1 2 0 3 0
等价于
0 3 0 1 2 0
这样的等价路径有很多,大大降低了我们的时间效率
我们只取字典序最小的路径,这样就可以避免重复
(貌似这样的“取字典序最小”的操作在状压dp中很常见)
效果:
59640812 | Aug/29/2019 09:18UTC+8 | qyj060604 | 8C - Looking for Order | GNU C++11 | Accepted | 108 ms | 197000 KB |
直接优化超过3900ms
输出路径的话,我们记录一个pre[state]表示state是由pre[state]转移来的
然后倒上去即可
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=30; struct node{ int x,y; }s[N]; ll f[1<<24|1]; int pre[1<<24|1]; ll dis[N][N]; int n; inline void print(int state){ if(state==0){ printf("0 "); return; } print(pre[state]); for(int i=1;i<=n;i++){ if((state|(1<<(i-1)))==state&&(pre[state]|(1<<(i-1)))!=pre[state]){ printf("%d ",i); } } printf("0 "); } int main(){ memset(f,127,sizeof(f)); ll opt=f[0]; scanf("%d%d",&s[0].x,&s[0].y); scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d%d",&s[i].x,&s[i].y); } for(int i=0;i<=n;i++){ for(int j=0;j<=n;j++){ dis[i][j]=(ll)(s[i].x-s[j].x)*(s[i].x-s[j].x)+(s[i].y-s[j].y)*(s[i].y-s[j].y); } } f[0]=0; for(int i=0;i<=(1<<n)-1;i++){ if(f[i]==opt) continue; for(int j=1;j<=n;j++){ if((i|(1<<(j-1)))==i){ continue; } for(int k=1;k<=n;k++){ if((i|(1<<(k-1)))==i){ continue; } ll u=f[i]+dis[0][j]+dis[j][k]+dis[k][0]; ll state=(i|(1<<(j-1))|(1<<(k-1))); if(u<f[state]){ f[state]=u; pre[state]=i; } } break; } } printf("%lld\n",f[(1<<n)-1]); print((1<<n)-1); printf("\n"); return 0; }