状压dp 持续更新
前置知识点:二进制状态压缩,动态规划。
1. AcWing 91 最短Hamilton路径 (https://www.acwing.com/problem/content/93/)
给定一张 n 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数n。
接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(记为a[i,j])。
对于任意的x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]>=a[x,z]。
输出格式
输出一个整数,表示最短Hamilton路径的长度。
数据范围
1≤n≤20
0≤a[i,j]≤107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18
暴力跑一遍dfs: O(N*N*N!) 枚举n个点的全排列求最小值:O(N*N!).
分析:
在暴力的方法中,有一些重复计算存在,比如在计算路径1->2->3->4->5与路径1->2->4->3->5的长度时,我们重复计算了路径1->2的长度。
所以我们可以考虑dp。
我们的已知信息是各点之间的路径长度,所以dp数组需要靠这些路径的长度来增加。
那么我们加上一个新的路径长度需要的条件有:
1.路径的端点一个已经到达,一个未到达,所以我们考虑使用当前点的到达情况作为状态划分变量。
2.当前的点是路径的端点,所以我们考虑使用当前所在的点作为状态划分变量。
dp[i][j]:点的到达情况为j,目前所在点是i的最短路径。
状态转移方程:dp[i][j]=min(dp[i^(1<<j)][k]+G[k][j]); (变量k枚举前一个点)。
起始状态:dp[1][0]=0;
最终状态:dp[(1<<n)-1][n-1];
时间复杂度:O(N*N*2^N)
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long ll;
using namespace std; const int N=; int dp[<<N][N];
int G[N][N];
int n; int main() {
scanf("%d",&n);
for(int i=;i<n;i++) {
for(int j=;j<n;j++) {
scanf("%d",&G[i][j]);
}
}
for(int i=;i<(<<n);i++) {
for(int j=;j<n;j++) {
dp[i][j]=2e9;
}
}
dp[][]=;
for(int i=;i<(<<n);i++) {
for(int j=;j<n;j++) {
if((i>>j)&) {
for(int k=;k<n;k++) {
if(((i^(<<j))>>k)&) {
dp[i][j]=min(dp[i][j],dp[i^(<<j)][k]+G[k][j]);
}
}
}
}
}
printf("%d\n",dp[(<<n)-][n-]);
return ;
}
2. AcWing 291. 蒙德里安的梦想 (https://www.acwing.com/problem/content/293/)
求把N*M的棋盘分割成若干个1*2的的长方形,有多少种方案。
例如当N=2,M=4时,共有5种方案。当N=2,M=3时,共有3种方案。
如下图所示:
输入格式
输入包含多组测试用例。
每组测试用例占一行,包含两个整数N和M。
当输入用例N=0,M=0时,表示输入终止,且该用例无需处理。
输出格式
每个测试用例输出一个结果,每个结果占一行。
数据范围
1≤N,M≤11
输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205
这题问的是如何分割成1*2的方块,相当于如何用1*2的方块填满。
如果是随便填的话,我们需要考虑整个区域的状态,不太可做。我们可以一行一行填,先填满上面的在填下面的,这样就简单得多,所以考虑以当前行数作为一个状态划分变量。我们发现,在我们填满一行后并不一定要仅填满当前一行,而是可以凸到下面一行的,不同的凸法会对下一行造成影响,所以考虑以填当前行时的凸法作为一个状态划分变量。
上一行凸法与当前行凸法之间的关系:
1.上一行凸的一列当前行不凸。
2.当前行凸的一列上一行不凸。
确定完当前行突出的部分以及上一行凸下来的部分之后,剩下的部分只能用来放横着的方块,放横着的方块需要满足剩余的部分没有连续奇数个的剩余。
我们设1为凸,0为不凸,设当前行的凸状态为j,上一行的凸状态为k,则剩余的部分为j|k中0的部分。我们提前计算好该状态用来放下横着的方块是否可行,避免重复计算。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long ll;
using namespace std; const int N=; ll dp[N+][<<N];
bool able[<<N];
int n,m; int main() {
while(scanf("%d%d",&n,&m),(n||m)) {
if((n*m)&) {
printf("0\n"); continue;//n*m为奇数判断方案数为0.
}
memset(dp,,sizeof(dp));
dp[][]=;
for(int i=;i<(<<m);i++) {//提前计算是否可行。
int cnt=; able[i]=true;
for(int j=;j<m;j++) {
if((i>>j)&) {
if(cnt&) able[i]=false;
cnt=;
}else cnt++;
}
if(cnt&) able[i]=false;
}
for(int i=;i<=n;i++) {
for(int j=;j<(<<m);j++) {
for(int k=;k<(<<m);k++) {
if(!(k&j)&&able[j|k]) {//!(k&j)判断是否满足上面说的行之间的两个关系。
dp[i][j]+=dp[i-][k];
}
}
}
}
printf("%lld\n",dp[n][]);
}
return ;
}
3. AcWing 164. 可达性统计
给定一张N个点M条边的有向无环图,分别统计从每个点出发能够到达的点的数量。
输入格式
第一行两个整数N,M,接下来M行每行两个整数x,y,表示从x到y的一条有向边。
输出格式
输出共N行,表示每个点能够到达的点的数量。
数据范围
1≤N,M≤30000
输入样例:
10 10
3 8
2 3
2 5
5 9
5 9
2 3
3 9
4 8
2 10
4 9
输出样例:
1
6
3
3
2
1
1
1
1
1
这题其实是一道拓扑排序的题目,但我也不知道为什么是拓扑排序的题目,因为这题用dfs来算和拓扑排序之后再算似乎没有本质的区别。
分析:
一看到数据量,不能用暴力直接从每个点开始dfs计算到达了多少个点。
看到有向无环图,想到了先生成个dfs搜索生成树,然后在树上加些边看看能不能发现什么东西,最终失败。
于是开始考虑dp,我们发现一个点能到达的点等于它本身+他下一步能到的点所能到的点。但是显然无法直接累加,因为会有重复,于是考虑到二进制状态压缩。
得到状态转移方程:able[u]=(able[v1]|able[v2]|...|able[vn])|(1<<(u-1));
这题的点有30000个,所以用数字记录状态需要30000位,显然做不到,开bool数组算起来太慢,所以用bitset。(bitset用法 https://www.cnblogs.com/magisk/p/8809922.html)
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <bitset>
typedef long long ll;
using namespace std; const int N=3e4+; bitset <N> able[N]; int E[N<<],fir[N],nex[N<<],tot=;
bool deg[N];
bool vis[N];
int n,m; void connect(int from,int to) {
E[tot]=to;
nex[tot]=fir[from];
fir[from]=tot++;
} void dfs(int now) {
vis[now]=true;
for(int i=fir[now];i!=-;i=nex[i]) {
int to=E[i];
if(!vis[to]) dfs(to);
able[now]|=able[to];
}
} int main() {
memset(fir,-,sizeof(fir));
scanf("%d%d",&n,&m);
for(int i=;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
connect(x,y);
deg[y]=true;
}
for(int i=;i<=n;i++) {
able[i][i-]=true;
}
for(int i=;i<=n;i++) {
if(!deg[i]) dfs(i);
}
for(int i=;i<=n;i++) {
printf("%d\n",able[i].count());
}
return ;
}
4. POJ 1185(https://vjudge.net/problem/POJ-1185)
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
Input
接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。
Output
Sample Input
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
Sample Output
6
分析:
这题有点类似AcWing 291. 蒙德里安的梦想,只不过这题要考虑2行的状态,即当前行的状态与往上2行的状态有关。
dp[i][j][k]:当前行数为i,当前行与上一行的炮兵放置状态为j,k的最大炮兵放置数量。
我们事先计算出仅考虑当前行的可放置方案(不考虑地形以及往上2行的放置状态,只考虑同一行之间会不会互相攻击)。
然后再计算出仅考虑2行的可放置方案(只考虑同一行之间以及相邻两行之间会不会互相攻击)。
接下来的步骤见于代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <bitset>
#include <vector>
typedef long long ll;
using namespace std; struct Status {//仅考虑2行的可放置方案。
int up;//上面的一行。
int down;//下面的一行。
}; int get[];//事先计算好每种1行的状态有多少个炮兵。
ll dp[][][]; int main() {
int n,m;
scanf("%d%d",&n,&m);
bitset <> dixing[n];//储存地形。
for(int i=;i<=n;i++) dixing[i]=;
for(int i=;i<=n;i++) {
char s[];
scanf("%s",s);
for(int j=;j<m;j++) {
if(s[j]=='P') dixing[i][j]=true;
}
}
vector <int> able;//仅考虑当前行的可放置方案
for(int i=;i<(<<m);i++) {
bool flag=true;
for(int j=;j<m;j++) {
if((i>>j)&) {//找到一个炮兵,下面四个判断用来判断左,右2格是否有其他炮兵。
if(j>=) {
if((i>>(j-))&) {
flag=false;break;
}
}
if(j>=) {
if((i>>(j-))&) {
flag=false;break;
}
}
if(j<=m-) {
if((i>>(j+))&) {
flag=false;break;
}
}
if(j<=m-) {
if((i>>(j+))&) {
flag=false;break;
}
}
}
}
if(flag) able.push_back(i);
}
for(int i=;i<able.size();i++) {
bitset <> tmp=able[i];
get[i]=tmp.count();
}
vector <Status> status;
for(int i=;i<able.size();i++) {
for(int j=;j<able.size();j++) {
if((able[i]&able[j])!=) continue;
status.push_back((Status){i,j});
}
}
for(int i=;i<=n;i++) {
for(int j=;j<able.size();j++) {
bitset <> tmp=able[j];
bool flag=true;
for(int k=;k<m;k++) {
if(dixing[i][k]==&&tmp[k]==) {//判断当前行地形是否支持该放置方案。
flag=false;
break;
}
}
if(!flag) continue;
for(int k=;k<status.size();k++) {
if((able[j]&able[status[k].up])||((able[j]&able[status[k].down]))) continue;//判断当前行的放置是否与上2行冲突。
dp[i][j][status[k].down]=max(dp[i][j][status[k].down],dp[i-][status[k].down][status[k].up]+get[j]);
}
}
}
ll ans=;
for(int i=;i<status.size();i++) {
ans=max(ans,dp[n][status[i].down][status[i].up]);
}
printf("%lld\n",ans);
return ;
}
状压dp 持续更新的更多相关文章
- POJ1185 炮兵阵地 —— 状压DP
题目链接:http://poj.org/problem?id=1185 炮兵阵地 Time Limit: 2000MS Memory Limit: 65536K Total Submissions ...
- 状压dp(状态压缩&&dp结合)学习笔记(持续更新)
嗯,作为一只蒟蒻,今天再次学习了状压dp(学习借鉴的博客) 但是,依旧懵逼·································· 这篇学习笔记是我个人对于状压dp的理解,如果有什么不对的 ...
- fzu2188 状压dp
G - Simple String Problem Time Limit:2000MS Memory Limit:32768KB 64bit IO Format:%I64d & ...
- HDU5816 Hearthstone(状压DP)
题目 Source http://acm.hdu.edu.cn/showproblem.php?pid=5816 Description Hearthstone is an online collec ...
- 【62测试】【状压dp】【dfs序】【线段树】
第一题: 给出一个长度不超过100只包含'B'和'R'的字符串,将其无限重复下去. 比如,BBRB则会形成 BBRBBBRBBBRB 现在给出一个区间[l,r]询问该区间内有多少个字符'B'(区间下标 ...
- 2014 Super Training #1 B Fix 状压DP
原题: HDU 3362 http://acm.hdu.edu.cn/showproblem.php?pid=3362 开始准备贪心搞,结果发现太难了,一直都没做出来.后来才知道要用状压DP. 题意: ...
- 【POJ 2923】Relocation(状压DP+DP)
题意是给你n个物品,每次两辆车运,容量分别是c1,c2,求最少运送次数.好像不是很好想,我看了网上的题解才做出来.先用状压DP计算i状态下,第一辆可以运送的重量,用该状态的重量总和-第一辆可以运送的, ...
- HDU 3920Clear All of Them I(状压DP)
HDU 3920 Clear All of Them I 题目是说有2n个敌人,现在可以发n枚炮弹,每枚炮弹可以(可以且仅可以)打两个敌人,每一枚炮弹的花费等于它所行进的距离,现在要消灭所有的敌人 ...
- Codeforces Round #321 (Div. 2) D. Kefa and Dishes 状压dp
题目链接: 题目 D. Kefa and Dishes time limit per test:2 seconds memory limit per test:256 megabytes 问题描述 W ...
随机推荐
- GSON解译Json为DTO
除了用okhttp网络库外,还用到google的gson库. 1. uti类的对象一般都用懒汉模式.这次gson也是用懒汉模式. public class GsonTools { private st ...
- CodeForces 527C. Glass Carving (SBT,线段树,set,最长连续0)
原题地址:http://codeforces.com/problemset/problem/527/C Examples input H V V V output input H V V H V ou ...
- 移植linux4.14内核到四核Exynos4412开发板
最近法师收到了很多留言,其中有一部分问法师什么时候更新,还有一大部分问法师我是买迅为的IMX6UL精英版好呢还是买4412精英版好呢,因为我们这俩个都不贵.法师的建议的是入手4412!为什么呢? 第一 ...
- 类似postman插件
Talend API Tester - Free Edition https://chrome.google.com/webstore/detail/talend-api-tester-free-ed ...
- MRP自动运算设置
1.执行计划-删除老的调度计划: 2.运算日志-清除冲突: 3.MRP计划运算向导,清除预留: 4.创建MRP凌晨调度任务,名称自己修改: 5.创建完成: 6.设置消息通知:
- Uncaught TypeError: Cannot read property 'querySelector' of null
报错. 解决办法:把报错部分的js放到body后面
- NAIPC 2019 A - Piece of Cake(凸包计算)
学习:https://blog.csdn.net/qq_21334057/article/details/99550805 题意:从nn个点中选择kk个点构成多边形,问期望面积. 题解:如果能够确定两 ...
- mysql之结果集去重
mysql操作中,经常会遇到对结果集的去重 本篇文章列出几种应对办法: 1.使用distinct做去重,测试了一下,DISTINCT可以支持多列去重 select DISTINCT user_id_t ...
- springmvc拦截器入门及其执行顺序源码分析
springmvc拦截器是偶尔会用到的一个功能,本案例来演示一个较简单的springmvc拦截器的使用,并通过源码来分析拦截器的执行顺序的控制.具体操作步骤为:1.maven项目引入spring依赖2 ...
- spring学习笔记三:Component注解(把POJO类实例化到spring的IOC容器中)
Component注解:把普通的POJO 类实例化到spring的IOC容器中,就是定义成<bean id="" class=""> 项目目录树: ...