涂色游戏 题解

bsoj6412 没找到出处。。。

题意简述:给一颗树,一次操作定义为随机选择一个点,染掉该点和它周围一圈的点,问期望多少次染黑所有点。

这是道好题啊!全面考察了容斥、反演、期望和dp,有许多值得注意的细节。


一、做法1(容斥/二项式反演+dp)

1.1 化式子

首先肯定第一个想到的式子就是

\[
Ans = \sum_{i=1}^{\infty} i * P(在染第i次时刚好黑完)
\]

这是根据期望的定义直接得到的。

然后发现这个\(i\)实在是非常的恶心,因为它居然和无穷有关。但既然是一个合法的期望题,这个\(i\)必然可以找到某种转化的手段把它弄成一个能算的且收敛的东西,比如等比数列级数之类的。

于是这里有一个套路化法
\[
\sum_{i=1}^{\infty} i * P(x=i) = \sum_{i=0}^{\infty} P(x>i)
\]

(\(P(A)\)代表事件\(A\)发生的概率)

就是改了改枚举的方式,随便想一想应该能够明白了吧(

总之,根据上式我们就可以得到
\[
Ans = \sum_{i=0}^{\infty} P(染i次未黑完)
\]

我们成功把\(i\)丢到了\(P\)里面去。但是我们还是没有办法求这个东西。

发现虽然选点可以进行无数次,但是最多只会选有限个点,许多选点是重复的。用实际选择的点的个数,我们可以在不可计算的无限和可计算的有限之间搭上一座桥梁。我们考虑将上面的\(P\)进行一个拆分,得到
\[
Ans = \sum_{i=0}^{\infty} \sum_{k=0}^{n} P(选i次恰好选中某k个点) * N(k个点未黑完整棵树)
\]

\(N(A)\)代表事件\(A\)的方案数

注意式子中“某”的含义。可以这样理解这个式子:

我钦定了某\(k\)个点

首先我想知道:进行\(i\)次随机选择,选中且只选中这\(k\)个点的概率

然后再判断这\(k\)个点是否能让整棵树黑完。如果不能,则将这部分概率计入。

将所有可能钦定的情况合起来就是\(N\),而选择的概率实际上和树形结构无关,选中任意\(k\)个点的概率都是一样的,所以直接乘起来即可。

好,理解了上式,我们来仔细研究\(P\)和\(N\)到底是什么。

1.2 容斥/二项式反演

首先研究\(P\)。首先如果恰好选中某\(k\)个点,那么必然先得保证这\(i\)次都不能选中其他的点。概率是
\[
(\frac{k}{n})^i
\]

但这样计算显然是有问题的。因为可能出现有点一次都没有被选中的情况,而这不满足我们“恰好选中”的要求。换句话说,我们只能计算\(P(|被选中的点| \le p)\)。而实际上,我们需要的是 \(P(|选中的点集| = p)\)。

容易想到容斥掉它。

考虑枚举一次都没有被选中的点,经过仔细思考,我们能够艰难的得到
\[
P(选i次恰好选中某k个点) = \sum_{p=0}^k C_k^p (-1)^p (\frac{k-p}{n})^i
\]

我无力解释这个式子...各位自己尝试理解一下吧...

把某个点一次都没有被选中画成一个圆圈,用Venn图的形式可能有助于理解。

虽然难以理解...不过好在可以用二项式反演推导。
\[
f(n) = \sum_{i=0}^n C_n^i g(i) \iff g(n) = \sum_{i=0}^n C_n^i (-1)^i f(i)
\]

套入本题
\[
g_i(k) = P(选i次恰好选中某k个点)
\]

\[
f_i(k) = (\frac{k}{n})^i
\]

这样就好懂多了,,

不管怎么说,我们终于搞到了\(P\)表达式,而且这个表达式里\(i\)是指数!带回原式说不定可以用等比级数干掉它。

1.3 回到答案式

为了方便书写,令\(a_k = N(k个点未黑完整棵树)\)
\[
\begin{align}
Ans &= \sum_{i=0}^{\infty} \sum_{k=0}^{n} a_k \sum_{p=0}^k C_k^p (-1)^p (\frac{k-p}{n})^i \ &= \sum_{k=0}^{n} a_k \sum_{p=0}^k C_k^p (-1)^{k-p} \sum_{i=0}^{\infty} (\frac{p}{n})^i \\end{align}
\]

由于当\(|q| < 1\)时,有
\[
\begin{align}
\sum_{i=0}^{\infty} q^i &= \lim_{n \to \infty} \frac{q^{n+1}-1}{q-1} \ &= \frac{1}{1-q}
\end{align}
\]


\[
Ans = \sum_{k=0}^{n} a_k \sum_{p=0}^k C_k^p (-1)^{k-p} \frac{n}{n-p}
\]

(当\(k=p=n\)时,由于上一步转化要求\(|q|<1\),而此时\(|q|=1\),会出现级数发散的情况。但发现\(a_n\)显然一定等于\(0\),所以直接不算\(k=n\)的情况即可)

直接枚举是\(O(n^2)\)的,现在我们只需要求出每一个\(a_k = N(k个点未黑完整棵树)\)

1.4 树形dp

首先可以做一步简单容斥简化问题

\[
N(k个点未黑完整棵树) = C_n^k - N(k个点黑完整棵树)
\]

求\(N(k个点黑完整棵树)\),很容易想到树上背包

实际上就是在树上分配选点,也就是一个背包,而方案数背包的实质是卷积,所以就是用树形dp维护卷积合并。

开始写状态。

\(f[u][0/1][0/1]\)表示只考虑以\(u\)为根的子树,父亲是否被选择,自己是否被选择的方案数。(可能有更简单的状态表示,但我觉得这种更好想更靠谱)

随便写写就有转移方程了。

\[
f[u][0][0] = f[u][1][0] - \prod_v f[v][0][0]
\]

没有父亲援助,自己也不选,只能靠儿子。儿子节点只需要有一个选就可以养活自己。也就是儿子随便乱选减去儿子一个都不选的情况。
\[
f[u][0][1] = I * \prod_v (f[v][1][0] + f[v][1][1])
\]

自己选了,上下随便。
\[
f[u][1][0] = \prod_v ( f[v][0][0] + f[v][0][1] )
\]

父亲选了,自己不选,下面随便。
\[
f[u][1][1] = f[u][0][1]
\]

自己选了,上下随便。

照着dp即可。
\[
N(k个点黑完整棵树) = f[root][0][0][k]+f[root][0][1][k]
\]

顺着之前倒着带回去行了。

1.5 时间复杂度

1.5.1 答案式

显然是\(O(n^2)\)的。

1.5.2 树形dp

一次卷积\(O(n ^ 2)\),会向上合并\(O(n)\)次...

诶?这不是\(O(n^3)\)的吗?

实则不然。

设\(sz[u]\)为以\(u\)为根的子树大小。显然可能涉及的卷积长度\(len \le sz[u]\)

于是考虑每个节点\(u\)下的所有儿子\(v\)合并起来的耗时。
\[
\sum_{v_1 \not= v_2} sz[v_1] *sz[v_2] = sz[u]^2 - \sum_v sz[v]^2 \\]

后面减去的和式,将抵消掉所有儿子节点产生的时间复杂度!

所以真正的复杂度是\(O(n^2)\)

妙啊

1.6 总结

爆拆期望得无穷级数,尝试去掉无穷,套路化法将\(i\)化入\(P\)。

拆掉\(P\)在无穷与有限间建立联系,分别处理\(P\)和\(N\)。

\(P\)可容斥得出,带回原式用等比级数干掉无穷的\(i\)

\(N\)用树上背包可解,仔细推转移即可

Code

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#include<set>
typedef long long ll;
using namespace std;

ll Rd(){
    ll ans=0;char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') ans=ans*10+c-'0',c=getchar();
    return ans;
}

const ll MOD=998244353;
ll QPow(ll x,ll up){
    x=(x+MOD)%MOD;
    ll ans=1;
    while(up)
        if(up%2==0) x=x*x%MOD,up/=2;
        else ans=ans*x%MOD,up--;
    return ans;
}
ll Inv(ll x){
    return QPow(x,MOD-2);
}

const ll PTN=1005;
ll N;
ll fac[PTN],facInv[PTN];
void FacInit(){
    fac[0]=1;for(ll i=1;i<=N;i++) fac[i]=fac[i-1]*i%MOD;
    facInv[N]=Inv(fac[N]);for(ll i=N-1;i>=1;i--) facInv[i]=facInv[i+1]*(i+1)%MOD;
    facInv[0]=1;
}
ll C(ll n,ll m){
    if(n<m) return 0;
    return fac[n]*facInv[m]%MOD*facInv[n-m]%MOD;
}

struct Edge{
    ll u,v;ll nxt;
}edge[PTN*2];
ll graM,last[PTN];
void GraphInit(){graM=0;for(ll i=0;i<PTN;i++) last[i]=0;}
void AddBscEdge(ll u,ll v){
    edge[++graM]=(Edge){u,v,last[u]};
    last[u]=graM;
}
void AddDbEdge(ll u,ll v){
    AddBscEdge(u,v);AddBscEdge(v,u);
}

class Func{public:
    ll sav[PTN];ll len;
    Func(){}
    Func(ll len){
        this->len=len;
        for(ll i=0;i<=len;i++) sav[i]=0;
    }
    void Resize(ll nwLen){
        for(ll i=len+1;i<=nwLen;i++) sav[i]=0;
        len=nwLen;
    }
    ll& operator [] (ll idx){return sav[idx];}
    /*void Debug(){
        cout<<len<<":";
        for(ll i=0;i<=len;i++) cout<<sav[i]<<",";cout<<endl;
    }*/
};
Func operator + (Func A,Func B){
    Func C(max(A.len,B.len));
    A.Resize(C.len);B.Resize(C.len);
    for(ll i=0;i<=C.len;i++) C[i]=A[i]+B[i]%MOD;
    return C;
}
Func operator - (Func A,Func B){
    Func C(max(A.len,B.len));
    A.Resize(C.len);B.Resize(C.len);
    for(ll i=0;i<=C.len;i++) C[i]=A[i]-B[i]%MOD;
    return C;
}
Func operator * (Func A,Func B){
    Func C(A.len+B.len);
    for(ll i=0;i<=A.len;i++)
        for(ll j=0;j<=B.len;j++)
            C[i+j]=(C[i+j]+A[i]*B[j])%MOD;
    return C;
}
Func I(){
    Func A(1);A[1]=1;return A;
}
Func E(){
    Func A(0);A[0]=1;return A;
}
Func f[PTN][2][2];
void FDFS(ll u,ll father){
    Func s00,s00_01,s10_11;
    s00=s00_01=s10_11=E();
    for(ll i=last[u];i!=0;i=edge[i].nxt){
        ll v=edge[i].v;if(v==father) continue;
        FDFS(v,u);
        s00=s00*f[v][0][0];
        s00_01=s00_01*(f[v][0][0]+f[v][0][1]);
        s10_11=s10_11*(f[v][1][0]+f[v][1][1]);
    }
    f[u][0][0]=s00_01-s00;
    f[u][0][1]=I()*s10_11;
    f[u][1][0]=s00_01;
    f[u][1][1]=I()*s10_11;
}
ll A[PTN];
void Solve(){
    FDFS(1,0);
    for(ll k=0;k<=N;k++) A[k]=(C(N,k)-(f[1][0][0][k]+f[1][0][1][k])%MOD+MOD)%MOD;
    ll Ans=0;
    for(ll k=0;k<N;k++){//注意<N
        ll t=0;
        for(ll p=0;p<=k;p++){
            ll alpha;
            if((k-p)%2==0) alpha=1;
            else alpha=(-1+MOD)%MOD;
            t=(t+C(k,p)*alpha%MOD*N%MOD*Inv(N-p)%MOD)%MOD;
        }
        t=t*A[k]%MOD;
        Ans=(Ans+t)%MOD;
    }
    cout<<Ans;
}
int main(){
    N=Rd();FacInit();
    GraphInit();
    for(ll i=1;i<N;i++){
        ll u=Rd(),v=Rd();
        AddDbEdge(u,v);
    }
    Solve();
    return 0;
}

二、做法2(minmax容斥+dp)

2.1 minmax容斥

minmax容斥标准式:
\[
\max(S) = \sum_{T \subseteq S \\ T \not= \varnothing} (-1)^{|T|-1} \min(T)
\]

由于期望具有线性性,它可以拓展到期望:
\[
E(\max(S)) = \sum_{T \subseteq S \\ T \not= \varnothing} (-1)^{|T|-1} E(\min(T))
\]

然后把这个式子映射到本题中来。

\(S\)就是整棵树。下面把操作了多少次称为“时间”。

对于一种染色方案,集合中的元素可以看做,每一个点会在一个确定的时间从白色染成黑色,\(\max(S)\)就表示染黑集合中所有点的耗时;\(\min(T)\)表示至少染黑集合中的某一个点的耗时。

这时,我们可以直接遍历一道\(S\)得到\(\max(S)\)

套上期望后:\(E(\max(S))\)表示染黑集合中所有点的期望耗时,也就是本题所要求的答案;\(E(\min(T))\)表示至少染黑集合中的某一个点的期望耗时。

捋一下思路,由于期望不满足\(E(\max(S)) = \max_{x \in S} E(x)\),无法直接遍历求\(\max\)。但是因为期望具有线性性,可以借助minmax容斥来达到目的。 (这是一类套路题型)

2.2 处理\(E(\min(T))\)

于是思考\(E(\min(T))\)是否容易求得。容易发现
\[
E(\min(T)) = \frac{n}{|U_T|}
\]

\(|U_T|\)表示选择后能让\(T\)中某个节点变黑的点的集合。

显然\(S\)、\(T\)、\(U_T\)有这样的关系
\[
T \subseteq U_T \subseteq S
\]

将表达式带入原式中
\[
E(\max(S)) = \sum_{T \subseteq S \\ T \not= \varnothing} (-1)^{|T|-1} \frac{n}{|U_T|}
\]

枚举子集是复杂度的瓶颈。不过发现枚举中许多项的\(|U_T|\)都是相同的,考虑把它单独拿出来枚举。
\[
E(\max(S)) = \sum_{u=1}^n \frac{n}{u} \sum_{T \subseteq S \\ T \not= \varnothing \\ |U_T|=u} (-1)^{|T|-1}
\]

也就是说,现在我们只需快速求得
\[
\sum_{T \subseteq S \\ T \not= \varnothing \\ |U_T|=u} (-1)^{|T|-1}
\]

思考这式子的意义。其实它就是一个带上了容斥系数的所有\(T\)的方案数之和,也就是\(|T|\)为奇的方案数减去\(|T|\)为偶的方案数。

尝试通过树形dp解决

2.3 树形dp

首先简单考虑一下所需的状态。考虑以某个点为根的子树,我们需要记录\(|U_T|\),这是我们上面枚举的基础;\(|T|\)是容斥系数,我们需要记录它的奇偶性。

可以想到转移大概的形式是在\(U_T\)上的卷积。

2.3.1 状态压缩

首先有一个小Trick,可以压掉记录\(|T|\)奇偶性这一维
\[
\sum_{T \subseteq S \\ T \not= \varnothing \\ |U_T|=u} (-1)^{|T|-1} = - \sum_{T \subseteq S \\ T \not= \varnothing \\ |U_T|=u} (-1)^{|T|}
\]

根据上式,我们在dp时求\(|T|\)为偶的方案数减去\(|T|\)为奇的方案数,最后计算答案时乘上个\(-1\)即可。

这么做的原因是可以压缩掉记录\(|T|\)奇偶性这一维。

2.3.1.1 对于转移

比如有两个对象\(A=(x_1,y_1)\),\(B(x_2,y_2)\)需要合并为\(C\)(第1个参数表示\(|T|\)为偶的方案数,第2个参数表示\(|T|\)为奇的方案数)
\[
C = (x_1 x_2 + y_1 y_2, x_1 y_2 + x_2 y_1)
\]

如果将对象改写为单个变量记录:\(A=x_1 - y_1\),\(B = x_2 - y_2\)
\[
\begin{align}
C &= x_1 x_2 + y_1 y_2 - (x_1 y_2 + x_2 y_1) \&= (x_1 - y_1) (x_2 - y_2) \&= A B
\end{align}
\]

而对象的值就是我们所求的(考虑容斥系数的方案数)。成功压缩。

如果不做上面那一个Trick的转化,压缩状态需要 正*正=负 和 负*负=正,而这显然是不成立的。

2.3.1.2 对于新增

如果所有情况的\(T\)内新增一个节点,原来\(|T|\)为奇的变为偶,原来为偶的变为奇。

如果不压缩,操作应该是交换\(x\)和\(y\)

如果压缩,只需对dp值乘上\(-1\)即可

2.3.2 设置状态并处理转移

我们的dp实际上是用背包分配\(|U_T|\)。

状态压缩后,剩下的主要问题在于合并时\(|U_T|\)、\(|T|\)发生的变化对dp值造成的影响,而这变化与当前点\(u\)到底属于哪个集合密切相关。

由于转移情况复杂,而背包的本质是卷积,所以用封装性良好的卷积实现。

令\(f[u][d=0/1/2]\)表示仅考虑\(S\)是\(u\)为根的子树时,以\(x\)为下标的列表
\[
\{\sum_{T \subseteq S \\ T \not= \varnothing \\ |U_T|=x \\ u \in Q_d} (-1)^{|T|} \Large | \normalsize \ x \in [0,|S|]\}
\]

\(Q_d\)是对\(u\)的限制,具体为

\(Q_0\):\(u \in S\)且\(u \not \in U_T\)

\(Q_1\):\(u \in U_T\)且\(u \not \in T\)

\(Q_2\):\(u \in T\)

定义\(I\),\(I[1]=1\)?,其余皆为\(0\),结合卷积可以表示向\(U_T\)内新增一个点。

我们开始处理转移。
\[
f[u][0] = \prod_{v} f[v][0] + f[v][1]
\]

意思是若\(u\)不在\(U_T\)里,则它的所有儿子一定不能在\(T\)里。
\[
f[u][1] = \left( \left( \prod_v f[v][0] + f[v][1] +f[v][2] \right) - f[u][0] \right) * I
\]

若\(u\)在\(U_T\)内而\(u\)不在\(T\)内,则\(u\)的儿子中至少有一个是\(T\)中的点。这可以转化为所有情况的答案减去所有儿子都不在\(T\)中的答案。最后卷上\(I\)为\(U_T\)新增\(u\)。
\[
f[u][2] = - \left( I * \prod_v \left( f[v][0] * I + f[v][1] + f[v][2] \right) \right)
\]

若\(u\)在\(T\)内,那些不在\(U_T\)内的\(u\)的儿子现在就应该属于\(T\)了。然后既然\(u\)属于\(T\),那么\(u\)也属于\(U_T\),卷上\(I\)为\(U_T\)新增\(u\)。最后,由于\(T\)新增了个点,也就是说所有项的容斥系数\(+1\),整体变号即可。

最后算答案,参考早前化出的答案式即可。

2.4 时间复杂度

答案式部分显然\(O(n)\)

树形dp卷积的时间复杂度为\(O(n^2)\),见做法1对dp的时间复杂度证明。

2.5 总结

首先我们发现这道题适用于minmax容斥的套路,于是将难求的\(E(max(S))\)转化到容易求的\(E(min(T))\)。

然后我们再想办法优化枚举子集,发现\(U_T\)是一个关键的变量,于是将其提出来单独枚举,问题转化为求带有容斥系数的方案数。

回头观察题面发现是树状结构,必然有其特殊性质,于是猜想用树形dp解决。讨论合并时\(U_T\)和\(T\)是如何变化的,能够列出转移方程式。在中间想到了压缩状态,简化了dp。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
ll Rd(){
    ll ans=0;char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') ans=ans*10+c-'0',c=getchar();
    return ans;
}

const ll MOD=998244353;
ll QPow(ll x,ll up){
    x=(x+MOD)%MOD;
    ll ans=1;
    while(up)
        if(up%2==0) x=x*x%MOD,up/=2;
        else ans=ans*x%MOD,up--;
    return ans;
}
ll Inv(ll x){
    return QPow(x,MOD-2);
}

const ll PTN=1E3+5;
struct Edge{
    ll u,v;ll nxt;
}edge[PTN*2];
ll N,graM,last[PTN];
void GraphInit(){graM=0;for(ll i=0;i<PTN;i++) last[i]=0;}
void AddBscEdge(ll u,ll v){
    edge[++graM]=(Edge){u,v,last[u]};
    last[u]=graM;
}
void AddDbEdge(ll u,ll v){
    AddBscEdge(u,v);AddBscEdge(v,u);
}

class Func{public:
    ll sav[PTN];
    ll len;
    ll& operator [] (ll idx){return sav[idx];}
    Func(){}
    Func(ll len){
        this->len=len;
        for(ll i=0;i<=len;i++) sav[i]=0;
    }
    void Expand(ll nwLen){
        for(ll i=len+1;i<=nwLen;i++) sav[i]=0;
        len=nwLen;
    }
    Func operator - (){
        Func B;B.len=len;
        for(ll i=0;i<=len;i++) B[i]=(-sav[i]+MOD)%MOD;
        return B;
    }
    /*void Debug(){
        cout<<len<<":";
        for(ll i=0;i<=len;i++) cout<<sav[i]<<',';cout<<endl;
    }*/
};
Func E(){
    Func A(0);A[0]=1;
    return A;
}
Func I(){
    Func A(1);A[1]=1;
    return A;
}
Func operator + (Func A,Func B){
    Func C(max(A.len,B.len));
    A.Expand(C.len);B.Expand(C.len);
    for(ll i=0;i<=C.len;i++) C[i]=(A[i]+B[i])%MOD;
    return C;
}
Func operator - (Func A,Func B){
    Func C(max(A.len,B.len));
    A.Expand(C.len);B.Expand(C.len);
    for(ll i=0;i<=C.len;i++) C[i]=(A[i]-B[i]+MOD)%MOD;
    return C;
}
Func operator * (Func A,Func B){
    Func C(A.len+B.len);
    for(ll i=0;i<=A.len;i++)
        for(ll j=0;j<=B.len;j++)
            C[i+j]=(C[i+j]+A[i]*B[j])%MOD;
    return C;
}

Func f[PTN][3];
void DFS(ll u,ll fa){
    f[u][0]=f[u][1]=f[u][2]=E();
    for(ll i=last[u];i!=0;i=edge[i].nxt){
        ll v=edge[i].v;if(v==fa) continue;
        DFS(v,u);
        f[u][0]=f[u][0]*(f[v][0]    +f[v][1]);
        f[u][1]=f[u][1]*(f[v][0]    +f[v][1]+f[v][2]);
        f[u][2]=f[u][2]*(f[v][0]*I()+f[v][1]+f[v][2]);
    }
    f[u][1]=(f[u][1]-f[u][0])*I();
    f[u][2]=-(f[u][2]*I());
}
void Solve(){
    DFS(1,0);
    ll Ans=0;
    for(ll i=1;i<=N;i++){//注意从1开始,因为minmax容斥不包含空集
        Ans=(Ans+N*Inv(i)%MOD*(f[1][0][i]+f[1][1][i]+f[1][2][i]))%MOD;
    }
    cout<<(-Ans+MOD)%MOD;
}
int main(){
    N=Rd();
    GraphInit();
    for(ll i=1;i<N;i++){
        ll u=Rd(),v=Rd();
        AddDbEdge(u,v);
    }
    Solve();
    return 0;
}

三、总结

我觉得两种解法都很妙啊(

解题思路很具有参考价值,实为一道期望好题!

原文地址:https://www.cnblogs.com/sun123zxy/p/12008794.html

时间: 2024-11-08 02:32:11

涂色游戏 题解的相关文章

涂色游戏

Description 在一个1*N的格子上,每个格子可以选择涂成红色或蓝色. 求至少 M 个连续为红色的方案数. Input 多组输入,每组输入包含两个整数M和N  (0<N,M<100000) Output 输出存在至少连续M个为红色的方案数,对1000000007取模 Sample Input 3 4 Sample Output 3 这道题有点意思,基本就是核电站问题的翻版 基本思路就是,还是和核电站一样,要考虑前几个格子避免连续n个红格子的思路 嗯,忘了刚才那句吧,我再说一遍 f[i]

[BZOJ 1260][CQOI2007]涂色paint 题解(区间DP)

[BZOJ 1260][CQOI2007]涂色paint Description 假设你有一条长度为5的木版,初始时没有涂过任何颜色.你希望把它的5个单位长度分别涂上红.绿.蓝.绿.红色,用一个长度为5的字符串表示这个目标:RGBGR. 每次你可以把一段连续的木版涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色.例如第一次把木版涂成RRRRR,第二次涂成RGGGR,第三次涂成RGBGR,达到目标. 用尽量少的涂色次数达到目标. Input 输入仅一行,包含一个长度为n的字符串,即涂色目标.字符串中的

hdu 4559 涂色游戏(SG)

在一个2*N的格子上染色 每次可以染1*1的格子 或者2*2的格子 最涂满所有格子的人胜 m为已染色格子的个数 Alice先手 1*1的点SG值为1 SG[i]表示连续2*i的SG值 sg[3] 就是连续2*3空白格子 假如n = 3 涂一个1*1个格子的情况 那么后继状态就可能是 状态1 连续0列的格子+连续2列的格子+剩余那一列涂1个格子状态2 连续1列的格子+连续1列的格子+剩余那一列涂1个格子 涂一个2*3个格子的情况 那么后继状态就可能是 状态1 连续0列的格子+连续1列的格子+剩余的

BZOJ 1260 [CQOI2007]涂色paint(区间DP)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1260 [题目大意] 假设你有一条长度为n的木版,初始时没有涂过任何颜色 每次你可以把一段连续的木版涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色 求最少的涂色次数达到目标状态 [题解] dp[i][j]表示涂抹i到j的最优答案, 显然当i和j相同时,可以从i+1……j,i……j-1,i+1……j-1转移过来, 同时也可以从两个区间组合得到. [代码] #include <cstdio>

【DP】BZOJ 1260: [CQOI2007]涂色paint

1260: [CQOI2007]涂色paint Time Limit: 30 Sec  Memory Limit: 64 MBSubmit: 893  Solved: 540[Submit][Status][Discuss] Description 假设你有一条长度为5的木版,初始时没有涂过任何颜色.你希望把它的5个单位长度分别涂上红.绿.蓝.绿.红色,用一个长度为5的字符串表示这个目标:RGBGR. 每次你可以把一段连续的木版涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色.例如第一次把木版涂成R

1260: [CQOI2007]涂色paint

Description 假设你有一条长度为5的木版,初始时没有涂过任何颜色.你希望把它的5个单位长度分别涂上红.绿.蓝.绿.红色,用一个长度为5的字符串表示这个目标:RGBGR. 每次你可以把一段连续的木版涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色.例如第一次把木版涂成RRRRR,第二次涂成RGGGR,第三次涂成RGBGR,达到目标. 用尽量少的涂色次数达到目标. Input 输入仅一行,包含一个长度为n的字符串,即涂色目标.字符串中的每个字符都是一个大写字母,不同的字母代表不同颜色,相同的字

P1283 平板涂色

P1283 平板涂色 题目描述 CE数码公司开发了一种名为自动涂色机(APM)的产品.它能用预定的颜色给一块由不同尺寸且互不覆盖的矩形构成的平板涂色. 为了涂色,APM需要使用一组刷子.每个刷子涂一种不同的颜色C.APM拿起一把有颜色C的刷子,并给所有颜色为C且符合下面限制的矩形涂色: 为了避免颜料渗漏使颜色混合,一个矩形只能在所有紧靠它上方的矩形涂色后,才能涂色.例如图中矩形F必须在C和D涂色后才能涂色.注意,每一个矩形必须立刻涂满,不能只涂一部分. 写一个程序求一个使APM拿起刷子次数最少的

codevs 1512 转向游戏 题解

Codevs 1512转向游戏 题解 时间限制: 1 s 空间限制: 1000 KB 题目等级 : 白银 Silver 题解 题目描述 Description 小明自认为方向感很好,请小红来测试.小红先让小明面对北方立正站好,然后发出"向左转""向右转"或"向后转"的命令.每个命令执行后,小明都正确地说出了他面对的方向.小红的命令共N个(1≤n≤10000),请你统计小明说[南]的次数. 命令是以数字方式表达: 0---向左转 1---向右转 2

POJ 1129 Channel Allocation(暴力搜--涂色问题)

Channel Allocation Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 13295   Accepted: 6806 Description When a radio station is broadcasting over a very large area, repeaters are used to retransmit the signal so that every receiver has a s