zoukankan      html  css  js  c++  java
  • 校内集训20180927

    T1(Loj2154):

    一共两行的扫雷游戏,第一行没雷,第二行没数,现在给出第一行的N个数,问第二行的雷有多少种可能的摆放方式。N<=10^4。

     

    题解:

    由于每一个格子有没有雷只会与它正上方的三个格子中的数有关,每个数最多只有3,可以考虑一遍平推式dp求出答案。

    设dp[i][0/1][0/1][0/1]表示处理到第i个格子,该格子前三个有或者没有雷,每次判断第i-1个格子是否合法并转移。

    考场上考虑到这就可以写了,100pts。

    但其实精通扫雷的同学会发现一个厉害的性质:如果只有一行雷并且知道上一行的数是什么,

    那么只要确定了前两个格子有没有雷,就可以通过每个格子的数推出后面所有格子是否有雷。

    换句话说,只要枚举前两个格子有没有雷,后面所有格子就要么无解,要么解唯一。

     

    代码:

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    
    using namespace std;
    #define MAXN 100005
    #define MAXM 500005
    #define INF 0x7fffffff
    #define ll long long
    
    int A[MAXN],B[MAXN];
    int num[4][2]={{0,0},{1,0},{0,1},{1,1}};
    inline int read(){
        int x=0,f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar())
            if(c=='-')
                f=-1;
        for(;isdigit(c);c=getchar())
            x=x*10+c-'0';
        return x*f;
    }
    
    int main(){
        int N=read(),ans=0;
        for(int i=1;i<=N;i++) A[i]=read();
        for(int k=0;k<4;k++){
            bool flag=0;
            int n1=num[k][0],n2=num[k][1];
            B[1]=n1,B[2]=n2;
            if(n1+n2!=A[1]) continue;
            for(int i=2;i<=N;i++)
                B[i+1]=A[i]-B[i-1]-B[i];
            for(int i=1;i<=N;i++)
                if(B[i-1]+B[i]+B[i+1]!=A[i] || B[i]<0 || B[i-1]<0 || B[i+1]<0 || B[i]>1 || B[i-1]>1 || B[i+1]>1)
                    {flag=1;break;}
            if(B[0]!=0 || B[N+1]!=0) flag=1;
            if(!flag) ans++;
        }
        printf("%d
    ",ans);
        return 0;
    }

     

    T2(Loj2424):

    有两个仅包含小写字母的字符串A和B,现在要从字符串A中取出k个互不重叠的非空子串,然后把这k个子串按照其在字符串A中出现的顺序依次连接起来得到一个新的字符串,请问有多少方案可以使得这个新串与B相等?

    |A|<=1000,|B|<=200,k<=|B|。

     

    题解:

    一般来说类似于两个串取子串的问题,dp是一种解法。

    设dp[i][j][k][0/1]表示A取到i,B匹配到j,取了k个串,A[i]这个字符不取/取的方案数。

    • 若取这个字符,则需要A[i]==B[j],取的时候可以继承上一个串或者新开一个串。那么得到dp[i][j][k][1]=dp[i-1][j-1][k][1]+dp[i-1][j-1][k-1][0/1]。
    • 若不取这个字符,那么当前字符对答案没有影响,得到dp[i][j][k][0]=dp[i-1][j][k][0/1]。

    我们发现最后一维[0/1]这一种状态多次出现,[0]在转移时压根没出现,那么可以将[0]改成取不取均可的方案数进行转移。

    但这样dp内存会过大,注意到dp[i]只与dp[i-1]有关,那么我们可以把第一维压掉。

    NOIP2015D2T2就做完了,100pts。

    代码:

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    
    using namespace std;
    #define MAXN 1005
    #define MAXM 205
    #define INF 0x7fffffff
    #define ll long long
    #define mod 1000000007
    
    char A[MAXN],B[MAXM];
    ll dp[MAXM][MAXM][2];
    ll tp[MAXM][MAXM][2];
    inline ll read(){
        ll x=0,f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar())
            if(c=='-')
                f=-1;
        for(;isdigit(c);c=getchar())
            x=x*10+c-'0';
        return x*f;
    }
    
    int main(){
        ll N=read(),M=read(),K=read();
        cin>>A+1>>B+1;
        dp[0][0][0]=1;
        for(ll i=1;i<=N;i++){
            memcpy(tp,dp,sizeof(dp));
            for(ll j=1;j<=M;j++)
                for(ll k=1;k<=K;k++){
                    if(A[i]==B[j]) tp[j][k][1]=(dp[j-1][k][1]+dp[j-1][k-1][0])%mod;
                    else tp[j][k][1]=0;
                    tp[j][k][0]=(tp[j][k][1]+dp[j][k][0])%mod;
                    //cout<<i<<" "<<j<<" "<<k<<" "<<tp[j][k][0]<<endl;
                }
            memcpy(dp,tp,sizeof(tp));
        }
        printf("%lld
    ",dp[M][K][0]%mod);
        return 0;
    }

    T3(Loj6185):

    求N个点组成的每个点度数不超过4且根节点度数不超过3的有根树的个数。N<-400。

     

    题解:

    从“每个点度数不超过4且根节点度数不超过3”这句话我们就可以发现处理完大小为n的树后往上连一条边变为某棵树的子树依然是满足条件的。这给了我们dp转移的提示。

    设dp[n]表示有多少棵大小为n的树满足要求,由于根节点最多有三棵子树可以直接枚举三棵子树的大小i,j,k(人为规定顺序i<=j<=k)。

    然后我在考场上开心的写出了dp[n]+=dp[i]*dp[j]*dp[k]这个转移方程。拿到了0pts。

    因为子树是无序的,那么如果有两棵子树相等,dp[i]*dp[j]就必定会出现重复状态(i中第一个状态+j中第二个状态和i中第二个状态+j中第一个状态被认为是同样的)。

    所以我们需要分类讨论子树大小是否会出现相等的情况。

    • 如果i==j==k,三棵子树大小全部相等,那么相当于从dp[i]中任取三个状态,可以重复取的方案数。

        此时设第i种状态取了xi个,有∑xi=3。相当于在3个物品中插入dp[i]-1个板使其分成dp[i]份,每份可以为空。

        容易得到dp[n]+=C(dp[i]+3-1,dp[i]-1)=C(dp[i]+3-1,3)。

        (这也是可重复组合数的模型,即从{a}的n个元素中取出r个元素,可以重复取的方案数=C(n+r-1,n-1))。

    • 如果i==j!=k,相当于从dp[i]中任取两个状态的方案数*dp[k]。dp[n]+=C(dp[i]+2-1,2)*dp[k]。
    • 如果i!=j==k,相当于从dp[j]中任取两个状态的方案数*dp[i]。dp[n]+=C(dp[j]+2-1,2)*dp[i]。
    • 如果i!=j!=k,所有状态都可以随意组合,dp[n]+=dp[i]*dp[j]*dp[k]。

     

    代码:

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    
    using namespace std;
    #define MAXN 100005
    #define MAXM 15
    #define INF 0x7fffffff
    #define mod 1000000007
    #define ll long long
    
    ll dp[MAXN],inv[MAXN];
    inline ll read(){
        ll x=0,f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar())
            if(c=='-')
                f=-1;
        for(;isdigit(c);c=getchar())
            x=x*10+c-'0';
        return x*f;
    }
    
    inline ll C(ll x,ll y){
        ll ans=1,ans1=1;
        for(ll i=y;i>=1;i--) ans1*=i;
        for(ll i=x;i>=x-y+1;i--) ans*=i%mod,ans%=mod;
        return ans*inv[ans1]%mod;    
    }
    
    int main(){
        ll N=read();inv[0]=0;inv[1]=1;dp[0]=1;
        for(ll i=2;i<=MAXM;i++) inv[i]=(-inv[mod%i]*(mod/i)%mod+mod)%mod;
        for(ll n=1;n<=N;n++)
            for(ll i=0;i<=N;i++)
                for(ll j=i;j<=N;j++){
                    ll k=n-1-i-j;
                    if(k<j || k<i) break;
                    if(i==k) dp[n]+=C(dp[i]+3-1,3)%mod,dp[n]%=mod;
                    else if(i==j) dp[n]+=C(dp[i]+2-1,2)%mod*dp[k]%mod,dp[n]%=mod;
                    else if(j==k) dp[n]+=C(dp[j]+2-1,2)%mod*dp[i]%mod,dp[n]%=mod;
                    else dp[n]+=dp[i]%mod*dp[j]%mod*dp[k]%mod,dp[n]%=mod;
                }        
        printf("%lld
    ",dp[N]);
        return 0;
    }
    /*
    */
  • 相关阅读:
    剑指offer(14)链表中倒数第K个节点
    剑指offer(13)调整数组顺序使奇数位于偶数前面
    跨域资源共享CORS
    同源政策
    剑指offer(12)数值的整数次方
    剑指offer(11)二进制中1的个数
    面试金典——交点
    LeetCode——简化路径
    LeetCode——跳跃游戏 I-II
    LeetCode——最大矩形
  • 原文地址:https://www.cnblogs.com/YSFAC/p/9746222.html
Copyright © 2011-2022 走看看