[bzoj 1079--SCOI2008]着色方案

124 篇文章 2 订阅
26 篇文章 0 订阅

有n个木块排成一行,从左到右依次编号为1~n。你有k种颜色的油漆,其中第i种颜色的油漆足够涂ci个木块。
所有油漆刚好足够涂满所有木块,即c1+c2+…+ck=n。相邻两个木块涂相同色显得很难看,所以你希望统计任意两个相邻木块颜色不同的着色方案。

真是一道大神题啊。一开始看到时乱想一大推,但其实原来这题如此巧妙。
其实像这样统计方案的问题,一般就是用dp来做的,只不过这题dp用记忆化搜索罢了。
先定义一个f数组,f[a1][a2][a3][a4][a5][x]中ai表示还剩下i次机会涂色的颜色种类数,x表示上一次涂色用的是a几。为什么这样呢,因为决定现在这个颜色是否能涂只跟上一次涂色有关。
那么问题来了,如何快速继承状态呢?
首先得弄明白一点,当前选什么样的颜色不重要,只需满足相邻条件就行,还剩下多少次能涂才是重要的。举个例子,假设你要选还剩下3次机会涂色的颜色,只需让a3(或a3-1,为什么等一下讲)乘上f[a1][a2+1][a3-1][a4][a5][x](为什么等一下讲)就行。
明白后就方便继承了。但还有几点需要注意,假设你选了还剩下3次机会涂色的颜色,那你a2就要+1,a3要-1,因为你选了以后它就变成了还剩下2次机会涂色的颜色。相邻条件似乎是最大的难点,但我们知道了你上次选了还剩下i次机会涂色的颜色,如果你这次要选还剩下i-1次机会涂色的颜色,那你就不能再选上次选过的颜色,不然会相邻,这就是a(i-1)-1乘而不是a(i-1)乘的原因。
那么这题便迎刃而解了。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#define mod 1000000007
using namespace std;
int s[6];
long long f[17][17][17][17][17][7];
long long dfs(int a1,int a2,int a3,int a4,int a5,int x)
{
    if(a1==0 && a2==0 && a3==0 && a4==0 && a5==0)f[a1][a2][a3][a4][a5][x]=1;
    if(f[a1][a2][a3][a4][a5][x]!=0)return f[a1][a2][a3][a4][a5][x];
    long long ans=0;
    if(a1!=0)
    {
        int he=a1;if(x==2)he--;
        ans+=he*dfs(a1-1,a2,a3,a4,a5,1);ans%=mod;
    }
    if(a2!=0)
    {
        int he=a2;if(x==3)he--;
        ans+=he*dfs(a1+1,a2-1,a3,a4,a5,2);ans%=mod;
    }
    if(a3!=0)
    {
        int he=a3;if(x==4)he--;
        ans+=he*dfs(a1,a2+1,a3-1,a4,a5,3);ans%=mod;
    }
    if(a4!=0)
    {
        int he=a4;if(x==5)he--;
        ans+=he*dfs(a1,a2,a3+1,a4-1,a5,4);ans%=mod;
    }
    if(a5!=0)
    {
        int he=a5;
        ans+=he*dfs(a1,a2,a3,a4+1,a5-1,5);ans%=mod;
    }
    f[a1][a2][a3][a4][a5][x]=ans;
    return ans;
}
int main()
{
    int k;
    scanf("%d",&k);
    for(int i=1;i<=k;i++)
    {
        int x;
        scanf("%d",&x);
        s[x]++;
    }
    printf("%lld\n",dfs(s[1],s[2],s[3],s[4],s[5],0));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值