zoukankan      html  css  js  c++  java
  • 状压dp题解

    数位dp题解:

    A. 特殊方格棋盘

    • 题意:在(n imes n)的方格棋盘上放置n个车,某些格子不能放,求使它们不能互相攻击的方案总数。注意:同一行或同一列只能有一个车,否则会相互攻击。

    • 分析:

      • 首先,每一行只能有一个车,只要一行行地放,每行只放一个,保证同一行之间不会有攻击。
      • 其次,每一列只能放一个车,只要能记录下哪一列有车,下次不再考虑这一列就可以了。
      • 用一个二进制数S来表示某一列是否已经放置了车。例如n=5, S=01101,就表示第一、三、四列(从低位开始)已经放置了车。我们最终求出状态S=11111时的方案数。
      • 定义f[s]表示在状态s下的方案数。(sin [0sim 2^n-1])
        • 假设 s=01101,显然有:f[01101]= f[01100]+f[01001]+f[00101]
        • 但如果在当前位置有障碍呢?所以我们再放置车的时候判断当前位是否有障碍。具体实现见代码。
    • 代码实现:

      #include<bits/stdc++.h>
      const int maxn=(1<<20)-1;
      typedef long long LL;
      LL f[maxn],a[25];//a[x]记录第x行的障碍状态
      int lowbit(int x){return x & -x;};
      int main(){
          int n,m;scanf("%d%d",&n,&m);
          for(int i=1;i<=m;++i){
              int x,y;scanf("%d%d",&x,&y);
              a[x]+=1<<(y-1);
          }
          f[0]=1;//一个也不放也是一种方案
          int maxs=1<<n;//最大的状态
          for(int S=1;S<maxs;++S){//从状态1开始枚举
              int cnt=0;//计算状态S里的1的个数,几个1说明处理到第几行
              for(int i=S;i;i-=lowbit(i))cnt++;
              for(int i=S;i;i-=lowbit(i)){//依次判断当前状态每一个1
                  if(!(a[cnt] & lowbit(i))){//状态S当前的1所在列如果没有障碍
                      int s=S^lowbit(i);//说明lowbit(i)处可以放车,当前就在此处放车
                      f[S]+=f[s];//s<S,保证能转移
                  }
              }
          }
          printf("%lld
      ",f[maxs-1]);
          return 0;
      }
      

    B. 互不侵犯

    • 题意:在(n imes n) 的棋盘里面放 n个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 个格子。
    • 分析:
      • 此题和我们讲的例题玉米地很相似,少了障碍,但多了个数限制,一般情况多一个限制我们就增加一维的状态。
      • dp[ i ][ j ][ k ] 为第i 行摆放国王状态为 j ,棋盘上国王数目为k的方案数.
      • 阶段很明显,就是我们可以一行一行的处理,第一行比较特殊,我们可以预处理
      • 判断冲突:假设s为当前行,s' 为上一行。
        • 行冲突,即不能相邻,只需判断 (s & (s<<1))==0,不为零说明有相邻。
        • 列冲突:
          • 上下冲突:判断(s & s')==0 ,如果不为0,说明上下冲突。
          • 左上冲突:判断((s<<1) & s')==0,如果不为0,说明冲突。
          • 右上冲突:判断((s>>1) & s')==0,如果不为0,说明冲突。
        • 判断当前状态下状态的1的个数,用lowbit即可。

    D. 旅游景点

    • 题意:n个城市1~n编号,m条道路组成的连通图,要从1n最短路,但必须经过编号为2~k+1k 个城市,这k个城市间,到达某些城市并停留前,必须到达其先到城市,这个地方比较绕,仔细读题意。
    • 分析:
      • 此题就是在最短路的基础上加了路径的限制和先后的限制,k<=20,我们很容易想到用 (0sim 2^k -1) 的状态去枚举路径上到达城市的情况。
      • 如何解决先停留城市呢?题目里明确你可以先到后到城市再到先到城市,只要你不停留,实际上这句话的意思是让你忽略中间城市,只需要保证经过先到城市后必须经过后到城市即可。这样,我们可以先预处理出1~k+1个城市间的最短距离,并任意两城市间均为直接距离,剩下的只要处理冲突和限制即可。
      • 定义:f[s][i],表示经过的并停留的k个城市状态为s,目前到达了城市i的最短距离。
      • 动态转移方程:(f[s][i]=min(f[s][i],f[s'][j]+dis[j][i]) (a[i]==s & a[i]))
        • (a[i])记录的是在(i)停留之前必须到达的城市
        • 必须满足:(s' in s)
    #include<bits/stdc++.h>
    #define pa pair<int,int>
    #define inf 1000000000
    using namespace std;
    const int maxn=2e4+5,maxk=20+2;//1表示起点,0表示终点即n 
    int n,m,K,len,ed;
    int a[maxk];
    int dis[maxk][maxk],d[maxn],head[maxn];
    int f[1<<20][maxk];
    bool vis[maxn];
    struct data{int to,next,dis;}e[400005];
    void Insert(int u,int v,int w){
    	e[++len].to=v;e[len].dis=w;e[len].next=head[u];head[u]=len;
    }
    void dijkstra(int x){
    	priority_queue<pa,vector<pa>,greater<pa> >q;
        for(int i=1;i<=n;i++){d[i]=inf;vis[i]=0;}
    	d[x]=0;q.push(make_pair(0,x));
    	while(!q.empty()){
    		int now=q.top().second;q.pop();
    		if(vis[now])continue;vis[now]=1;
    		for(int i=head[now];i;i=e[i].next)
    			if(d[now]+e[i].dis<d[e[i].to]){
    				d[e[i].to]=d[now]+e[i].dis;
    				q.push(make_pair(d[e[i].to],e[i].to));
    			}
    	}
    	for(int i=1;i<=K+1;i++)
    		dis[x][i]=d[i];
    	dis[x][0]=d[n];//dis[x][0]来记录x到n的最短距离 
    }
    void dp(){
    	for(int now=0;now<=ed;now++)//当前状态 
    		for(int x=1;x<=K+1;x++)//当前状态下最后到达x 
    			if(f[now][x]!=-1)//如果此状态未求出无法推导下一个状态 
    				for(int i=2;i<=K+1;i++){//枚举下一个到达的城市i 
    					int to=(now|(1<<(i-2)));//下一个状态 
    					if((now&a[i])==a[i])//当前状态下必须包含下一个到达城市的先到城市 
    						if(f[to][i]>f[now][x]+dis[x][i]||f[to][i]==-1)
    							f[to][i]=f[now][x]+dis[x][i];
    				}
    }
    int main(){
    	scanf("%d%d%d",&n,&m,&K);
    	ed=(1<<K)-1;//最终状态 
    	for(int i=1;i<=m;i++){
    		int u,v,w;scanf("%d%d%d",&u,&v,&w);
    		Insert(u,v,w);Insert(v,u,w);
    	}//求出1~k+1到其他城市的最短路,即把间接变直接,且为最短 
    	for(int i=1;i<=K+1;i++)dijkstra(i);
    	int x;scanf("%d",&x); 
    	for(int i=1;i<=x;i++){
    		int u,v;scanf("%d%d",&u,&v);
    		a[v]+=1<<(u-2);//把2~k+1平移一位,变成1~k 
    	}
    	memset(f,-1,sizeof(f));//因为距离可能为0,初始化为-1 
    	f[0][1]=0;//状态0,到达城市1的最短距离为0,起点到自己 
    	dp();
    	int ans=inf;
    	for(int i=1;i<=K+1;i++)//dis[i][0]表示i到n的最短路 
    		if(f[ed][i]!=-1)ans=min(ans,f[ed][i]+dis[i][0]);
    	printf("%d",ans);
    	return 0;
    }
    
  • 相关阅读:
    数据结构(2)链表的实现
    vc 调试方法-2
    c语法拾遗-关于指针变量的声明
    收集的一些无聊的网站
    《将博客搬至CSDN》的文章
    黑马程序员-面向对象
    黑马程序员-类加载机制和反射。
    黑马程序员- 正则表达式
    黑马程序员-网络编程
    黑马程序员-File类+递归的简单应用
  • 原文地址:https://www.cnblogs.com/hbhszxyb/p/12710188.html
Copyright © 2011-2022 走看看