【考試記錄】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\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