飘花效果

【状压 DP】2022 绍兴市小学组 T4 | 三角形

回答我!为什么这么多人都谢了 DFS!为什么?!回答我!!!

所以考虑不使用 DFS 解决这道题。

碰巧以前学过状压 DP,今天又凑巧翻到了这题。

于是就有了这篇文章。


开篇注意:实际有 \(3n\) 根棍子。

  • 套路,设 \(f_i\) 表示在状态 \(i\) 的情况下不记顺序可以得到的方案数,这里思考一下发现设这一维就够了。

  • 初始状态:\(f_0 = 1\),理论上不选也是一种方案,更重要的是它是为了转移。

  • 下面是状态转移,刷表和填表都可以,这里使用顺推(不适用前面两个用语是因为分不清 QwQ),记当前状态为 \(i\)

  • 乍一看转移好像很难,我们要找到这些 \(0\) 并通过 DFS 选择 \(3\) 个不同的这样的 \(0\),新状态为这 \(3\)\(0\) 改为 \(1\),转移为 \(f_{new} \gets f_{new}+f_i\)

    • 比如 \(i = 100101101_{(2)}\),那么有四种新方案,分别是 \(111111101_{(2)}\)\(11101111_{(2)}\)\(110111111_{(2)}\)\(101111111_{(2)}\)

    • 但是注意需要判断添加这 \(3\) 个棍子为一组,是否合法(是否能够构成三角形)!不然不能转移。

    • 值得注意的是,我们开头说过不使用 DFS

    • 因此我们使用一种更暴力的手段:

  • 暴力枚举三根不同棍子 \(x\)\(y\)\(z\),进行转移。

    • 值得注意的是,第一需要判断是否能够构成三角形,第二是 \(i\) 对应状态不能出现它们三根棍子

      • 比如 \(i = 1111000_{(2)}\),但是 \(x=1,y=2,z=4\)\(new = 1111011_{(2)}\),实际就改变了 \(2\)\(0\),与我们目的不符。
    • 转移依旧是 \(f_{new} \gets f_{new} + f_i\)

  • 最后答案为 \(\frac{f_{2^{3n} - 1}}{n!}\)\(2^{3n} - 1\) 为最终状态,除以 \(n!\) 是因为这样转移不计顺序,一个方案中的 \(n\) 个组可以进行全排列,这样 \(f_{2^{3n} - 1} = \text{实际方案数} \times n!\),所以需要倒着算回去。

时间复杂度为 \(\mathcal O(2^{3n}(3n)^3)\),这样做足以通过 \(1\le n\le 5\)(理论上需要卡常)!

但是我们希望它更优秀。

我们发现这种做法实在太暴力了,每次都要重新判断是否合法,那么我们能不能先 \(\mathcal O((3n)^3)\) 记录合法的方案数,在转移时枚举呢?

这是可以的,但是注意去重,通过组合数可以知道方案数最多为 \(C_{3n}^3\),情况为 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 等。

这样时间复杂度为 \(\mathcal O(C_{3n}^32^{3n})\)\(n=5\) 时原式为 \(14909400\),当 \(n=6\) 时原式为 \(213909504\)

我们希望继续优化,但是我们发现,如果状态 \(i\) 不合法,我们也没有由 \(i\) 向后推的必要。

所以我们可以跳过 \(f_i = 0\) 的情况,这样经过测试 DP 共进行了 \(8912352\) 次转移,舍掉了 \(62391360\) 种不合法状态(去除 \(f_i = 0\)),稍微卡常一下就可以通过了,毕竟位运算速度很快。

至此,我们成功解决了 \(n = 6\) 的情况。

//c[i] * b[j] < c[j] * b[i]
# include <bits/stdc++.h>

# define int long long
# define up(i ,x ,y) for (int i = x ; i <= y ; i ++)
# define dn(i ,x ,y) for (int i = x ; i >= y ; i --)
# define inf 1e14
 
using namespace std;
 
inline int read (){int s = 0 ; bool w = 0 ; char c = getchar () ; while (!isdigit (c)) {w |= (c == '-') ,c = getchar () ;} while (isdigit (c)){s = (s << 1) + (s << 3) + (c ^ 48) ; c = getchar ();}return w ? -s : s;}
inline void write (int x){if (x < 0) putchar ('-') ,x = -x; if (x > 9) write (x / 10) ; putchar (x % 10 | 48);}
inline void writesp (int x){write (x) ,putchar (' ');}
inline void writeln (int x){write (x) ,putchar ('\n');}

const int N = 22;
const int fact[] = {1 ,1 ,2 ,6 ,24 ,120 ,720};
int n ,cnt ,mp[N][N][N] ,a[N] ,f[1 << N];
struct triangle {int first ,second ,third;}tri[N * N * N];
signed main (){
  n = read ();
  n *= 3;
  up (i ,1 ,n) a[i] = read ();
  up (i ,1 ,n) up (j ,1 ,n) up (k ,1 ,n)
    if (i ^ j && j ^ k && i ^ k && a[i] + a[j] + a[k] > 2 * max ({a[i] ,a[j] ,a[k]}) && !mp[i][j][k]) tri[++ cnt] = {i ,j ,k} ,mp[i][j][k] = mp[i][k][j] = mp[j][i][k] = mp[j][k][i] = mp[k][i][j] = mp[k][j][i] = 1;
  f[0] = 1;
  int limit = (1 << n) - 1;
  up (i ,0 ,limit){
    if (!f[i]) continue;
    up (j ,1 ,cnt){
      int x = tri[j].first ,y = tri[j].second ,z = tri[j].third;
      if ((i >> (x - 1)) & 1 || (i >> (y - 1)) & 1 || (i >> (z - 1)) & 1) continue;
      int k = (i | (1 << (x - 1)) | (1 << (y - 1)) | (1 << (z - 1)));
      f[k] += f[i];
    }
  } writeln (f[limit] / fact[n / 3]);
  return 0 ;
}
posted @ 2025-07-01 18:09  2021zjhs005  阅读(7)  评论(0)    收藏  举报