算法就不解释了,直接上题
题目:http://codevs.cn/problem/1647/
这老经典的题了嚎~~~
首先,我们来看看m,(m<=10),
这样,我们就可以考虑动态规划,
并且,如果我们把它划分成n层的话,那么第i层只跟第i-1层和第i-2层有关。
设第i层的决策状态为ki,则
a[i,ki,ki-1]=max(a[i-1,ki-1,ki-2]+num[ki])
对于每个状态,可以考虑状态压缩,
事先把每个状态都预处理一遍,储存在二进制数里。
二进制数中'0'表示没有放置炮台,'1'表示放置了炮台。
再把每一层的地形存在二进制数里,'0'表示平原,'1'表示山地。
设map[i]为第i层的地形,
这样,对于第i(i>=3)层,首先枚举ki-2,使得k[i-2] & map[i-2]=0
再枚举ki-1,使得k[i-1] & map[i-1]=0且k[i-2] & k[i-1]=0,
再枚举ki,使得k[i] & map[i]=0且k[i] & k[i-1]=0且k[i] & k[i-2]=0(反过来也没关系啦)
代码上~:
#include <cstdio>
#include <iostream>
using namespace std;
int f[105][65][65],map[105],zbk[1050],num_zbk[1050];
char ch[150];
int main()
{
int i,j,k,l,n,m,x,num,ans=0;
freopen("a.txt","r",stdin);
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
{
scanf("%s",&ch);
for (j=1;j<=m;j++)
if (ch[j-1]=='H')
map[i]|=1<<(j-1);//map[i]记录第i行
}
num=0;
for (i=0;i<=(1<<m)-1;i++)//i=0~111..1
{
if ((i&(i<<1)) || (i&(i<<2)))//不能有相邻1~2个1
continue;
x=0;
for (j=1;j<=m;j++)//找i的二进制1个数
if (i&(1<<(j-1)))
x++;
zbk[++num]=i;//zbk[]记录每行的二进制状态
num_zbk[num]=x;//状态下该行炮台个数
}
//前面为预处理。。。
for (i=1;i<=num;i++)//第一行
if ((map[1]&zbk[i])==0)
f[1][i][1]=num_zbk[i];
//第二行
for (i=1;i<=num;i++) //枚举两层状态:第二层
if ((map[2]&zbk[i])==0)
for (j=1;j<=num;j++)//第一层
if ((map[1]&zbk[j])==0 && (zbk[i]&zbk[j])==0)
f[2][i][j]=max(f[2][i][j],f[1][j][1]+num_zbk[i]);
for (i=3;i<=n;i++)//3~n层
for (j=1;j<=num;j++)//第i层状态
if ((map[i]&zbk[j])==0)
for (k=1;k<=num;k++)//i-1层状态
if ((map[i-1]&zbk[k])==0 && (zbk[j]&zbk[k])==0)
for (l=1;l<=num;l++)//i-2层状态
if ((map[i-2]&zbk[l])==0 && (zbk[k]&zbk[l])==0 && (zbk[l]&zbk[j])==0)
f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+num_zbk[j]);
for (i=1;i<=num;i++)
{
for (j=1;j<=num;j++)
ans=max(ans,f[n][i][j]);
}
printf("%d",ans);
return 0;
}
熟悉了算法如何写以后,再来看看这道题:
题目:http://www.gdfzoj.com/oj/contest/254/problems/3
看到n<=20我就试着做了下状压,发现可以实现,结果只有40。。。(第一次还忘了删文操。。。),才发现看错题了。。。
所以所以,看题!看题!!看题!!!(重要的事情说三遍)
思路不多说,直接上代码:
代码细节:
1)此处find≈lower_bound(pair不能用lower_bound)
找第一个x
2)注意pair定义,使用等。。。
T3:http://www.lydsy.com/JudgeOnline/problem.php?id=1087
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxSize=23;
int a[maxSize],n;
pair <long long,long long> c[1<<maxSize];//写sort不用cmp,比map好
long long find(long long x)
{
int l=1,r=(1<<n)-1,mid;
while (l!=r)
{
mid=(l+r)/2;
if (c[mid].first>=x)//写等于可以找第一个x
r=mid;
else
l=mid+1;
}
return l;
}
int main()
{
long long i,j,sum,ans=0;
freopen("a.txt","r",stdin);
scanf("%d",&n);
for (i=0;i<n;i++)
scanf("%d",&a[i]);
for (i=1;i<(1<<n);i++)//预处理一遍和
{
sum=0;
for (j=0;j<n;j++)
if (i&(1<<j))
sum+=a[j];
c[i]=make_pair(sum,i);
}
sort(c+1,c+i);//排序(按和)
for (i=1;i<(1<<n);i++)
{
sum=0;
for (j=0;j<n;j++)//再找一遍和(排序后的,与前面预处理不重复)
if (i&(1<<(j)))
sum+=a[j];
if (sum%2==1) continue;
for (j=find(sum/2);c[j].first==sum/2;j++)//二分找sum/2
if ((c[j].second&i)==c[j].second && c[j].second!=i)//sum/2 的状态属于sum状态子集
{
ans++;
break;//看题!!!!!!!!!
}
}
printf("%lld",ans);
return 0;
}
T3:http://www.lydsy.com/JudgeOnline/problem.php?id=1087
这题跟第一题很像。。。
注意预处理、答案大小(long long)
#include <cstdio>
#include <algorithm>
using namespace std;
int zbk[1050],num_zbk[1050];
long long f[15][1050][150];//f[i,j,k]记录第i层为j状态,前i行放k个的方案数
//要开ll,可以预估一下答案大小
int main()
{
int i,j,k,n,k1,num=0,x,l;
long long ans;
freopen("a.txt","r",stdin);
scanf("%d%d",&n,&k1);
for (i=0;i<(1<<n);i++)//预处理
{
if (i&(i<<1))
continue;
x=0;
for (j=0;j<n;j++)
if (i&(1<<j))
x++;
zbk[++num]=i; num_zbk[num]=x;
}
// for (i=1;i<n;i++)
// for (j=1;j<=num;j++)
// f[i][j][0]=1;//初始化 k=0时的情况
for (i=1;i<=num;i++)//第一层
if (num_zbk[i]<=k1)
f[1][i][num_zbk[i]]=1;
for (i=2;i<=n;i++)//第2~n层
{
for (j=1;j<=num;j++)//第i层状态
{
for (k=1;k<=num;k++)//第i-1层状态
if (!(zbk[j]&(zbk[k]<<1)) && !(zbk[j]&zbk[k])&&!(zbk[k]&(zbk[j]<<1)))
for (l=num_zbk[j];l<=k1;l++)//循环'k'
f[i][j][l]+=f[i-1][k][l-num_zbk[j]];
}
}
ans=0;
for (i=1;i<=num;i++)
ans+=f[n][i][k1];
printf("%lld",ans);//记得打lld
return 0;
}