HDU-4281 Judges‘ response(2012 ACM/ICPC Asia Regional Tianjin Online)

HDU-4281 Judges' response(2012 ACM/ICPC Asia Regional Tianjin Online)

状态压缩 + 01背包 + 区间dp

题意:

有n个地点,第一个地点是裁判位置,其他n-1个地点是选手的位置。裁判要给选手解决问题,每个选手都有一个时间表示解决这个选手问题所需要的时间。同样的,裁判也有一个时间,表示这个裁判的耐心值。

问题有两个:

  • 问最少需要多少裁判能解决选手所有的问题

  • 问在可以用无限裁判的情况下(依然考虑裁判的耐心值),裁判到达所有选手位置,再回到裁判原来的位置最少的时间

思路:

对于第一个问题:

类似于小猫爬山,感觉很典,用dfs剪枝好像更快一点。

dfs剪枝思路:

数组倒序排,然后dfs剪枝能填就填。但是在这个题里要特判一下如果有一个人的解答时间超过裁判的耐心值就输出-1

剪枝的复杂度证明不了,但是比状压快

   auto f =[&] () {
      vector<int> v;
      for (int i = 1; i < n; i++) {
         v.push_back(c[i]);
      }
​
      sort(v.begin(), v.end(), greater<int>());
      if(v[0] > m) return inf;
      vector<int> vis(n + 10, 0);
      int res = inf;
​
      function<void(int, int)> dfs =[&] (int now, int cnt) {
         if(cnt >= res) {
            return ;
         }
         if(now == n - 1) {
            res = min(res, cnt);
            return ;
         }
         for (int i = 1; i <= cnt; i++) {
            if(vis[i] + v[now] <= m) {
               vis[i] += v[now];
               dfs(now + 1, cnt);
               vis[i] -= v[now];
            }
         }
​
         vis[cnt + 1] = v[now];
         dfs(now + 1, cnt + 1);
         vis[cnt + 1] = 0;
      };
​
      dfs(0, 1);
      return res;
​
   };

状态压缩 + 01背包:

我感觉很妙,先把一个教练能解答的情况表示出来。

情况怎么表示?二进制枚举,0表示不解答,1表示解答。

把一个人能解决的情况,也就是这几个人的解答时间之和不会超过耐心值。而且要注意,这个情况是一个人能完成的所有情况而不是最大情况。

这些压缩后的二进制看成01背包的物品价值,重量都是1。

   for (int i = 0; i < (1 << n); i++) {
      if(check(i)) {
         statue.push_back(i);
      }
   }
​
   auto dp01 =[&] () {
      vector<int> dp((1 << n) + 10, inf);
      dp[0] = 0;
​
      for (int step : statue) {
         for (int j = (1 << n) - 1; j >= 0; j--) {
            if(dp[j] != inf && !(j & step)) {
               dp[j + step] = min(dp[j + step], dp[j] + 1);
            }
         }
      }
​
      return dp[(1 << n) - 1];
   };

最后特判一下,-1的情况

   int ans1 = f();
   // int ans1 = dp01();
   if(ans1 == inf) {
      cout << -1 << " " << -1 << "\n";
      return;
   }
   cout << ans1 << ' ';

对于第二个问题:

我们怎么在M(裁判耐心值)的限制下完成这个旅行商问题?

原来的旅行商是一个人可以走很多地方。但是这个问题里有了限制,裁判就不一定能走到所有地方了。

那我们应该把这个情况先表示出来。每一个都是独立的。每一次走完都要回到原点,把这几次的情况用区间dp合并就好了。

我们现在用三个数组表示状态。

tem[i][j] 表示走完j状态,停到i点

best[i] 表示走完j状态,停到原点的最短时间

两个转移方程:

  • tem[k][s | (1 << k)] = min(tem[k][s | (1 << k)], tem[j][s] + dis[j][k]);

  • best[s] = min(best[s], tem[j][s] + dis[j][0]);

这两个dp完成之后我们得到了best[i]。也就是一个人能到达的情况的最优值。

最后只需要把这些情况合并就好。

   for (int i = 0; i < (1 << n); i++) {
      if(i & 1) {
         for (int j = i; j; j = i & (j - 1)) {
               best[i] = min(best[i], best[j | 1] + best[(i - j) | 1]);
         }
      }
   }

有两个需要注意的地方:

  • i&1,也就是这个情况必须从1点出发,不然不合法

  • 第二层循环的循环方式,就是枚举掩码的所有子掩码的方式,为的就是两个情况去的点不重复。

完整代码:

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <functional>
​
using namespace std;
​
const int inf = 1e9;
void solve(int n, int m) {
   vector<int> x(n + 1), y(n + 1), c(n + 1);
   for (int i = 0; i < n; i++) cin >> x[i] >> y[i];
   for (int i = 0; i < n; i++) cin >> c[i];
​
   vector<int> statue;
   auto check =[&] (int t) {
      int res = 0;
      for (int i = 0; i < n; i++) {
         if((1 << i) & t) {
            res += c[i];
         }
      }
      return res <= m;
   };
​
   for (int i = 0; i < (1 << n); i++) {
      if(check(i)) {
         statue.push_back(i);
      }
   }
​
   auto dp01 =[&] () {
      vector<int> dp((1 << n) + 10, inf);
      dp[0] = 0;
​
      for (int step : statue) {
         for (int j = (1 << n) - 1; j >= 0; j--) {
            if(dp[j] != inf && !(j & step)) {
               dp[j + step] = min(dp[j + step], dp[j] + 1);
            }
         }
      }
​
      return dp[(1 << n) - 1];
   };
​
   auto f =[&] () {
      vector<int> v;
      for (int i = 1; i < n; i++) {
         v.push_back(c[i]);
      }
​
      sort(v.begin(), v.end(), greater<int>());
      if(v[0] > m) return inf;
      vector<int> vis(n + 10, 0);
      int res = inf;
​
      function<void(int, int)> dfs =[&] (int now, int cnt) {
         if(cnt >= res) {
            return ;
         }
         if(now == n - 1) {
            res = min(res, cnt);
            return ;
         }
         for (int i = 1; i <= cnt; i++) {
            if(vis[i] + v[now] <= m) {
               vis[i] += v[now];
               dfs(now + 1, cnt);
               vis[i] -= v[now];
            }
         }
​
         vis[cnt + 1] = v[now];
         dfs(now + 1, cnt + 1);
         vis[cnt + 1] = 0;
      };
​
      dfs(0, 1);
      return res;
​
   };
​
   int ans1 = f();
   // int ans1 = dp01();
   if(ans1 == inf) {
      cout << -1 << " " << -1 << "\n";
      return;
   }
   cout << ans1 << ' ';
​
​
   vector<int> best((1 << n) + 10, inf);
   vector<vector<int>> tem(n + 10, vector<int>((1 << n) + 10, inf));
   vector<vector<int>> dis(n + 10, vector<int>(n + 1));
   tem[0][1] = 0;
​
   auto cal =[&] (int a, int b) {
      return ceil(sqrt((x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b])));
   };
​
   for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
         dis[i][j] = cal(i, j);
      }
   }
​
   for (auto s : statue) {
      for (int j = 0; j < n; j++) {
         for (int k = 0; k < n; k++) {
            if(!((1 << k) & s)) {
               tem[k][s | (1 << k)] = min(tem[k][s | (1 << k)], tem[j][s] + dis[j][k]);
            }
         }
      }
   }
   for (auto s : statue) {
      for (int j = 0; j < n; j++) {
         if((1 << j) & s) {
            best[s] = min(best[s], tem[j][s] + dis[j][0]);
         }
      }
   }
​
​
   for (int i = 0; i < (1 << n); i++) {
      if(i & 1) {
         for (int j = i; j; j = i & (j - 1)) {
               best[i] = min(best[i], best[j | 1] + best[(i - j) | 1]);
         }
      }
   }
​
   cout << best[(1 << n) - 1] << "\n";
}
​
signed main() {
   ios::sync_with_stdio(0);
   cin.tie(0), cout.tie(0);
​
   int n, m;
   while(cin >> n >> m) {
      // cin >> n >> m;
      solve(n, m);
   }
​
   return 0;
}
​

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值