题
点击打开链接
又沉浸在了模运算的海洋中了。。。这个模运算真的太恶心了,有时候又神奇又复杂,还是经验不够丰富,导致了很多错误。在此我也得到一个教训,对于涉及模运算的问题,宁可写的复杂一些,难看一些,也不要耍小聪明,那样一旦失误造成的后果可能是惨重的。
说说思路,一眼看上去就知道应该是状态压缩dp,但是这个复杂度得好好算一算,首先两数之积不大于40,说明最小边不大于6,因为是三层所以要储存两层内容,但是这样一算最大复杂度就是4的12次方,这个是不可接受的,后来想了想发现可以优化一下,我们其实不需要知道最上面的是什么,只需要知道他是不是和下面的颜色相同即可,因为之前的dp肯定会排除横着三个相邻的情况,所以是成立的(当然我当时心里也不是百分百的把握,但想想也只能这么做了,就做下去了),这样复杂度就变成了2的18次方了,再加上一些优化是可以完成的,我们首先要知道初始状况,所以第一行直接进行一个搜索,把可以的情况都找出来(这里参考了聚聚的题解,万分感谢,不过大体思路还是自己研究的,因为我根本看不懂那些位运算。。。),然后再看时查找,最后把所有情况相加即可。
这道题其实实现并不难,但是被位运算坑了好久,最后终于把问题都排查出来了,还是那句话,宁可难看,但要正确,这个教训真的记住了。另外我这个代码速度还是可以的,好像暂时是第一,我觉得有这么几个优化,首先如果只有一种颜色而且边最大值大于等于3那么直接回0,另外一些发现没用的情况直接pass,还有一点,就是取模运算听说挺费时间的,所以像这种只有加法的情况可以用判断减法代替取模。
代码
# include <stdio.h>
# include <string.h>
# include <algorithm>
using namespace std;
const int Mod = 1e9 + 7;
const int MAX_N = 1 << 18;
int dp[2][MAX_N];
int N, M, K, cnt;
void dfs(int h , int num , int a , int b)
{
if(h == M)
{
dp[0][num] = 1;
return;
}
num <<= 3;
int i;
for(i = 0 ; i < K ; i++)
{
if(i == a && i == b)
continue;
dfs(h + 1 , num | i, b , i);
}
}
int main()
{
while(~scanf("%d %d %d", &N, &M, &K))
{
if(K == 1 && (M > 2 || N > 2))
{
puts("0");
continue;
}
if(M > N)
swap(N , M);
memset(dp , 0 , sizeof(dp));
int * now = dp[0], * next = dp[1];
dfs(0 , 0 , -1 , -1);
int q = 1 << (3 * M);
//int num = 0;
int i, j, k, g;
for(i = 1 ; i < N ; i++)
{
for(j = 0 ; j < M ; j++)
{
fill(next , next + q , 0);
for(k = 0 ; k < q ; k++)
{
if(!now[k])
continue;
for(g = 0 ; g < K ; g++)
{
if(j > 1 && (k & (3 << (3 * (j - 1)))) == (g << (3 * (j - 1))) && (k & (3 << (3 * (j - 2)))) == (g << (3 * (j - 2))))
continue;
int op = ((k >> (3 * j)) & 7), ch;
if((op & 3) == g && op > 3)
continue;
if((op & 3) == g)
{
ch = k & ~(3 << (3 * j)) | ((4 + g) << (3 * j));
}
else
ch = k & ~(7 << (3 * j)) | (g << (3 * j));
next[ch] += now[k];
if(next[ch] > Mod)
next[ch] -= Mod;
}
}
swap(now , next);
}
}
int ans = 0, w;
for(w = 0 ; w < q ; w++)
{
ans += now[w];
if(ans > Mod)
ans -= Mod;
}
printf("%d\n", ans);
}
return 0;
}