【算法•日更•第十五期】信息奥赛一本通1594:涂抹果酱题解

  废话不多说,直接上题:


1594:涂抹果酱


时间限制: 1000 ms         内存限制: 524288 KB
提交数: 146     通过数: 46 

【题目描述】

Tyvj 两周年庆典要到了,Sam 想为 Tyvj 做一个大蛋糕。蛋糕俯视图是一个 N×M 的矩形,它被划分成 N×M 个边长为 1×1 的小正方形区域(可以把蛋糕当成 N 行 M 列的矩阵)。蛋糕很快做好了,但光秃秃的蛋糕肯定不好看!所以,Sam 要在蛋糕的上表面涂抹果酱。果酱有三种,分别是红果酱、绿果酱、蓝果酱,三种果酱的编号分别为 1,2,3。为了保证蛋糕的视觉效果,Admin 下达了死命令:相邻的区域严禁使用同种果酱。但 Sam 在接到这条命令之前,已经涂好了蛋糕第 K 行的果酱,且无法修改。

现在 Sam 想知道:能令 Admin 满意的涂果酱方案有多少种。请输出方案数 mod 106 。若不存在满足条件的方案,请输出 0。

【输入】

输入共三行。

第一行:N,M;

第二行:K;

第三行:M 个整数,表示第 K 行的方案。

字母的详细含义见题目描述,其他参见样例。

【输出】

输出仅一行,为可行的方案总数。

【输入样例】

2 2 
1 
2 3

【输出样例】

3

【提示】

样例说明:

 

方案一 方案二 方案三
2 32 3 2 32 3 2 32 3
1 21 2 3 13 1 3 23 2

 

数据范围与提示:

对于 30% 的数据,1N×M20

对于 60% 的数据,1N1000,1M3

对于 100% 的数据,1N10000,1M5

【来源】


  这道题和上一次的题目十分相像,可以直接判定这是状态压缩动态规划。

  那么怎么状态压缩呢?这个果酱有三种颜色,所以我们就不能使用二进制了,而是采用三进制的方法状态压缩。

  题目中的果酱有红绿蓝三色,那么我们就分别让他们用0,1,2来表示。

  那么肯定要预处理,只要枚举好一行所有的状态即可,注意有些二进制的方法要变的,详见代码。

  先来说状态怎么设计,我们可以用f[i][j]来表示第i行状态为a[j],值为状态是否可行。总之先分析一下现在这张图是什么样的。

  

  其中第k行已经被涂过了,然后我们就可以分成这三个部分,分别是上图所示的三个部分(红绿蓝)。

  因为第k行已经被涂过了,所以可以把它看成是第0行。显然,这样蓝色部分和红色部分第i行的方案数是一样的(因为互相不受干扰)。

  

  忽略小编拙劣的画技。

  因此我们便利的行数只要是红色的行数就可以了,那么我们已经区分开了两个部分,怎样状态转移呢?

  显然,f[i][j]=sum{f[i-1][k]},(前提是不发生冲突)其中a[j]是当前行的状态,a[k]是上一行的状态,那么这一行的方案数自然就是上一行方案数的和呗。

  最后,我们要把所有状态的方案数都要加在一起,同时注意两个部分分别处理,方案总数是两个部分方案数的积。(乘法原理)

  好了,详见注释,代码如下:

 1 #include<iostream> 
 2 #define mod 1000000
 3 using namespace std;
 4 long long n,m,K,c[10000],cnt,a[10000],f[10001][200],s,num,correct[10000][10000],ans1,ans2;
 5 inline int check(int x)//判断行内冲突 
 6 {
 7     for(int i=m-1;i;i--) 
 8     if((x%c[i+1]/c[i])==(x%c[i]/c[i-1]))
 9     return 0;
10     return 1;
11 }
12 inline int check2(int x,int y)//判断两行间的冲突 
13 {
14     for(int i=m-1;i>=0;i--) 
15     if((x%c[i+1]/c[i])==(y%c[i+1]/c[i]))
16     return 0;
17     return 1;
18 }
19 int main()
20 {
21     cin>>n>>m>>K;c[0]=1;
22     for(int i=1;i<=m;i++)
23     {
24         cin>>s;
25         num*=3;
26         num+=s-1;//三进制表示 
27         c[i]=c[i-1]*3;//c是表示3进制数位的数组 
28     }
29     if(!check(num)){cout<<0;return 0;}//如果第K行有冲突,那么直接输出0 
30     for(int i=0;i<c[m];i++)//逐个枚举一行的状态 
31     {
32         if(check(i)) a[++cnt]=i;//记录状态 
33         if(i==num) f[0][cnt]=1;//初始化 
34     }
35     for(int i=1;i<=cnt;i++)//枚举当前行状态 
36     for(int j=1;j<=cnt;j++)//枚举上一行状态 
37     if(check2(a[i],a[j])) correct[i][j]=1;//判断是否发生冲突 
38     int x=max(K-1,n-K);int y=min(K-1,n-K);//分成两个部分 
39     for(int i=1;i<=x;i++)//枚举行 
40     for(int j=1;j<=cnt;j++)//枚举当前行状态 
41     for(int k=1;k<=cnt;k++)//枚举上一行状态 
42     if(correct[j][k]) f[i][j]=(f[i][j]+f[i-1][k])%mod;//注意是否发生冲突 
43     for(int i=1;i<=cnt;i++)//枚举状态数 
44     {
45         ans1+=f[x][i];
46         ans1%=mod;
47         ans2+=f[y][i];//两个部分分别处理 
48         ans2%=mod;
49     }
50     cout<<ans1*ans2%mod;//乘法原理 
51     return 0;
52 }
posted @ 2019-07-18 15:50  c1714-gzr  阅读(545)  评论(1编辑  收藏  举报