感觉现在只会用比较无脑比较暴力的状压dp,完全没思考,就是枚举所有的状态,等集训结束了搜点要努力想dp方程的题做做。
hdu3681 prison break
大体意思就是给了张地图,走路要耗费能量,有能量池能补充能量,求要走完特定的几个点初始能量的最小值
因为给的点很少所以可以用状压dp,走过的各自可以重复走所以图本身的意义就不大了,就是首先要spfa求一下各点之间的距离
dp[s][i]=max(dp[s-(1<<i)][j]+dis[i][j])
我用了二分找初始能量的最小值,dp的过程很暴力就过了,这部分做的几道题都比较基础,一般都能暴力过。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
#define INF 10000000
typedef struct P
{
int x,y;
} POINT;
char map[16][16];
int n,cnt,dp[35000][16],cnt_y,m,dir[4][2]={0,1,0,-1,1,0,-1,0},d[16][16],num[16][16];
bool judge (int x,int y)
{
if(x>=0&&x<n&&y>=0&&y<m&&map[x][y]!='D') return 1;
return 0;
}
void ini()
{
memset(num,0,sizeof(num));
for(int i=0;i<=15;i++)
for(int j=0;j<=15;j++)
d[i][j]=INF;
cnt_y=0;
cnt=1;
return;
}
void input()
{
for(int i=0;i<=n-1;i++)
{
scanf("%s",map[i]);
}
for(int i=0;i<=n-1;i++)
for(int j=0;j<=m-1;j++)
if(map[i][j]=='Y')
{
cnt_y++;
num[i][j]=cnt++;
}
for(int i=0;i<=n-1;i++)
for(int j=0;j<=m-1;j++)
if(map[i][j]=='G')
{
num[i][j]=cnt++;
}
for(int i=0;i<=cnt;i++)
for(int j=0;j<=cnt;j++)
d[i][j]=INF;
return;
}
void spfa(int x,int y)
{
POINT now,next;
now.x=x;
now.y=y;
queue<POINT> Q;
Q.push(now);
int a[16][16],v[16][16];
memset(v,0,sizeof(v));
for(int i=0;i<=cnt;i++)
for(int j=0;j<=cnt;j++)
a[i][j]=INF;
a[x][y]=0;
while(!Q.empty())
{
now=Q.front();
Q.pop();
v[now.x][now.y]=0;
for(int i=0;i<=3;i++)
{
next.x=now.x+dir[i][0];
next.y=now.y+dir[i][1];
if(judge(next.x,next.y)&&a[now.x][now.y]+1<=a[next.x][next.y])
{
a[next.x][next.y]=a[now.x][now.y]+1;
if(num[next.x][next.y]||map[next.x][next.y]=='F') d[num[next.x][next.y]][num[x][y]]=a[now.x][now.y]+1;
if(!v[next.x][next.y])
{
Q.push(next);
v[next.x][next.y]=1;
}
}
}
}
return;
}
int cnt_bit(int x)
{
int ans=0,tmp,j=1;
while(x)
{
if(x&1)
{
ans++;
tmp=j;
}
x>>=1;
j++;
}
if(ans==1) return tmp;
return 0;
}
bool check(int x)
{
for(int i=0;i<=cnt_y-1;i++)
{
if(x&(1<<i)) continue;
else return 0;
}
return 1;
}
int deal(int energy)
{
memset(dp,-1,sizeof(dp));
for(int i=1;i<=(1<<cnt)-1;i++)
{
int tmp=cnt_bit(i);
if(tmp)
{
if(energy>=d[tmp][0])
{
dp[i][tmp-1]=energy-d[tmp][0];
if(tmp>cnt_y) dp[i][tmp-1]=energy;
}
continue;
}
int j=0;
while((1<<j)<=i)
{
if((1<<j)&i)
{
int k=0;
while((1<<k)<=i)
{
if(k!=j&&(i&(1<<k)))
{
if(dp[i-(1<<j)][k]>=d[j+1][k+1])
{
dp[i][j]=max(dp[i-(1<<j)][k]-d[j+1][k+1],dp[i][j]);
if(j>=cnt_y) dp[i][j]=energy;
}
}
k++;
}
if(check(i)&&dp[i][j]>=0)
{
int a=1;
return 1;
}
}
j++;
}
}
return 0;
}
int binary_search(int l,int r)
{
if(r-l<=1)
{
if(deal(l)) return l;
else return r;
}
int m=(l+r)/2;
if(deal(m)) return binary_search(l,m);
return binary_search(m+1,r);
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF&&(n!=0||m!=0))
{
ini();
input();
for(int i=0;i<=n-1;i++)
for(int j=0;j<=m-1;j++)
if(map[i][j]=='F'||num[i][j]) spfa(i,j);
int ans=binary_search(1,10000);
if(deal(ans))
printf("%d\n",ans);
else printf("-1\n");
}
return 0;
}
hdu1789 doing homework
题意就是许多作业,给你完成各项作业需要的时间和交作业的时限,晚交一天减一分,求减的最小分数。
一看见这种题就想用贪心,同样N给的比较小,又是状压DP的专题,于是就没仔细考虑贪心的问题。。
dp[s][i]=max(0,dp[s-(1<<i)][j]+time[i]-end[i])
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
#define INF 100000000
int n,parent[35000],dp[35000],t[16],e[16],tmp1[16],tmp2[16];
string subject[20];
void ini()
{
memset(parent,-1,sizeof(parent));
for(int i=0;i<=(1<<n)-1;i++)
dp[i]=INF;
return;
}
void input()
{
scanf("%d",&n);
for(int i=0;i<=n-1;i++)
{
cin>>subject[i]>>e[i]>>t[i];
}
for(int i=0;i<=(1<<n)-1;i++)
dp[i]=INF;
dp[0]=0;
return;
}
int time(int x)
{
int i=0,ans=0;
while(x)
{
if(x&1) ans+=t[i];
x>>=1;
i++;
}
return ans;
}
void exchange(int sit,int a[],int &cnt)
{
if(sit==0) return;
exchange(sit-(1<<parent[sit]),a,cnt);
a[cnt++]=parent[sit];
return;
}
bool check(int sit1,int sit2)
{
int cnt1=0,cnt2=0;
exchange(sit1,tmp1,cnt1);
exchange(sit2,tmp2,cnt2);
int i;
for(i=0;i<cnt1&&i<cnt2;i++)
{
if(tmp1[i]>tmp2[i]) return 1;
}
if(i!=cnt2&&i==cnt1) return 1;
return 0;
}
void deal()
{
for(int i=1;i<=(1<<n)-1;i++)
{
int j=0;
while((1<<j)<=i)
{
if(i&(1<<j))
{
int a=max(0,time(i-(1<<j))+t[j]-e[j])+dp[i-(1<<j)];
if(dp[i]>a)
{
dp[i]=a;
parent[i]=j;
}
else if(dp[i]==a)
{
if(check(i-(1<<parent[i]),i-(1<<j))) parent[i]=j;
}
}
j++;
}
}
return;
}
void output()
{
int x=(1<<n)-1;
cout<<dp[x]<<endl;
int tmp[16],j=n-1;
while(x)
{
int v=parent[x];
tmp[j--]=v;
x-=(1<<v);
}
for(int i=0;i<=n-1;i++)
cout<<subject[tmp[i]]<<endl;
return;
}
int main()
{
int t;
cin>>t;
while(t--)
{
ini();
input();
deal();
output();
}
return 0;
}
hdu 4856 Tunnels
题意:一张地图,上面有许多通道,给你各个通道的起点和终点,问要走所有的通道,在通道之间走的距离的最小值是多少。
跟A题几乎完全一样,SPFA求出各个通道的起点到其他各个通道终点的最短距离,然后用状压dp暴力解决。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define INF 100000000
char map[25][25];
int dir[4][2]={0,1,0,-1,1,0,-1,0},n,m,dis[20][20],dp[35000][20];
typedef struct point{
int x,y;
}POINT;
typedef struct tunnel{
POINT s,e;
}TUNNEL;
TUNNEL t[20];
bool check(POINT a)
{
int x=a.x,y=a.y;
if(x>=0&&x<n&&y>=0&&y<n&&map[x][y]!='#') return 1;
return 0;
}
void spfa(POINT x,int num)
{
POINT now,next;
queue<POINT> Q;
Q.push(x);
bool v[20][20];
int d[20][20];
for(int i=0;i<=n-1;i++)
for(int j=0;j<=n-1;j++)
d[i][j]=INF;
d[x.x][x.y]=0;
memset(v,0,sizeof(v));
while(!Q.empty())
{
now=Q.front();
Q.pop();
v[now.x][now.y]=0;
for(int i=0;i<=3;i++)
{
next.x=now.x+dir[i][0];
next.y=now.y+dir[i][1];
if(check(next)&&d[next.x][next.y]>d[now.x][now.y]+1)
{
d[next.x][next.y]=d[now.x][now.y]+1;
if(!v[next.x][next.y])
{
Q.push(next);
v[next.x][next.y]=1;
}
}
}
}
for(int i=0;i<=m-1;i++)
{
TUNNEL tmp=t[i];
if(num!=i)
dis[num][i]=d[tmp.s.x][tmp.s.y];//从num到i,num是出口
else dis[num][i]=0;
}
return;
}
void initial()
{
for(int i=0;i<=m-1;i++)
for(int j=0;j<=m-1;j++)
if(i!=j) dis[i][j]=INF;
for(int i=0;i<=(1<<m)-1;i++)
for(int j=0;j<=m-1;j++)
dp[i][j]=INF;
return;
}
void input()
{
for(int i=0;i<=n-1;i++)
scanf("%s",map[i]);
for(int i=0;i<=m-1;i++)
{
POINT S,E;
scanf("%d%d%d%d",&S.x,&S.y,&E.x,&E.y);
S.x--;
S.y--;
E.x--;
E.y--;
t[i].s=S;
t[i].e=E;
}
return;
}
int cnt_bit(int x)
{
int ret=0,tmp,j=0;
while(x)
{
j++;
if(x&1)
{
ret++;
tmp=j;
}
x>>=1;
}
if(ret==1) return tmp;
return 0;
}
void deal()
{
for(int i=1;i<=(1<<m)-1;i++)
{
int j=0,tmp=cnt_bit(i);
if(tmp)
{
dp[i][tmp-1]=0;
continue;
}
while((1<<j)<=i)
{
if(i&(1<<j))
{
int k=0;
while((1<<k)<=i)
{
if(j!=k&&(i&(1<<k)))
{
dp[i][j]=min(dp[i-(1<<j)][k]+dis[k][j],dp[i][j]);
}
k++;
}
}
j++;
}
}
return;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
initial();
input();
for(int i=0;i<=m-1;i++)
spfa(t[i].e,i);
deal();
int ans=INF;
for(int i=0;i<=m-1;i++)
{
ans=min(ans,dp[(1<<m)-1][i]);
}
if(ans==INF) ans=-1;
printf("%d\n",ans);
}
return 0;
}
Hdu 4568 Hunter 同上= = 函数里一个m写成n调了一个上午。。什么时候改了这个毛病- -
poj 1185 炮兵阵地
超级经典的题,总之地图上是山的地方不能放,横着竖着两格之内放了阵地的不能放,问最多能放多少个阵地
因为求一行的时候要知道前两行的状态,所以dp中要一次存这一行和上一行的状态,当然还要存行数,第一次做的时候就被这个状态坑了,如果真把所有状态都存起来肯定要爆内存的,纠结好一会儿没敢写,上网搜了以后才知道因为阵地之间至少隔两个格,那么满足条件的状态其实是确定的,先把这些状态打表就好了,至于为什么只有60种,等集训完了再证一下好了。
dp[row][s][pre]=max(dp[row-1][pre][pre_pre]+cnt_bit(s));
打表的时候因为用得递归所以大数放在前面了,下面递归的时候想当然地以为第0个状态就是0,又是坑了一上午,蠢哭,最后还是拿出以前的代码对拍才过的。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
int map[105],n,m,dp[103][100][100],s[100],amo,v[103][100][100];
void input()
{
char tmp[15];
for(int i=0;i<=n-1;i++)
{
scanf("%s",tmp);
for(int j=0;j<=m-1;j++)
{
map[i]<<=1;
if(tmp[j]=='P')
map[i]+=0;
else map[i]+=1;
}
}
return;
}
int bit_cnt(int x)
{
int cnt=0;
while(x)
{
if(x&1) cnt++;
x>>=1;
}
return cnt;
}
bool check(int sit,int ground)
{
if(sit&ground) return 0;
return 1;
}
void pre(int sit,int bit)
{
if(bit==m)
{
s[amo++]=sit;
return;
}
if(!(sit&2)&&!(sit&1)) pre(sit<<1|1,bit+1);
pre(sit<<1,bit+1);
return;
}
void dfs(int row,int sit1,int sit2)
{
if(row==n-1)
{
dp[row][sit1][sit2]=bit_cnt(s[sit1]);
return;
}
for(int i=0;i<=amo-1;i++)
{
if(check(s[i],map[row+1])&&check(s[i],s[sit1])&&check(s[i],s[sit2]))
{//i=3和4
if(!v[row+1][i][sit1])
{
v[row+1][i][sit1]=1;
dfs(row+1,i,sit1);
}
dp[row][sit1][sit2]=max(dp[row+1][i][sit1]+bit_cnt(s[sit1]),dp[row][sit1][sit2]);
}
}
return;
}
void init()
{
memset(dp,0,sizeof(dp));
amo=0;
memset(map,0,sizeof(map));
memset(v,0,sizeof(v));
return;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
pre(0,0);
input();
for(int i=0;i<=amo-1;i++)
{
if(check(s[i],map[0]))
{
dfs(0,i,amo-1);
}
}
int ans=0;
for(int i=0;i<=amo-1;i++)
ans=max(ans,dp[0][i][amo-1]);
printf("%d\n",ans);
}
return 0;
}
poj 2411 Mondriaan's Dream
铺砖的题,1*2的砖,问铺满给定区域有多少种铺法,这题也是学长当时讲的例题,如果自己想的话真是无从下手。
把竖着放的砖的上半部分计为1,即1表示下面必须为半块砖。
然后枚举每一行的每一个状态,暴力求解。
一开始就想在一个递归里面解决整个问题,后来发现我的做法是在一行的状态的值求完之前就去求下面的状态,导致有最后的值有加重了的现象,然后脑子一抽干脆每次递归出一个状态都在这个状态上+1,成了个赤裸裸的DFS,但是还没等着TLE,样例就没过,然后没耐心了,其实也想到要先求完上一行的所有状态,但是当时总想着递归解决,还想要递归套递归,实在不好处理,翻出以前的代码恍然大悟。。这尼玛就是两层循环加一个递归= =
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
__int64 h,w,dp[12][2500];
void dfs(__int64 father,__int64 sit,__int64 bit,__int64 row)
{
if(bit==w)
{
dp[row][sit]+=dp[row-1][father];
return;
}
if((father&(1<<(w-bit-1)))) dfs(father,sit<<1,bit+1,row);
else
{
if(bit<=w-2&&!(father&(1<<(w-bit-2)))) dfs(father,sit<<2,bit+2,row);
dfs(father,sit<<1|1,bit+1,row);
}
return;
}
int main()
{
while(scanf("%I64d%I64d",&h,&w)!=EOF&&(h!=0||w!=0))
{
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(__int64 i=1;i<=h;i++)
for(__int64 j=0;j<=(1<<w)-1;j++)
if(dp[i-1][j]!=0) dfs(j,0,0,i);
printf("%I64d\n",dp[h][0]);
}
return 0;
}
果然不冷静的时候什么都干不了= =
还剩一个D题,总是懒得思考想暴力解出来,T了N次,还是得用脑想吧- -做出来再贴