状压
状压目前有两种,一种比如AB就是把状态转换前后枚举情况。再看符不符合条件。一种比如CDE就是图中遍历点。从一个点走到另一个店。能不能走到也算条件。所以其实两种看起来一个是模拟情况一个是模拟路径但本质都是枚举条件不同而已。其实我在想为什么要状压状压和普通dp有什么区别.如果要表示状态普通dp用1234这样模拟状态数据量跟状压不也是一样的,也就是在空间并没有节省多少,我觉得最重要的可能是 二进制,就是通过二进制你可以 位运算更简单,而且 通过01可以很快知道是什么状态,而不需要另外去定义什么状态用什么数字。
那什么时候用状压呢?首先肯定是有dp那么用。一开始我觉得是在有明确模拟状态情况需要可是有一个问题来了,大多数dp如果清晰地模拟状态那是最好不过了,那为什么有些dp不用状压呢?因为用不了。。。状压dp模拟状态一般需要罗列每个状态,所以对数字要求高。一般来说 数字不能超过12,13.
没必要用,状态和状态之间存在 条件。比如A两个方块不能相邻,模拟状态01串中不能出现11.但是01背包里面只需要知道一个物品选了或者没选而不需要知道是第几个或者是有特定条件的选择所以不需要特别状态。
简而言之。 数据小,需要具体状态就需要状压。
A - Corn Fields POJ - 3254
状压入门第一题。 原理很容易理解。但是鉴于代码能力,今天第一次直接写,wa了一次,发现没有mod没想到改过了直接A了开心。还是要自己写一遍呀#include <iostream>
#include <string.h>
using namespace std;
const int mod=100000000;
int mp[20],dp[20][1<<13];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) {
mp[i] = 0;
for (int j = 1; j <= m; j++) {
int k;
cin >> k;
mp[i] = mp[i] << 1 | (k^1);
}
}
memset(dp,0, sizeof(dp));
int li=1<<m;
for(int i=1;i<=n;i++)
for(int sta=0;sta<li;sta++)
for(int pre=0;pre<li;pre++) {
if ((sta & sta << 1) && (sta & sta >> 1))continue;
if (sta & mp[i])continue;
if(i==1){dp[1][sta]=1;continue;}
if(sta&pre)continue;
dp[i][sta] = (dp[i][sta] + dp[i - 1][pre])%mod;
}
int ans=0;
for(int i=0;i<li;i++)
{
ans=(ans+dp[n][i])%mod;
}
cout<<ans<<endl;
}
//2 3
//1 1 1
//0 1 0
B - 炮兵阵地 POJ - 1185
与之前那提大同小异。暂时不写节约时间。C - Hie with the Pie POJ - 3311
经典旅行商问题。这道题其实很简单。但是很有意思。 思考这个题可以思考一下到底什么是状压,首先看一个比较简单的代码。其实就是枚举状态然后如果s这个状态里面没有i这个点,我们就枚举其他的点,通过其他比较到i的路径,得到新状态(包含i点)的最短距离。#include <stdio.h>
#include <algorithm>
#include<string.h>
using namespace std;
const int inf=0x3f3f3f3f;
int dis[20][20],dp[1<<11][20];
int main()
{
int n;
while(~scanf("%d",&n)&&n){
memset ( dis, inf, sizeof(dis) );
memset ( dp, inf, sizeof(dp) );
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++){
scanf("%d",&dis[i][j]);
}
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
for(int k=0;k<=n;k++){
dis[j][k]=min(dis[j][k],dis[j][i]+dis[i][k]);
}
int li=1<<(n+1);
dp[0][0]=0;
for(int s=0;s<li;s++){
for(int i=0/**/;i<=n;i++) {
for (int k = 0; k <= n; k++) {
if (!(s & (1 << i))) {
dp[s | 1 << i][i] = min(dp[s | 1 << i][i], dp[s][k] + dis[k][i]);
}
}
}
}
printf("%d\n",dp[li-1][0]);
}
}
其实这个不需要先懂得什么是状压,看到这道题你只需要思考,我要枚举得到比较短的路程,那么枚举的过程中我需要用一个什么东西来表示状态,所以就用二进制,然后根据状态的变化再跑一下各种枚举就得到答案了。其实在枚举状态的时候就已经是状压的核心概念了。但是这还不够。
再看一个
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int map[20][20],dis[20][20],dp[1<<11][20];
int main()
{
int n,i,j,k;
while(~scanf("%d",&n),n)
{
for(i = 0; i<=n; i++)
for(j = 0; j<=n; j++)
{
scanf("%d",&map[i][j]);
dis[i][j] = map[i][j];
}
for(j = 0; j<=n; j++)
for(i = 0; i<=n; i++)
for(k = 0; k<=n; k++)
if(dis[i][j]>dis[i][k]+map[k][j])
dis[i][j] = dis[i][k]+map[k][j];
memset(dp,-1,sizeof(dp));
dp[1][0] = 0;
int cou=0;
for(i = 1; i<1<<(n+1); i++)
{
i = i|1;
for(j = 0; j<=n; j++)
{
if(dp[i][j]!=-1)
{
for(k = 0; k<=n; k++)
{
if(j!=k && (dp[(1<<k)|i][k]==-1 || dp[(1<<k)|i][k]>dp[i][j]+dis[j][k]))
{dp[(1<<k)|i][k]=dp[i][j]+dis[j][k];}cou++;
}
}
}
}
printf("%d\n",dp[(1<<(n+1))-1][0]);
}
return 0;
}
这个比上面多了许多限制。用一个变量泡一下你会发现。之前代码最内层跑了1888而这个代码只跑了18这不难理解加了限制。但是有趣的是状压就是使限制操作更加简单。也就是说,状压其实就是在搜索,但是枚举状态形式不同同时限制条件操作简单还有适当记忆化。
D - Travelling HDU - 3001
TSP问题。#include<iostream>
using namespace std;
const int maxn=12;
const int inf=0x3f3f3f3f;
int mp[maxn][maxn],dp[maxn][60000];
int li,n,m;
void initt(int li){
for(int i=0;i<n;i++){
for(int sta=0;sta<li;sta++){
dp[i][sta]=-1;
}
for(int j=0;j<n;j++){
mp[i][j]=-1;
}
}
}
int changethree(int three[],int ans){
// int k=0,cou=0;
// while(ans){
// three[k]=ans%3;
// ans/=3;
// if(three[k++])cou++;
// }
// return cou;
int k=0;
for(int i=0;i<n;i++){
three[i]=ans%3;
ans/=3;
if(three[i])k++;
}
return k;
}
int main()
{
int a,b,c;
int in[12],three[12];
in[0]=1;
for(int i=1; i<=10; i++)in[i]=in[i-1]*3;
while(~scanf("%d%d",&n,&m)) {
initt(in[n]);
li=in[n];
for(int i=0;i<m;i++){
cin>>a>>b>>c;
a--,b--;
if(mp[a][b]==-1)
mp[a][b]=mp[b][a]=c;
else mp[a][b]=mp[b][a]=min(mp[a][b],c);
}
int ans=inf;
int flag=-1;
for (int sta = 1; sta < li; sta++) {
int k=changethree(three,sta);
for (int po = 0; po < n; po++){//point is po
if(three[po]){
if(k==1){dp[po][sta]=0;}
if(dp[po][sta]==-1)continue;
if(k==n){flag=0;ans=min(ans,dp[po][sta]);}
for(int nep=0;nep<n;nep++){//next point is nep
if(nep!=po&&three[nep]<2&&mp[po][nep]!=-1){
int nes=sta+in[nep];
if(dp[nep][nes]==-1){
dp[nep][nes]=dp[po][sta]+mp[po][nep];
}
else dp[nep][nes]=min(dp[nep][nes],dp[po][sta]+mp[po][nep]);
}
}
}
}
}
if(flag){cout<<flag<<endl;}
else cout<<ans<<endl;
}
}
E - Islands and Bridges POJ - 2288
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m;
int val[15],map[13][13];
int dp[1<<13][13][13]; //dp[state][i][j]表示state状态下倒数第二个岛为i,最后一个岛为j时的最优解
long long num[1<<13][13][13]; //num[state][i][j]为相应的路径数目
int main(){
//freopen("input.txt","r",stdin);
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%d",&val[i]);
memset(map,0,sizeof(map));
int u,v;
while(m--){
scanf("%d%d",&u,&v);
u--;v--;
map[u][v]=map[v][u]=1;
}
if(n==1){
printf("%d 1\n",val[0]);
continue;
}
memset(dp,-1,sizeof(dp));
memset(num,0,sizeof(num));
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(i!=j && map[i][j]){
dp[(1<<i)|(1<<j)][i][j]=val[i]+val[j]+val[i]*val[j];
num[(1<<i)|(1<<j)][i][j]=1;
}
for(int i=0;i<(1<<n);i++)
for(int j=0;j<n;j++)
if((i&(1<<j))!=0)
for(int k=0;k<n;k++)
if(map[j][k] && j!=k && (i&(1<<k))!=0 && dp[i][j][k]!=-1) //这里得注意,先彻底明白该dp[][][]的具体含义,
for(int x=0;x<n;x++)
if(map[k][x] && j!=x && k!=x && (i&(1<<x))==0){
int tmp=dp[i][j][k]+val[x]+val[k]*val[x];
if(map[j][x])
tmp+=val[j]*val[k]*val[x];
if(dp[i|(1<<x)][k][x]<tmp){
dp[i|(1<<x)][k][x]=tmp;
num[i|(1<<x)][k][x]=num[i][j][k];
}else if(dp[i|(1<<x)][k][x]==tmp)
num[i|(1<<x)][k][x]+=num[i][j][k];
}
int ans1=0;
long long ans2=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(i!=j && map[i][j]){
if(ans1<dp[(1<<n)-1][i][j]){
ans1=dp[(1<<n)-1][i][j];
ans2=num[(1<<n)-1][i][j];
}else if(ans1==dp[(1<<n)-1][i][j])
ans2+=num[(1<<n)-1][i][j];
}
cout<<ans1<<" "<<ans2/2<<endl;
}
return 0;
}
F - Most Powerful ZOJ - 3471
这个题也比较简单。一开始转移没想明白。往图那边想,有模拟走路径不可以重复点。但是有不同,如果是走路那么走过这个点,这个点连下一个点的还可以但是这里走过这个点,这个就消失那么与它相连的店走不了。所以有点蒙了。
看了下题解,其实很简单,也是我之前忽略的一点,就是如果是走路经那么从i到k。状态中i是要有的,k是不要有的。如果是这题,状态i不能有,k也不能有(代表两个原子都没有用)。所以自动就过滤掉消失的原子所连接的能量了。
这道题1表示用过,0表示没用过。其实用0表示用过,1表示没用过更方便但是记得要反向遍历状态
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double eps = 1e-15;
typedef long long LL;
typedef pair <int, int> PLL;
LL dp[1050];
LL A[15][15];
int main() {
int n;
while (cin >> n) {
if (!n) {
break;
}
memset(dp, 0, sizeof(dp));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> A[i][j];
}
}
int cou=0;
int li=1<<n;
for (int i = 0; i <li; i++) {
for (int j = 0; j < n; ++j) {
if (!(i & (1 << j))) {
for (int k = 0; k < n; ++k) {
if (j == k) {
continue;
}
if (!(i & (1 << k))) {
dp[i ^ (1 << k)] = max(dp[i ^ (1 << k)], dp[i] + A[j][k]);
cou++;}
}
}
}
}
LL ans = 0;
for (int i = 0; i < n; ++i) {
ans = max(ans, dp[(li-1)^(1<<i)]);
}
cout << ans << endl;
}
return 0;
}
补充题Doing Homework HDU - 1074
未自己写过题解:https://blog.youkuaiyun.com/qq_37493070/article/details/77336109 #include<cstdio>
#include<iostream>
#include<cmath>
#include<queue>
#include<vector>
#include<cstring>
#include<algorithm>
#include<map>
#include<set>
#include<stack>
#include<sstream>
#include<string>
using namespace std;
#define inf 0x3f3f3f3f
struct pp
{
string name; //课名字
int cost; //写完需要的时间
int dead; //还有几天交
}ss[20]; //存课的信息
struct ps
{
int fa,zj,soce,time;
//fa 父节点 //zj 记录放的那本书 //soce 扣掉的分数 //做完作业所需的时间
}dp[1<<15];
int main()
{
ios::sync_with_stdio(false); //去cin同步 听说会减时间
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
memset(dp,0,sizeof(dp)); //初始化
for(int i=0;i<n;i++)
cin>>ss[i].name>>ss[i].dead>>ss[i].cost;
int end=1<<n;
for(int i=1;i<end;i++) //枚举任意一种情况 例如 1 1 0 前两门完成 0 0 1 最后一门完成
{ dp[i].soce=inf; //初始化 为了找最小所以Inf
for(int j=n-1;j>=0;j--) //看前一步是写的哪一门课的作业 倒着来的原因:If there are more than one orders, you should output the alphabet smallest one. 要输出字典序最小的
{
int temp=1<<j; // temp 代表课的二进制 例第二门课 temp = 1<<(1)(从 0 开始的,所以是 1)就是 1 0 表示写的第二门课
if(temp&i) //看 i 是由那种状态过来的
{ int tem=i-temp; //前一种状态
int tt=dp[tem].time+ss[j].cost-ss[j].dead; //扣掉的分数 ==写完前一门的时间 + 需要写的时间 - 要交的天数
if(tt<0) //扣掉的分数不可能为负数 归 0
tt=0;
if(tt+dp[tem].soce<dp[i].soce) //比较 就是取每一种状态过来的最大值
{
dp[i].soce=tt+dp[tem].soce; //记录扣掉的分数
dp[i].fa=tem; //记录父节点
dp[i].zj=j; //记录写的哪一门课
dp[i].time=dp[tem].time+ss[j].cost; //记录写完所需时间
}
}
}
}
//下面就是输出了 不多说相信大家都能看懂
cout<<dp[end-1].soce<<endl;
stack<int>q;
int tt=end-1;;
while(dp[tt].time)
{
q.push(dp[tt].zj);
tt=dp[tt].fa;
}
while(!q.empty())
{
int k=q.top();
cout<<ss[k].name<<endl;
q.pop();
}
}
return 0;
}
K - Painful Bases LightOJ - 1021
#include <iostream>
#include <string.h>
using namespace std;
long long dp[1<<17][25];
int cou=0;
int main()
{
int c;cin>>c;
int base,k;
char z[20];int num[20];
while(c--){
cou++;memset(dp,0,sizeof(dp));
memset(num,0, sizeof(num));
scanf("\n%d%d",&base,&k);
scanf("%s",z);
int len=strlen(z);
for(int i=0;i<len;i++){
if(z[i]>='0'&&z[i]<='9')num[i]=z[i]-'0';
else num[i]=z[i]-'A'+10;
}
dp[0][0]=1;
int li=1<<len;
for(int s=0;s<li;s++)
for(int mod=0;mod<k;mod++){
if(dp[s][mod]){
for(int j=0;j<len;j++){
if(!(s&(1<<j))){
dp[s|1<<j][(mod*base+num[j])%k]+=dp[s][mod];
}
}
}
}
printf("Case %d: %lld\n",cou,dp[li-1][0]);
}
}
M - Little Pony and Harmony Chest CodeForces - 453B
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int prime[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
const int inf=0x3f3f3f3f;
int n,a[200],ans[200];
int dp[200][80000],contain[60],ans1[200][80000],pre[200][80000];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}int li=1<<16;memset(dp,0x3f,sizeof(dp));
for(int nu=1;nu<60;nu++)
for(int i=0;i<16;i++)
if(nu%prime[i]==0)
contain[nu]|=(1<<i);dp[0][0]=0;//
for(int i=1;i<=n;i++)
for(int nu=1;nu<60;nu++)
for(int s=0;s<li;s++){
if(!(s&contain[nu])&&dp[i][s|contain[nu]]>dp[i-1][s]+abs(a[i]-nu)){
int nes=s|contain[nu];
dp[i][nes]=dp[i-1][s]+abs(a[i]-nu);
ans1[i][nes]=nu;
pre[i][nes]=s;
}
}
int minn=inf,finalstate=0;
for(int s=0;s<li;s++){
if(minn>dp[n][s]){
minn=dp[n][s];
finalstate=s;
}
}
for(int i=n;i>=1;i--){
ans[i]=ans1[i][finalstate];
finalstate=pre[i][finalstate];
}
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
}