序:除了刚开始的看了几道题的题解,后来也自己肛出了几道 剩下不可做的题不也没做吗
这些题目最大的特点是在于需要自己构造状态,这往往会成为一道题的最大卡点 穷举表示水不到几分
题目选讲:
E:
如果直接模拟,复杂度为 k∗n2
既然每一步只能往上下或往左右走,
那么我们可以把题目分解为在x轴上行走k步与在y轴上行走k步的方案数都处理出来 k∗n
然后枚举往左右走i步,往上下走k-i步,然后再用组合处理(常规套路)
#include
#include
#define P 9999991
int dp[2][1005][1005];
long long Sum[2][1005];
int C[1005][1005];
int main(){
int T;
scanf("%d",&T);
for(int i=1;i<=1000;i++){
C[i][0]=C[i][i]=1;
for(int j=1;j1][j]+C[i-1][j-1])%P;
}
}
for(int cas=1;cas<=T;cas++){
int n,m,k,x0,y0;
memset(dp,0,sizeof(dp));
memset(Sum,0,sizeof(Sum));
scanf("%d%d%d%d%d",&n,&m,&k,&x0,&y0);
dp[0][0][x0]=1;
dp[1][0][y0]=1;
Sum[1][0]=1;
Sum[0][0]=1;
for(int j=1;j<=k;j++){
for(int i=1;i<=n;i++){
if(i>1)dp[0][j][i]+=dp[0][j-1][i-1];
if(i>2)dp[0][j][i]+=dp[0][j-1][i-2];
if(i0][j][i]+=dp[0][j-1][i+1];
if(i+10][j][i]+=dp[0][j-1][i+2];
dp[0][j][i]%=P;
Sum[0][j]+=dp[0][j][i];
Sum[0][j]%=P;
}
}
for(int j=1;j<=k;j++){
for(int i=1;i<=m;i++){
if(i>1)dp[1][j][i]+=dp[1][j-1][i-1];
if(i>2)dp[1][j][i]+=dp[1][j-1][i-2];
if(i<m)dp[1][j][i]+=dp[1][j-1][i+1];
if(i+1<m)dp[1][j][i]+=dp[1][j-1][i+2];
dp[1][j][i]%=P;
Sum[1][j]+=dp[1][j][i];
Sum[1][j]%=P;
}
}
long long ans=0;
for(int i=0;i<=k;i++){
ans+=Sum[0][i]*Sum[1][k-i]%P*C[k][i]%P;
ans%=P;
}
printf("Case #%d:\n%lld\n",cas,ans);
}
return 0;
}
F:
又是一道 关于位运算的题目,一看到数据范围,就可以揣测出大概复杂度
求两个序列,正着扫一遍,倒着扫一遍。然后枚举两点(左序列的右端点,右序列的左端点),进行运算,这样复杂度为 O(n3) 加一个前缀和优化就是 O(n2) 了
#include
#include
#define P 1000000007
const int M=1023;
int A[1005];
long long dp[2][1005][M+5],dp1[2][1005][M+5];
long long Sum[1005][M+5];
int main(){
int T;
scanf("%d",&T);
while(T--){
memset(dp,0,sizeof(dp));
memset(dp1,0,sizeof(dp1));
memset(Sum,0,sizeof(Sum));
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&A[i]);
dp[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=M;j++){
dp[0][i][j]+=dp[1][i-1][j]+dp[0][i-1][j];
dp[0][i][j]%=P;
}
for(int j=0;j<=M;j++){
int t=A[i]^j;
dp[1][i][t]+=dp[0][i-1][j]+dp[1][i-1][j];
dp[1][i][t]%=P;
}
}
dp1[0][n+1][M]=1;
for(int i=n;i>=1;i--){
for(int j=0;j<=M;j++){
dp1[0][i][j]+=dp1[1][i+1][j]+dp1[0][i+1][j];
dp1[0][i][j]%=P;
}
for(int j=0;j<=M;j++){
int t=j&A[i];
dp1[1][i][t]+=dp1[0][i+1][j]+dp1[1][i+1][j];
dp1[1][i][t]%=P;
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=M;j++){
Sum[i][j]=Sum[i-1][j]+dp[1][i][j];
Sum[i][j]%=P;
}
}
long long ans=0;
for(int j=0;j<=M;j++){
for(int i=1;i1][i+1][j]%P;
ans%=P;
}
}
printf("%lld\n",ans);
}
return 0;
}
G:
像这种两维的题目可以存一维,枚举一维
定义dp两维,一维是枚举到哪一列,另一维是有几行是空着的
对于不同的情况,进行填充空行,或者是填充填过的行
#include
using namespace std;
const int M=55,P=1000000007;
long long dp[55][55],C[M][M],Pow[M];
void init(){
for(int i=1;i<=50;i++){
C[i][0]=C[i][i]=1;
for(int j=1;j1][j]+C[i-1][j-1])%P;
}
}
Pow[0]=1;
for(int i=1;i<=50;i++)Pow[i]=Pow[i-1]*2%P;
}
int main(){
init();
int n,m;
while(~scanf("%d %d",&n,&m)){
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++)dp[1][i]=1;
for(int i=1;i<=n;i++)dp[i][1]=1;
for(int i=2;i<=n;i++){
for(int j=2;j<=m;j++){
dp[i][j]+=1ll*dp[i][j-1]*(Pow[i]-1)%P;
dp[i][j]%=P;
for(int k=1;k1]*C[i][k]%P*Pow[i-k]%P;
dp[i][j]%=P;
}
}
}
printf("%lld\n",dp[n][m]);
}
return 0;
}
H:
这道题的难度相比于上面的题目略大
在树形dp的套路上还要加上组合数
虽然说是1-n的序列,但我们只用处理他们之间的大小关系就好了
可以证明:任意大小关系都可被不同的序列表示
在合并两个子树的时候,我们已知子树自身节点的大小关系,但并不知道两棵树之间的大小关系,且这个关系是确定的,那么就可以乘上 C(size(A)size(A)+size(B))
合并完子树之后,再加入根节点,根节点作为最大值时 dp[i]+=dp[i−1]
不然就是 dp[i]+=dp[i]∗size(A) 。 size(A) 为以某节点根的树的的节点个数
#include
#include
#include
using namespace std;
#define P 1000000007
vector<int>edge[1005];
long long dp[1005][1005];//i节点,j个最大值
long long dp1[1005][1005];
int C[1005][1005];
int degree[1005],n;
void dfs(int x,int f){
bool flag=0;
int cnt=0;
for(int k=0;k<(int)edge[x].size();k++){
int y=edge[x][k];
if(y==f)continue;
dfs(y,x);
if(!flag)for(int i=1;i<=n;i++)dp[x][i]=dp[y][i];
else {
for(int i=1;i<=n;i++){
if(!dp[x][i])continue;
for(int j=1;j<=n;j++){
if(!dp[y][j])continue;
dp1[x][i+j]+=dp[x][i]*dp[y][j]%P*C[cnt+degree[y]][degree[y]]%P;
dp1[x][i+j]%=P;
}
}
for(int i=1;i<=n;i++){
dp[x][i]=dp1[x][i];
dp1[x][i]=0;
}
}
cnt+=degree[y];
flag=1;
}
degree[x]=cnt+1;
if(!flag)dp[x][1]=1;
else {
for(int i=1;i<=n;i++)dp1[x][i]=dp[x][i];
for(int i=1;i<=n;i++)dp[x][i]=dp[x][i]*(degree[x]-1)%P;
for(int i=1;i<=n;i++){dp[x][i+1]+=dp1[x][i];dp1[x][i]=0;dp[x][i+1]%=P;}
}
}
void Init(){
for(int i=0;i<=1000;i++){
C[i][0]=C[i][i]=1;
for(int j=1;jif(i)C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
}
}
}
int main(){
int T,t=0;
scanf("%d",&T);
Init();
while(T--){
int k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)edge[i].clear();
memset(dp,0,sizeof(dp));
memset(dp1,0,sizeof(dp1));
for(int i=1;iint x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
edge[y].push_back(x);
}
dfs(1,0);
printf("Case #%d: %lld\n",++t,dp[1][k]);
}
return 0;
}
K: 若直接根据题意模拟取了几个数,然后用组合数来计算,复杂度为 n3
然后思考优化,对于满足条件的一组数,保证至少取两个数,且不去两个数就好了,那么dp只用存 选到哪个数 累和为多少 不选几个数 选几个数
由于不选2个数和不选3.4….n个数的效果都是一样的,所以只用存0-2就好了
同理与选几个数
#include
#include
#define P 1000000007
int dp[1005][1005][3][3];//选到那个数 得到的值 可以不选 必须选
int A[1005];
void Add(int &x,int y){
x+=y;
if(x>=P)x-=P;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
memset(dp,0,sizeof(dp));
int n,s;
scanf("%d%d",&n,&s);
for(int i=1;i<=n;i++)scanf("%d",&A[i]);
dp[0][0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=s;j++){
for(int k=0;k<=2;k++){
Add(dp[i][j][0][k],dp[i-1][j][0][k]);
Add(dp[i][j][1][k],dp[i-1][j][0][k]);
Add(dp[i][j][1][k],dp[i-1][j][1][k]);
Add(dp[i][j][2][k],dp[i-1][j][1][k]);
Add(dp[i][j][2][k],dp[i-1][j][2][k]);
}
if(j+A[i]<=s){
for(int k=0;k<=2;k++){
Add(dp[i][j+A[i]][k][0],dp[i-1][j][k][0]);
Add(dp[i][j+A[i]][k][1],dp[i-1][j][k][1]);
Add(dp[i][j+A[i]][k][1],dp[i-1][j][k][0]);
Add(dp[i][j+A[i]][k][2],dp[i-1][j][k][1]);
Add(dp[i][j+A[i]][k][2],dp[i-1][j][k][2]);
}
}
}
}
int ans=0;
for(int i=1;i<=s;i++)Add(ans,dp[n][i][2][2]);
printf("%lld\n",1ll*ans*4%P);
}
return 0;
}
M:
是一道裸的递推题
可以预处理出用j种颜色染i个格子的方案数(第二类斯特林数)
然后枚举第一行用几种颜色,第二行用几种颜色
同时对于染色的方案再乘上颜色数量的全排
然后就可以在 O(n2) 的时间内算出答案
#include
#define M 2005
#define P 1000000007
long long dp[M][M],C[M][M],A[M];
void Init(){
dp[0][0]=1;
for(int i=1;i<=2000;i++){
for(int j=1;j<=2000;j++){
dp[i][j]=dp[i-1][j]*j+dp[i-1][j-1];
dp[i][j]%=P;
}
}
for(int i=1;i<=2000;i++){
C[i][0]=C[i][i]=1;
for(int j=1;j1][j]+C[i-1][j-1];
C[i][j]%=P;
}
}
A[0]=1;
for(int i=1;i<=2000;i++){
A[i]=A[i-1]*i%P;
}
}
int main(){
Init();
int T;
scanf("%d",&T);
while(T--){
int n,m;
long long ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
for(int j=1;i+j<=m;j++){
ans+=dp[n][i]*A[i]%P*C[m][i]%P*dp[n][j]%P*A[j]%P*C[m-i][j]%P;
ans%=P;
}
}
printf("%lld\n",ans);
}
return 0;
}