【状压 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 ;
}
本文来自博客园,作者:2021zjhs005,转载请注明原文链接:https://www.cnblogs.com/2021zjhs005/p/18960110

浙公网安备 33010602011771号