SRM 671

总体来说难度适中的一场SRM~

300:BearCries

题意:

给定一个长度为N(N<=200)的字符串 字符串中只含有”;”和”_“两种字符 一个合法的Cry表情为两个”;”中间含有至少一个”_“
一个合法Cry表情为字符串的一个子串(不必在原串中连续)
求题目中给出的字符串分解成若干个合法Cry表情的方案数(原字符串中的每个字符都必须出现在某个Cry表情中 并且只能出现一次)

题解:

问题可以由dp解决
我们考虑状态f[i][j][k]表示当前考虑到第I个字符 前面有J个”;”还未匹配”_“ 有K个”;”已经匹配至少一个”_“
考虑第i+1个字符
若第i+1个字符为; 我们可以分两种情况讨论

1.他和前面的某个已经至少匹配了一个”_“的”;”组合成一个合法的Cry表情
2.他不和前面的”;”匹配 他成为一个新的还未匹配”_“的”;”

若第i+1个字符为_ 我们也可以分两种情况讨论

1.他和前面某个还未匹配”_“的”;”组合成为一个已经匹配至少一个”_“的”;”
2.他和前面某个已经至少匹配一个”_“的”;”组合 仍然为一个至少匹配一个”_“的”;”

到这里问题已经得到解决 答案即为f[n][0][0]

代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#include <map>
#include <string>
#include <set>

using namespace std;

typedef long long LL;

class BearCries
{
    public:
    int count(string s)
    {
        int n=s.size();
        LL f[205][205][205];
        int a[205];
        LL mod=1e9+7;
        int ans;
        for (int i=0;i<n;++i) if (s[i]==';') a[i+1]=1;else a[i+1]=0;
        memset(f,0,sizeof(f));
        f[0][0][0]=1;
        for (int i=1;i<=n;++i)
            for (int j=0;j<=n;++j)
                for (int k=0;k<=n;++k)
                {
                    // f[j][k]  j not has k  has
                    if (a[i]==1)
                    {
                        f[i][j+1][k]+=f[i-1][j][k],f[i][j+1][k]%=mod;
                        if (k>=1) f[i][j][k-1]+=k*f[i-1][j][k]%mod,f[i][j][k-1]%=mod;
                    }
                    else
                    {
                        f[i][j][k]+=k*f[i-1][j][k]%mod,f[i][j][k]%=mod;
                        if (j>=1) f[i][j-1][k+1]+=j*f[i-1][j][k]%mod,f[i][j-1][k+1]%=mod;    
                    }
                }
        cout<<f[1][1][0]<<endl;
        ans=f[n][0][0];
        return ans;
    }
};

500: BearDarts

题意:

给定一个含有N(N<=1000)个元素的数组w 求有多少四元组(i,j,p,q)满足

  • i<j<p<q
  • $w[i] \times w[p]$=$w[j] \times w[q]$

题解

为了方便我们令$a=w[i]$ $b=w[j]$ $c=w[p]$ $d=w[q]$
我们先来枚举a和b 那么问题就变成了有多少对(c,d)满足条件
$a \times c$=$b \times d$
于是 $a / b$=$d / c$
于是我们可以枚举a,b,查找后面有多少个数对满足条件啦!
具体实现和我的上一篇日志CF 325 的D题很像,用个map就好啦

代码

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#include <map>
#include <string>
#include <set>

using namespace std;

typedef long long LL;

class BearDarts
{
    public:
    long long count(vector <int> w)
    {
        int n,a,b,c,d,z;
        map<pair<int,int>,LL> mp;
        LL ans=0;
        n=w.size();
        c=w[n-2];d=w[n-1];
        z=__gcd(c,d);
        mp[make_pair(c/z,d/z)]++;
        for (int i=n-3;i>=1;--i)
        {
            b=w[i];
            for (int j=i-1;j>=0;--j)
            {
                a=w[j];
                //ac=bd
                //a/b=d/c
                z=__gcd(a,b);
                ans+=mp[make_pair(b/z,a/z)];        
            }
            c=w[i];
            for (int j=i+1;j<n;++j)
            {
                d=w[j];
                z=__gcd(c,d);
                mp[make_pair(c/z,d/z)]++;
            }
        }
        return ans;
    }
};

900: BearDestroys

题意:

给一个n$\times$m的方格图(n<=13,m<=30),每个格子可以写E或者S,所以一共有2^(n$\times$m)种图
一个人从(0,0)开始行优先行动 即从(0,0)->(0,m-1)->(1,0)->(1,m-1)->….->(n-1,0)->(n-1,m-1)
每次他到一个格子(i,j) 他执行以下操作:
1.如果该格子已经放置了骨牌 那么不做任何操作去下一格子
2.若该格子没有放置骨牌 该格子写的是E 那么如果他能放置一块1$\times$2的骨牌覆盖(i,j) (i,j+1) 则用1$\times$2的骨牌覆盖(i,j)(i,j+1) 否则如果他能放置一块2$\times$1的骨牌覆盖(i,j) (i+1,j) 则用2$\times$1的骨牌覆盖(i,j) (i+1,j)
3.若该格子没有放置骨牌 该格子写的是S 那么如果他能放置一块2$\times$1的骨牌覆盖(i,j) (i+1,j) 则用2$\times$1的骨牌覆盖(i,j)(i+1,j) 否则如果他能放置一块1$\times$2的骨牌覆盖(i,j) (i,j+1) 则用1$\times$2的骨牌覆盖(i,j) (i,j+1)

直到他到达(n-1,m-1)

一个图的得分为放置的骨牌数量

求这2^(n$\times$m)种图的得分的和对MOD取余

题解:

看到n<=13不难想到要状态压缩,然后我们来分析下放置骨牌的情况
我们考虑一下骨牌占得两个格子的位置 可以发现无论是1$\times$2的骨牌还是2$\times$1的骨牌 两个格子的坐标之和的差总是1

所以我们考虑将方格图分层
比如一个3$\times$4的方格图 可以分为6层(0~5)

0123
1234
2345

于是每层有最多N个元素 我们来对这N个元素进行状态压缩

对于一个状态now 若now的二进制表示第I位为1 则表示该格子已经被骨牌覆盖了 如果为0则为没覆盖

那么考虑dp:
f[i][j]表示到了第I层 第I层的状态为J时候的骨牌数量的总和
g[i][j]表示到了第I层 第I层的状态为J时候这种图的数量

考虑转移的时候比较麻烦 这里考虑从now状态可以转移到哪些next状态
对now状态每一行进行讨论

若now状态第r行为1 此时证明改格子已经被覆盖 那么他填E或S都无所谓 直接转移

接下来就是该格子未被覆盖的情况了

如果现在改格子右方已被覆盖(即next状态第r行为1)

此时无论该格子填E还是填S 他都是被2$\times$1的骨牌覆盖 将next状态第r+1行标志为1进行转移

如果现在该格子右方未被覆盖

此时如果该格子填E那么将next状态第R行标志为1进行转移

此时如果该格子填S那么将next状态第R+1行标志位1进行转移

这样进行转移更新后答案即为f[n+m-1][0]

具体的转移以及边界看代码有一些帮助

代码

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <string>

using namespace std;

typedef long long LL;

class BearDestroys
{
    public:
    int w,h,mod;
    int f[50][10000];
    int g[50][10000];
    void dfs(int r,int sum,int now,int next,int ff,int gg)
    {
        if (r==h)
        {
            f[sum+1][next]=(f[sum+1][next]+ff)%mod;
            g[sum+1][next]=(g[sum+1][next]+gg)%mod;
            return;
        }
        int c=sum-r;
        if (0<=c&&c<=w-1)
        {
            if (now&(1<<r))
            {
                dfs(r+1,sum,now,next,ff*2%mod,gg*2%mod);
            }
            else
            {
                if ((c==w-1)||(next&(1<<r)))
                {
                    if (r==h-1)
                    {
                        dfs(r+1,sum,now,next,ff*2%mod,gg*2%mod);
                    }
                    else
                    {
                        dfs(r+1,sum,now,next+(1<<(r+1)),(ff*2%mod+gg*2%mod)%mod,gg*2%mod);
                    }
                }
                else
                {
                    if (r==h-1)
                    {
                        dfs(r+1,sum,now,next+(1<<r),(ff*2%mod+gg*2%mod)%mod,gg*2%mod);
                    }    
                    else
                    {
                        dfs(r+1,sum,now,next+(1<<r),(ff+gg)%mod,gg);
                        dfs(r+1,sum,now,next+(1<<(r+1)),(ff+gg)%mod,gg);
                    }
                }    
            }
        }
        else
        {
            dfs(r+1,sum,now,next,ff,gg);
        }
    }
    int sumUp(int W, int H, int MOD)
    {
        w=W;h=H;mod=MOD;
        int s=w-1+h-1;
        memset(f,0,sizeof(f));
        memset(g,0,sizeof(g));
        f[0][0]=0;g[0][0]=1;
        for (int i=0;i<=s;++i)
        {
            for (int j=0;j<(1<<h);++j)
            {
                if (f[i][j]==0&&g[i][j]==0) continue;
                dfs(0,i,j,0,f[i][j],g[i][j]);
            }
        }
        return f[s+1][0];
    }
};