状压dp题目

题目描述

司令部的将军们打算在 N\times MN×M 的网格地图上部署他们的炮兵部队。

一个 N\times MN×M 的地图由 NN 行 MM 列组成,地图的每一格可能是山地(用 \texttt{H}H 表示),也可能是平原(用 \texttt{P}P 表示),如下图。

在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。

图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。

现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入格式

第一行包含两个由空格分割开的正整数,分别表示 NN 和 MM。

接下来的 NN 行,每一行含有连续的 MM 个字符,按顺序表示地图中每一行的数据。

输出格式

一行一个整数,表示最多能摆放的炮兵部队的数量。

输入输出样例

输入 #1复制

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

输出 #1复制

6
#include<iostream>
using namespace std;
const int maxn=10;
int dp[1<<maxn][1<<maxn][3];
int sum[1<<maxn];
int a[101];
int n,m;
void pre()
{
    for(int i=0;i<(1<<m);i++)
    {
        int num=0;
        for(int j=0;j<m;j++)
        {
            if((i>>j)&1) num++;
        }
        sum[i]=num;
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            a[i]=2*a[i];
            char ch;cin>>ch;
            if(ch=='H')
            {
                a[i]+=1;
            }
        }
    }
    pre();
    for(int i=0;i<(1<<m);i++)
    {
        if(i&a[1]||i&(i<<1)||i&(i<<2)) continue;
        dp[0][i][1]=sum[i];
        for(int j=0;j<(1<<m);j++)
        {
            if(j&i||j&a[2]||j&(j<<1)||j&(j<<2)) continue;
            dp[i][j][2]=sum[i]+sum[j];
        }
    }
    for(int i=3;i<=n;i++)
    {
        for(int j=0;j<(1<<m);j++)
        {
            if(j&a[i-1]||j&(j<<1)||j&(j<<2)) continue;
            for(int s=0;s<(1<<m);s++)
            {
                if(s&j||s&a[i]||s&(s<<1)||s&(s<<2)) continue;
                for(int l=0;l<(1<<m);l++)
                {
                    if(l&s||l&j||l&a[i-2]||l&(l<<1)||l&(l<<2)) continue;
                    dp[j][s][i%3]=max(dp[j][s][i%3],dp[l][j][(i-1)%3]+sum[s]);
                }
            }
        }
    }
    int ans=0;
    for(int i=0;i<(1<<m);i++)
    {
        for(int j=0;j<(1<<m);j++)
        {
            ans=max(ans,dp[i][j][n%3]);
        }
    }
    cout<<ans<<endl;
}

题目描述

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

注:数据有加强(2018/4/25)

输入格式

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

输出格式

所得的方案数

输入输出样例

输入 #1复制

3 2

输出 #1复制

16
#include<iostream>
using namespace std;
#define int long long
const int maxn=10;
int dp[maxn][1<<maxn][maxn*maxn];
int sum[1<<maxn];
signed main()
{
    int n,k;cin>>n>>k;
    for(int i=0;i<(1<<n);i++)
    {
        int t=i;int num=0;
        for(int j=0;j<n;j++)
        {
            if((t>>j)&1) num++;
        }
        //cout<<num<<endl;
        sum[i]=num;
    }
    for(int i=0;i<(1<<n);i++)
    {
        if(sum[i]>k||i&(i<<1)) continue;
        dp[1][i][sum[i]]=1;
    }
    for(int i=2;i<=n;i++)
    {
        for(int j=0;j<(1<<n);j++)
        {
            if(sum[j]>k||j&(j<<1)) continue;
            for(int s=0;s<(1<<n);s++)
            {
                if(sum[s]>k||s&(s<<1)||j&(s<<1)||j&(s>>1)||j&s) continue;
                for(int p=sum[s];p<=k;p++)
                {
                    dp[i][s][p]+=dp[i-1][j][p-sum[s]];
                }
            }
        }
    }
    int ans=0;
    for(int i=0;i<(1<<n);i++)
    {
        ans+=(dp[n][i][k]);
    }
    cout<<ans<<endl;
}

题目背景

yyy 对某些数字有着情有独钟的喜爱,他叫他们为幸运数字;然而他作死太多,所以把自己讨厌的数字成为“厄运数字”。

题目描述

一群同学在和 yyy 玩一个游戏。

每次,他们会给 yyy nn 张卡片,卡片上有数字,所有的数字都是“幸运数字”,我们认为第 ii 张卡片上数字是 a_{i}ai​。

每次 yyy 可以选择向前走 a_{i}ai​ 步并且丢掉第 ii 张卡片。当他手上没有卡片的时候他就赢了。

但是呢,大家对“厄运数字”的位置布置下了陷阱,如果 yyy 停在这个格子上,那么他就输了。注意:即使到了终点,但是这个位置是厄运数字,那么也输了。

现在,有些同学开始问:yyy 有多大的概率会赢呢?

大家觉得这是个好问题,有人立即让 yyy 写个程序:“电脑运行速度很快!2424 的阶乘也不过就 620\,448\,401\,733\,239\,439\,360\,000620448401733239439360000,yyy 你快写个程序来算一算。”

yyy 表示很无语,他表示他不想算概率,最多算算赢的方案数,而且是对 10^9+7109+7 取模后的值。

大家都不会写程序,只好妥协。

但是这时候 yyy 为难了,24!24! 太大了,要跑好长时间。

他时间严重不够!需要你的帮助!

由于 yyy 人格分裂,某个数字可能既属于幸运数字又属于厄运数字。

输入格式

第一行一个整数 nn。

下面一行 nn 个整数,第 ii 个整数代表第 ii 张卡片上的数字 a_iai​。

第三行 mm 表示 yyy 的厄运数字个数(最多 22 个)。

输出格式

输出胜利方案数对 10^9+7109+7 取模的结果。

 lowbit()优化

#include<iostream>
using namespace std;
const int maxn=24;
int dp[1<<maxn];
const int MOD=1e9+7;
int sum[1<<maxn];
int a[maxn];int b[2];
int main()
{
    int n;cin>>n;
    for(int i=0;i<n;i++) {
        cin>>a[i];
        sum[1<<i]=a[i];
    }
    int m;
    cin>>m;
    for(int i=0;i<m;i++) cin>>b[i];
    dp[0]=1;
    for(int i=1;i<(1<<n);i++)
    {
        int k=i;
        sum[i]=sum[i^(k&(-k))]+sum[k&(-k)];
        int flag=0;
        for (int j = 0; j < m; j++) {
            if (sum[i] == b[j]) {
                flag=1;
                break;
            }
        }
        if(flag) continue;
        while(k)
        {
            int j=k&(-k);
            dp[i]+=dp[i^j];
            if(dp[i]>=MOD) dp[i]-=MOD;
            k=k^j;
        }
    }
    cout<<dp[(1<<n)-1];
}

题目描述

为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。

在晚宴上,主办方为大家提供了 n−1n−1 种不同的寿司,编号 1,2,3,\ldots,n-11,2,3,…,n−1,其中第种寿司的美味度为 i+1i+1。(即寿司的美味度为从 22 到 nn)

现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 xx 的寿司,小 W 品尝的寿司中存在一种美味度为 yy 的寿司,而 xx 与 yy 不互质。

现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 pp 取模)。注意一个人可以不吃任何寿司。

输入格式

输入文件的第 11 行包含 22 个正整数 n, pn,p 中间用单个空格隔开,表示共有 nn 种寿司,最终和谐的方案数要对 pp 取模。

输出格式

输出一行包含 11 个整数,表示所求的方案模 pp 的结果。

输入输出样例

输入 #1复制

3 10000

输出 #1复制

9

输入 #2复制

4 10000

输出 #2复制

21

输入 #3复制

100 100000000

输出 #3复制

3107203

 

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int maxn=8;
int f1[1<<maxn][1<<maxn];
int f2[1<<maxn][1<<maxn];
int dp[1<<maxn][1<<maxn];
int p[8]={2,3,5,7,11,13,17,19};
struct node{
    int val,s,mp;
}a[505];
bool cmp(node a,node b)
{
    return a.mp<b.mp;
}
signed  main()
{
    int n,m;cin>>n>>m;
    for(int i=1;i<=n-1;i++)
    {
        a[i].val=i+1;
        int temp=i+1;
        int num=0;
        for(int j=0;j<8;j++)
        {
            if(temp%p[j]) continue;
            num+=1<<j;
            while(temp%p[j]==0){
                temp/=p[j];
            }
        }
        if(temp>1) a[i].mp=temp;
        else a[i].mp=-1;
        a[i].s=num;
    }
    sort(a+1,a+n,cmp);
    dp[0][0]=1;
    for(int i=1;i<n;i++)
    {
        if(i==1||a[i].mp!=a[i-1].mp||a[i].mp==-1)
        {
            memcpy(f1,dp,sizeof(f1));
            memcpy(f2,dp,sizeof(f2));
        }
        for(int j=255;j>=0;j--)
        {
            for(int k=255;k>=0;k--)
            {
                if(j&k) continue;
                if((a[i].s&k)==0) f1[j|a[i].s][k]=(f1[j|a[i].s][k]+f1[j][k])%m;
                if((a[i].s&j)==0) f2[j][k|a[i].s]=(f2[j][k|a[i].s]+f2[j][k])%m;
            }
        }
        if(i==n-1||a[i].mp!=a[i+1].mp||a[i].mp==-1)
        {
            for(int j=255;j>=0;j--)
            {
                for(int k=255;k>=0;k--)
                {
                    if(j&k) continue;
                    dp[j][k]=(f1[j][k]+f2[j][k]-dp[j][k]+m)%m;
                }
            }
        }
    }
    int ans=0;
    for(int i=255;i>=0;i--)
    {
        for(int j=255;j>=0;j--)
        {
            if(i&j) continue;
            ans=(ans+dp[i][j])%m;
        }
    }
    cout<<ans<<endl;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值