2019年6月28日、学校のテストT3 [コンサート]道路千万

ビッグそれを説明するための質問の意味の下部を参照するには、目、およびことがわかっ値羅区P1310式は、おそらくいくつかの計算記号が与えられている、非常に、のように、真の確率のための最終的な表現するように、偽の確率は何ですかQwQ〜;

それでは、この問題は?つまり、すべてのオペレータは、オペレータの最後の操作として言及で滑って、その後、我々は左の演算子を計算する数式の束に行き、その後、右の計算式の束、そして最終的には最後のカウント動作千万人を対応する算術演算子!

だから、どのようにこの式の表現とその左側にオペレーターの権利を計算しますか?私たちは、その後、左側のオペレータの式から最後の操作のほとんどの演算子を見つけて、その左、その右の式の計算に方程式を計算し......

これは、再帰的プロセスの列挙+右QwQであることがわかった〜

我々は長さに再帰式を有するまでは1であり、今回、それは「T」真に対応する式である場合、それは「F」である場合、それは、式が偽である場合に対応します;

[I] [j]は式の間隔[I、J]はプログラム番号偽で示しfは、我々は、[I] [j]は[I、J]は、プログラム番号真で発現間隔を表す2つの配列Tを開き次の境界を設定します。

    以下のためにint型 i = 1 ; iが<= N; iは++します
    {
        IF(Sは、[I] == ' T '// のための真溶液を貢献
        {
            t[i][i]=1,f[i][i]=0;
        }
        else                        //否则就贡献一个为false的方案 
        {
            f[i][i]=1,t[i][i]=0;
        }
    }
//s数组里存的是't' or 'f' 

那么最终的答案就是:

然后 t 和 f 都是遍历区间内所有的运算符,假设作为最后一个运算,根据运算符的规则,递归求左子区间和右子区间的答案,相乘。最后把所有可能的情况累加起来,返回。
就是一个递归的思路,这里用了记忆化的原理,开数组将每种情况的方案数存起来。
但是时间复杂度还是比较高,对于这个题还是会炸!
那么这个题的正解就是:区间DP!
参考我们当时做 洛谷P1040 加分二叉树的思路(不知道的先看一下 这里):
我们也是推出了对于每个数字,让它作为根结点,然后算左子树,算右子树,最后再算整体的;这个思路不是和本题很相似嘛?
所以我们仍然可以套上之前我们区间DP的做法:
按照区间长度从小到大开始,枚举每个区间,算出每个区间内表达式为true的方案数,为false的方案数;
利用我们之前求出的小区间,然后一步一步得推出更大区间的答案,最后覆盖整个大区间的答案;
 
先献上区间DP的一般套路:
    for(int len=2;len<=n;len++)          //枚举区间长度 
        for(int i=1;i+len-1<=n;i++)       //枚举区间左端点的位置 
           {
            int j=i+len-1;                        //计算出区间右端点的位置 
            for(int k=i;k<j;k++)              //枚举区间内的中间点 
                …………… 
            }

已经确定了要使用区间DP了,接下来的任务就是要找状态转移方程了(下面的过程就和分析P1310表达式的值 的时候几乎一样了);

题目中只有三种运算符:

与&,或 |,异或 ^,先了解一下它们的运算规则:

&:要使 x&y=1,x和y必须都为1;要使 x&y=0,x和y有一个为0;

| :要使x | y=1,x和y有一个为1;要使x | y=0,x和y必须都为0;

^:要使x ^ y=1,x和y必须不相同(即x=0,y=1或 x=1,y=0);要使x ^ y=0,x和y必须相同(即x=0,y=0或 x=1,y=1);

然后我们就可以根据它们的运算法则来确定状态转移方程啦(还运用了乘法计数原理):

    for(int len=2;len<=n;len++)          //枚举区间长度 
    {
        for(int i=1;i+len-1<=n;i++)      //枚举区间左端点的位置 
        {
            int j=i+len-1;               //计算出区间右端点的位置 
            for(int k=i;k<j;k++)         //枚举区间内的中间点的运算符号 
            {
                if(ops[k]=='&')          //&运算 
                {
                    t[i][j]=(t[i][j]+(t[i][k]*t[k+1][j])%mod)%mod;    //必须两个都为true 
                    f[i][j]=(f[i][j]+(t[i][k]*f[k+1][j])%mod+(f[i][k]*t[k+1][j])%mod+(f[i][k]*f[k+1][j])%mod)%mod;  //只需要其中一个为false 
                }
                if(ops[k]=='|')          //|运算 
                {
                    t[i][j]=(t[i][j]+(t[i][k]*f[k+1][j])%mod+(f[i][k]*t[k+1][j])%mod+(t[i][k]*t[k+1][j])%mod)%mod;  //只需要其中一个为true 
                    f[i][j]=(f[i][j]+(f[i][k]*f[k+1][j])%mod)%mod;    //必须两个都为false 
                }
                if(ops[k]=='^')          //异或运算 
                {
                    t[i][j]=(t[i][j]+(t[i][k]*f[k+1][j])%mod+(f[i][k]*t[k+1][j])%mod)%mod;   //必须两个不同 
                    f[i][j]=(f[i][j]+(f[i][k]*f[k+1][j])%mod+(t[i][k]*t[k+1][j])%mod)%mod;   //必须两个相同 
                }
            }
        }
    }

 

注意到我们的答案是需要取模的,而最后的答案还涉及到了除法,所以最后我们还需要一步求乘法逆元的过程!

这里我用的是扩展欧几里得求逆元,这个大家应该都会吧QwQ~
上代码喽!
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int X=0,w=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if (c=='-')
        {
            w=-1;
        }
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        X=(X<<3)+(X<<1)+c-'0';
        c=getchar();
    }
    return X*w;
}
inline char gc()
{
    char c;
    do
    {
        c=getchar();
    }
    while(c==' '||c=='\n'||c=='\r'||c=='\0'||c=='\t');
}
int n;
long long x,y;
char s[501],ops[501];
long long t[501][501],f[501][501];
const int mod=998244353;
void exgcd(long long a,long long b,long long &x,long long &y)   //扩展欧几里得求逆元 
{
    if(b==0)
    {
        x=1;y=0;return ; 
    }
    exgcd(b,a%b,x,y);
    long long r=x;
    x=y;
    y=r-(a/b)*x;
} 
int main()
{
    n=read();
    for(int i=1;i<=n-1;i++)
    {
        s[i]=gc();         //存't' or 'f' 
        ops[i]=gc();       //存运算符 
    }
//这么读入的原因是注意到't','f'是和运算符一个一个隔开的 
    s[n]=gc();
    for(int i=1;i<=n;i++)
    {
        if(s[i]=='t')      //边界条件 
        {
            t[i][i]=1,f[i][i]=0;
        }
        else
        {
            f[i][i]=1,t[i][i]=0;
        }
    }
    for(int len=2;len<=n;len++)          //枚举区间长度 
    {
        for(int i=1;i+len-1<=n;i++)      //枚举区间左端点的位置 
        {
            int j=i+len-1;               //计算出区间右端点的位置 
            for(int k=i;k<j;k++)         //枚举区间内的中间点 
            {
                if(ops[k]=='&')          //&运算 
                {
                    t[i][j]=(t[i][j]+(t[i][k]*t[k+1][j])%mod)%mod;    //必须两个都为true 
                    f[i][j]=(f[i][j]+(t[i][k]*f[k+1][j])%mod+(f[i][k]*t[k+1][j])%mod+(f[i][k]*f[k+1][j])%mod)%mod;  //只需要其中一个为false 
                }
                if(ops[k]=='|')          //|运算 
                {
                    t[i][j]=(t[i][j]+(t[i][k]*f[k+1][j])%mod+(f[i][k]*t[k+1][j])%mod+(t[i][k]*t[k+1][j])%mod)%mod;  //只需要其中一个为true 
                    f[i][j]=(f[i][j]+(f[i][k]*f[k+1][j])%mod)%mod;    //必须两个都为false 
                }
                if(ops[k]=='^')          //异或运算 
                {
                    t[i][j]=(t[i][j]+(t[i][k]*f[k+1][j])%mod+(f[i][k]*t[k+1][j])%mod)%mod;   //必须两个不同 
                    f[i][j]=(f[i][j]+(f[i][k]*f[k+1][j])%mod+(t[i][k]*t[k+1][j])%mod)%mod;   //必须两个相同 
                }
            }
        }
    }
    exgcd(t[1][n]+f[1][n],mod,x,y);
    cout<<t[1][n]*((x+mod)%mod)%mod;
    return 0;
}

 

おすすめ

転載: www.cnblogs.com/xcg123/p/11105768.html