ABC025D 25个整数 题解
ABC025D 25 个整数
思路
看完题目,首先想到的可能是暴力搜索。像 数独 这样的题目,能想到暴力是很正常的。
但是,这是道黑题,用那种低级的方法一定过不了。
所以,我们可以用 状压 dp。
怎么状压?一般来说,有两种思路。第一种是记录哪些格子被填了,另一种是记录哪些数字被填了。
对于第二种,我们发现这样记录的信息不够(因为格子与其上下左右的格子都有关系),或者说难转移。所以应该使用第一种。
因为第一种没有记录具体填什么,所以填数要有顺序,例如从小到大或从大到小。事实上,两种都可以,所以这里从从小到大的方面来考虑。
这意味着,假如对于一个状态
如何转移?
对于每一个状态
如果
如果
可行性判断?
我们讨论一下横向(左右)的判断(纵向同理)。
如果一个格子在边缘,那么它必当不会成为一个单调数列的中间。所以可以不讨论。
如果它在中间,则我们可以发现,如果它左边和右边只填了一个,那么填了的那个格子上的数会小于它自己的数,而没填的将来会填个比它大的,所以这种情况是不行的。如果它左边和右边要么都填了,要么都没填,显然它不会是单调的。
复杂度:5s。
一些实现方法
本段可以跳过并直接看代码。
stl里面有一个函数,叫做__builtin_popcount(__libcpp_popcount是间接调用它的函数,但用不了,很奇怪),可以计算一个整数在二进制内1 的个数。-
参考代码
#define pop(x) __builtin_popcount(x)
#define MOD (1000000007)
int dp[1 << 25]; // 存数组
int mp[30]; // 存输入地图
void add(int i, int p) // i 为当前状态,p 为当前填的数的位置
{
int x = p % 5; // 横向判断
int y = p / 5; // 纵向判断
if (x && x < 4 && (i >> (p - 1) & 1 ^ i >> (p + 1) & 1)) // (p - 1) 即它左边的格子,(p + 1) 即它右边的。
{
return;
}
if (y && y < 4 && (i >> (p - 5) & 1 ^ i >> (p + 5) & 1)) // (p - 5) 为上,(p + 5) 为下。
{
return;
}
dp[i] = (dp[i] + dp[i ^ (1 << p)]) % MOD; // 记得模数
}
int main()
{
for (int i = 1; i <= 25; i++)
{
int c;
cin >> c;
mp[c] = i; // 如果 c 为 0 也没所谓。
}
dp[0] = 1; // 初始化
for (int i = 1; i < 1 << 25; i++) // dp
{
int p = mp[pop(i)];
if (p)
{
add(i, p - 1); // 只能填这个位置。注意要 -1!!
}
else
{
for (int j = 0; j < 25; j++) // 枚举
{
if (i & (1 << j))
{
add(i, j);
}
}
}
}
cout << dp[(1 << 25) - 1] << endl; // 结果。也要 -1!!
return 0;
}
update: 参考代码有点问题,却因语言特性卡过去了。现在的代码没问题了。