Corn Fields
题意:一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛,即牛与牛不能相邻。问有多少种放牛方案(一头牛都不放也是一种方案)
#include<stdio.h>
#include<string.h>
int map[15];
int st[1<<13];
int dp[15][1<<13];
int judge(int x,int y)
{
return map[x]&st[y];
}
int main()
{
int m,n;
while(scanf("%d %d",&m,&n)!=EOF)
{
int i,j;
memset(map,0,sizeof(map));
memset(st,0,sizeof(st));
memset(dp,0,sizeof(dp));
for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
{
int x;
scanf("%d",&x);
if(x==0) map[i]+=1<<(j-1);
}
}
int k=0;
for(i=0;i<1<<n;i++)
{
if(i&(i<<1)) continue;
st[k++]=i;
}
int l=k;
for(i=0;i<l;i++)
{
if(!judge(1,i))
dp[1][i]=1;
}
for(i=2;i<=m;i++)
{
for(j=0;j<l;j++)
{
if(!judge(i,j))
{
for(k=0;k<l;k++)
{
if(!judge(i-1,k))
{
if(!(st[j]&st[k]))
{
dp[i][j]+=dp[i-1][k];
}
}
}
}
}
}
int ans=0;
for(i=0;i<l;i++)
{
ans+=dp[m][i];
ans%=1000000000;
}
printf("%d\n",ans);
}
return 0;
}
炮兵阵地
题意:一个方格组成的矩阵,每个方格可以放大炮用0表示,不可以放大炮用1表示(原题用字母),让放最多的大炮,大炮与大炮间不会互相攻击。
#include<stdio.h>
#include<string.h>
int map[110]; //压缩后的地图
int st[70]; //符合条件的状态
int v[70]; //相对于st[i]存在阵地的个数
int dp[110][70][70]; //dp[i][j][k]:第i行其状态为j,第i-1行的状态为k的最大值
int max(int x,int y)
{
return x<y?y:x;
}
int judge1(int i,int j) //第i行和第j种状态是否匹配
{
return map[i]&st[j];
}
int judge2(int i,int j) //两种状态是否匹配
{
return st[i]&st[j];
}
int fun(int k) //根据st[k]求v[k]
{
int i=0;
while(k)
{
if(k%2) i++;
k/=2;
}
return i;
}
int main()
{
int n,m;
while(scanf("%d %d",&n,&m)!=EOF)
{
getchar();
int i,j,k,f;
char c;
memset(map,0,sizeof(map));
memset(st,0,sizeof(st));
memset(dp,0,sizeof(dp));
memset(v,0,sizeof(v));
for(i=0;i<n;i++) //压缩地图
{
for(j=0;j<m;j++)
{
scanf("%c",&c);
if(c=='H') map[i]+=1<<j;
}
getchar();
}
int l=0;
for(i=0;i<1<<m;i++) //求状态和其相对应的个数
{
if(i&i<<1) continue;
if(i&i<<2) continue;
st[l++]=i;
v[l-1]=fun(i);
}
for(i=0;i<l;i++) //初始化第一行
{
if(judge1(0,i)) continue;
for(j=0;j<l;j++)
{
dp[0][i][j]=v[i];
}
}
for(i=0;i<l;i++) //初始化第二行
{
if(judge1(1,i)) continue;
for(j=0;j<l;j++)
{
if(judge2(i,j)) continue;
dp[1][i][j] = max(dp[1][i][j],dp[0][j][0]+v[i]);
}
}
for(i=2;i<n;i++) //dp求解
{
for(j=0;j<l;j++)
{
if(judge1(i,j)) continue;
for(k=0;k<l;k++)
{
if(judge1(i-1,k)) continue;
if(judge2(k,j)) continue;
for(f=0;f<l;f++)
{
if(judge1(i-2,f)) continue;
if(judge2(j,f)) continue;
if(judge2(k,f)) continue;
dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][f]+v[j]);
}
}
}
}
int ans=0;
for(i=0;i<l;i++) //求最大值ans
{
for(j=0;j<l;j++)
{
if(ans<dp[n-1][i][j]) ans=dp[n-1][i][j];
}
}
printf("%d\n",ans);
}
return 0;
}
Hie with the Pie
原来路径不是对称的!sad
题意:类似于TSP问题,只是每个点可以走多次,比经典TSP问题不同的是要先用弗洛伊的预处理一下两两之间的距离。求最短距离。
#include<stdio.h>
#include<string.h>
int dis[12][12]; //图
int dp[12][1<<12]; //dp[i][j]:在第i个点的时候j状态最优解
int min(int x,int y)
{
return x<y?x:y;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF&&n)
{
int i,j,k;
memset(dp,0,sizeof(dp));
for(i=0;i<=n;i++)
{
for(j=0;j<=n;j++)
{
scanf("%d",&dis[i][j]);
}
}
for(k=0;k<=n;k++) //flody重新算两点之间的最短距离
{
for(i=0;i<=n;i++)
{
for(j=0;j<=n;j++)
{
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
int s;
/*其实我还是想不通为什么要s打头,
可能s递增表明了到达的点的个数递增,
直到最后完成遍历*/
for(s=0;s<(1<<n);s++) //枚举所有状态,用位运算表示
{
for(i=1;i<=n;i++)
{
if(s&(1<<(i-1))) //状态S中已经过城市i
{
if(s==(1<<(i-1)))//状态S只经过城市I,最优解自然是从0出发到i的dis,这也是DP的边界
dp[i][s]=dis[0][i];
else //如果S有经过多个城市
{
dp[i][s]=1<<30;
for(j=1;j<=n;j++)
{
if(s&(1<<(j-1))&&i!=j) //枚举不是城市I的其他城市
dp[i][s]=min(dp[i][s],dp[j][s^(1<<(i-1))]+dis[j][i]); //在没经过城市I的状态中,寻找合适的中间点J使得距离更短
}
}
}
}
}
int ans=dp[1][(1<<n)-1]+dis[1][0];
for(i=2;i<=n;i++)
{
ans=min(ans,dp[i][(1<<n)-1]+dis[i][0]);
}
printf("%d\n",ans);
}
return 0;
}
Travelling
三进制,但是做法和上面一题一样
题意:10个点的TSP问题,但是要求每个点最多走两边,不是只可以走一次,所以要用三进制的状态压缩解决这个问题。可以预处理每个状态的第k位是什么。
#include<stdio.h>
#include<string.h>
int tri[12]={1,3,9,27,81,243,729,2187,6561,19683,59049}; //3^i
int map[15][15];
int dig[59049][11]; //dig[s][i]表示对于状态s而言第i位是多少
int dp[59049][11]; //dp[s][i]表示在第i个点的时候s状态最优解
int min(int x,int y)
{
return x<y?x:y;
}
int main()
{
int n,m;
int i,j,k,l;
memset(dig,0,sizeof(dig));
for(i=0;i<59049;i++) //求dig
{
int k=i;
for(j=0;j<11;j++)
{
dig[i][j]=k%3;
k/=3;
if(k==0) break;
}
}
while(scanf("%d %d",&n,&m)!=EOF)
{
memset(map,-1,sizeof(map));
for(i=0;i<m;i++)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
if(map[x][y]==-1)
map[x][y]=map[y][x]=z;
else if(map[x][y]>z)
map[x][y]=map[y][x]=z;
}
for(i=1;i<=n;i++) map[i][i]=0;
memset(dp,-1,sizeof(dp));
int s;
for(s=0;s<tri[n];s++) //一共3^n种
{
for(i=1;i<=n;i++)
{
if(!dig[s][i-1]) continue; //判断第i个点是否在状态s中
for(j=1;j<=n;j++) //遍历s,判断s是否就i一个点
{
if(j==i) continue;
if(dig[s][j-1]) break;
}
if(j==n+1) //就一个点就初始化为0
{
dp[s][i]=0;
continue;
}
dp[s][i]=1<<30; //还有很多点,就初始化为最大值
int w=0; //用w来表示s状态中第i个点少经过一次的状态
for(j=0;j<11;j++)
{
if(j==i-1) w+=tri[j]*(dig[s][j]-1);
else w+=tri[j]*dig[s][j];
}
for(j=1;j<=n;j++)
{
if(dp[w][j]!=-1&&map[j][i]!=-1)
dp[s][i]=min(dp[s][i],dp[w][j]+map[j][i]); //状态转移方程
}
}
}
int ans=1<<30;
for(i=0;i<tri[n];i++)
{
int min=1<<30;
for(j=0;j<n;j++)
{
if(!dig[i][j]) break;
if(dp[i][j+1]!=-1&&min>dp[i][j+1]) min=dp[i][j+1];
}
if(j==n&&ans>min) ans=min; //保证所有点都经过
}
if(ans==1<<30) puts("-1");
else printf("%d\n",ans);
}
return 0;
}
Islands and Bridges
做法和前面的一样......
#include<stdio.h>
#include<string.h>
int v[15];
int map[15][15];
int dp[1<<13][15][15];
__int64 way[1<<13][15][15];
int max(int x,int y)
{
return x<y?y:x;
}
int main()
{
int q;
while(scanf("%d",&q)!=EOF)
{
while(q--)
{
int n,m;
int i,j,k;
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++) scanf("%d",&v[i]);
memset(map,0,sizeof(map));
for(i=0;i<m;i++)
{
int u,v;
scanf("%d %d",&u,&v);
map[u][v]=map[v][u]=1;
}
if(n==1) //只有一个点的时候,特判
{
printf("%d 1\n",v[1]);
continue;
}
int s;
memset(dp,-1,sizeof(dp));
memset(way,0,sizeof(way));
for(s=0;s<(1<<n);s++)
{
for(i=1;i<=n;i++)
{
if(!(s&1<<(i-1))) continue;
for(j=1;j<=n;j++)
{
if(i==j) continue;
if(!map[i][j]) continue;
if(!(s&1<<(j-1))) continue;
if(dp[s][i][j]==-1&&s==((1<<(i-1))+(1<<(j-1)))) //初始化
{
dp[s][i][j]=v[i]+v[j]+v[i]*v[j];
way[s][i][j]=1;
continue;
}
for(k=1;k<=n;k++)
{
if(i==k||k==j) continue;
if(!map[j][k]) continue;
if(!(s&1<<(k-1))) continue;
if(dp[s^1<<(i-1)][j][k]==-1) continue;
int temp=dp[s^1<<(i-1)][j][k]+v[i]+v[i]*v[j];
if(map[i][k]) temp+=v[i]*v[j]*v[k]; //没看到这个条件wa到死啊
if(dp[s][i][j]<temp)
{
way[s][i][j]=way[s^1<<(i-1)][j][k];
dp[s][i][j]=temp;
}
else if(dp[s][i][j]==temp)
way[s][i][j]+=way[s^1<<(i-1)][j][k];
}
}
}
}
int ans=-1;
__int64 num=0;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(map[i][j])
{
if(ans<dp[(1<<n)-1][i][j])
{
ans=dp[(1<<n)-1][i][j];
num=way[(1<<n)-1][i][j];
}
else if(ans==dp[(1<<n)-1][i][j])
num+=way[(1<<n)-1][i][j];
}
}
}
if(ans==-1)puts("0 0"); //走不了的时候
else printf("%d %I64d\n",ans,num/2);
}
}
return 0;
}
Most Powerful
这道题敲繁了,只要一维dp就可以了.....
不想写注释了,感觉都差不多
题意:不超过10种气体,两两之间相互碰撞可以产生一定的能量,如a碰b,那么b气体就消失,自身不能碰自身,问最后所能得到的最大能量。
#include<stdio.h>
#include<string.h>
int map[15][15];
int dp[1<<10][15]; //dp[s][i]在状态为s的情况下,i是最后一个消失的最优值
int max(int x,int y)
{
return x<y?y:x;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF&&n)
{
int i,j,k;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
scanf("%d",&map[i][j]);
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++) dp[1<<10-1][i]=0;
int s;
for(s=(1<<n)-1;s>0;s--)
{
for(i=1;i<=n;i++)
{
if(s&1<<(i-1)) continue;
for(j=1;j<=n;j++)
{
if(!(s&1<<(j-1))) continue;
dp[s][i]=max(dp[s][i],map[j][i]);
for(k=1;k<=n;k++)
{
if(s&1<<(k-1)||k==i) continue;
dp[s][i]=max(dp[s][i],dp[s+(1<<(i-1))][k]+map[j][i]);
}
}
}
}
int ans=-1;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(i==j) continue;
ans=max(ans,dp[1<<(i-1)][j]);
}
}
printf("%d\n",ans);
}
return 0;
}
Mondriaan's Dream
dp思想倒不是很繁,但是转化为0和1的|和&深深的发现对于运算符的运用还是不够啊
题意:一个矩阵,只能放1*2的木块,问将这个矩阵完全覆盖的不同放法有多少种。
思路:如果是横着的就定义11,竖着的定义为竖着的01,这样按行dp只需要考虑两件事,当前行&上一行,是不是全为1,不是说明竖着有空(不可能出现竖着的00),另一个要检查当前行里有没有横放的,但为奇数的1。
#include<stdio.h>
#include<string.h>
int n,m;
int st[1<<11];
__int64 dp[1<<11][15]; //dp[s][i]表示在第i行时状态为s的时候有多少个
int check(int s) //连续的1出现为偶数个(横着放)?
{
int i;
int k=0;
while(s)
{
if(s%2==0)
{
if(k%2==1) return 0;
k=0;
}
else k++;
s>>=1;
}
if(k%2) return 0;
return 1;
}
int match(int s,int t) //前一行的状态t和当前行的状态s是否匹配
{
if((s|t)!=(1<<m)-1) return 0; //出现竖着00的情况
return check(s&t); //s&t能得到横着的1的情况
}
int main()
{
int i,j,k,s,t;
for(s=0;s<(1<<11);s++)
{
if(check(s)) st[s]=1;
else st[s]=0;
}
while(scanf("%d %d",&n,&m)!=EOF&&n+m)
{
memset(dp,0,sizeof(dp));
for(s=0;s<(1<<m);s++) //第一行初始化
if(st[s]) //优化,减少运算
dp[s][1]=1;
for(i=2;i<=n;i++)
{
for(s=0;s<(1<<m);s++)
{
for(t=0;t<(1<<m);t++)
{
if(!match(s,t)) continue;
dp[s][i]+=dp[t][i-1];
}
}
}
printf("%I64d\n",dp[(1<<m)-1][n]);
}
return 0;
}