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; } /* */