1. 程式人生 > >【考試記錄】20180927

【考試記錄】20180927

ostream 代碼 lan 我們 eof 條件 上一個 滿足 當前

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\n",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\n",dp[M][K][0]%mod);
    return 0;
}

T3(Loj6185):

求N個點組成的每個點度數不超過4且根節點度數不超過3的有根樹的個數。

題解:

從“每個點度數不超過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\n",dp[N]);
    return 0;
}
/*
*/

【考試記錄】20180927