因为行数很小,我们可以一列一列的考虑。对于每一列的方法要受到前一列的影响,dp[i][j]表示第i列对第i+1列的影响为j的方案数,比如当n=5,j的二进制为01100时,表示当前列的第三个和第四个位置已经被之前占了。我们dfs遍历当前列的每一行。
#include<bits/stdc++.h>
using namespace std;
const int maxn=10;
const int maxm=1007;
int dp[maxm+7][(1<<maxn)];//dp[i][j]表示第i列对第i+1的影响为j
int n,m;
void dfs(int l,int h,int cur,int nex)
{
if(h==n)
{
dp[l+1][nex]+=dp[l][cur];
return ;
}
if(((1<<h)&cur)>0) dfs(l,h+1,cur,nex); //当前位置被占了,就直接dfs下一行
if(((1<<h)&cur)==0) dfs(l,h+1,cur,nex|(1<<h));//横着放
if(h+1<n&&(((1<<h)&cur)==0)&&(((1<<(h+1))&cur)==0)) dfs(l,h+2,cur,nex);//竖着放
return;
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
scanf("%d%d",&n,&m);
dp[1][0]=1;
for(int i=1;i<=m;i++)
for(int j=0;j<(1<<n);j++)
{
if(dp[i][j]){
dfs(i,0,j,0);
}
}
printf("%d\n",dp[m+1][0]);
return 0;
}
dp[i][j]表示经过状态j后在第i个人手中的最小代价。对于状态j,假设总共有5个人,二进制01100表示经过第3和第4个人的状态。
#include<bits/stdc++.h>
using namespace std;
int n;
int a[27][27];
int dp[20][1<<16];//dp[i][j]表示经过状态j后在i的最小代价
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
scanf("%d",&n);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&a[i][j]);
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<n;i++)
dp[i][1<<i]=0;
for(int i=0;i<(1<<n);i++)
{
for(int j=0;j<n;j++)
{
if((1<<j)&i)
{
for(int k=0;k<n;k++)
{
if(!((1<<k)&i))
{
dp[k][i|(1<<k)]=min(dp[k][i|(1<<k)],dp[j][i]+a[j][k]);
}
}
}
}
}
int ans=dp[0][(1<<n)-1];
for(int i=1;i<=n;i++)
ans=min(ans,dp[i][(1<<n)-1]);
printf("%d\n",ans);
return 0;
}
dp[i]表示当前选择的字符串的子序列编号状态压缩为i,对于每个状态,再考虑这个状态的字符中是否可以由回文串和剩余串移除。
#include<bits/stdc++.h>
using namespace std;
const int maxn=20;
const int inf=0x3f3f;
string s;
int dp[1<<maxn];
bool judge(int cur)
{
string s2="";
int pos=0;
while(cur)
{
if(cur&1)
{
s2+=s[pos];
}
cur>>=1;
pos++;
}
for(int i=0,j=s2.size()-1;i<j;i++,j--)
{
if(s2[i]!=s2[j]) return false;
}
return true;
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
cin>>s;
int len=s.length();
for(int i=1;i<(1<<len);i++)
{
dp[i]=judge(i)?1:inf;
for(int j=i;j;j=(j-1)&i)
{
dp[i]=min(dp[i],dp[j]+dp[j^i]);
}
}
printf("%d",dp[(1<<len)-1]);
return 0;
}
对于每一行,首先用一个数组存该行所有可选的列。然后一行一行的考虑,对于每一行枚举所有选择的,去掉当前选择中选到当前行中不合法的列或者选到了相邻列的,然后遍历上一行选择,如果上一行的选择与这一行起冲突则跳过,否则更新当前行。
自己敲的代码因为上课着急走忘了保存,不想再敲了,放上别人的代码:
#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 20;
const int MAX_M = 20;
int state[MAX_N + 1];
int dp[MAX_N + 1][1 << MAX_M];
bool not_intersect(int now, int prev) {
return (now & prev) == 0;
}
bool fit(int now, int flag) {
return (now | flag) == flag;
}
bool ok(int x) {
// 行内自己是否相交
return (x & (x >> 1)) == 0; //说明没有相邻的点
}
int count(int now) {
int s = 0; // 统计 now 的二进制形式中有多少个 1
while (now) {
s += (now & 1); // 判断 now 二进制的最后一位是否为 1,如果是则累加
now >>= 1; // now 右移一位
}
return s;
}
int main() {
int n, m;
cin >> n >> m;
// 初始化所有数组
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < m; ++j) {
int flag;
cin >> flag;
state[i] |= (1 << j) * flag; // 将 (i,j) 格子的状态放入 state[i] 中,state[i] 表示第 i 行的可选格子组成的集合
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < (1 << m); ++j) { // 枚举当前行的状态
if (!ok(j) || !fit(j, state[i])) { // 如果当前行状态不合法则不执行后面的枚举
continue;
}
int cnt = count(j); // 统计当前行一共选了多少个格子
for (int k = 0; k < (1 << m); ++k) {
if (ok(k) && fit(k, state[i - 1]) && not_intersect(j, k)) { // 判断前一行是否合法和当前行和前一行的方案是否冲突
dp[i][j] = max(dp[i][j], dp[i - 1][k] + cnt); // 更新当前行、当前状态的最优解
}
}
}
}
int ans = 0; // 保存最终答案
for (int i = 0; i < (1 << m); ++i) {
ans = max(ans, dp[n][i]);
}
cout << ans << endl;
return 0;
}
将每一包糖果的状态进行压缩。
#include<bits/stdc++.h>
using namespace std;
const int maxn=107;
const int maxm=27;
int n,m,k;
int t;
int d;
int dp[1<<20];
int a[maxn];
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
{
d=0;
for(int j=1;j<=k;j++)
{
scanf("%d",&t);
d=(1<<(t-1))|d;
}
a[i]=d;
dp[d]=1;
}
for(int i=1;i<=n;i++)
for(int j=0;j<(1<<m);j++)
{
if(!dp[j]) continue;
if(!dp[j|a[i]]) dp[j|a[i]]=dp[j]+1;
else dp[j|a[i]]=min(dp[j|a[i]],dp[j]+1);
}
printf("%d\n",dp[(1<<m)-1]);
return 0;
}