zoukankan      html  css  js  c++  java
  • CSUST 2007-我爱吃烧烤(状压DP)

    题目链接:http://acm.csust.edu.cn/problem/2007
    CSDN食用链接:https://blog.csdn.net/qq_43906000/article/details/107654460
    Description

    烧烤真的很好吃唉!集训队的团建除了佰烧,下馆子就是烧烤啦!

    这天集训队一群毒瘤想出去吃烧烤,这里一共有(n)个烧烤店,编号(1,2,...,n),这(n)个烧烤店中有(m)个特殊的烧烤店,初始时大家在1号烧烤店,他们想尝试其中至少(k)个不同的特殊的烧烤店。从任意两个烧烤店(x,y)走过去消耗的体力值都为(1),注意你在当前的烧烤店停留一次也会消耗(1)点体力值。(mp[i][j])表示从烧烤店(i)(j)的方案数。问他们恰好消耗(Q)点体力值且能品尝到至少(k)个不同的特殊烧烤店的方案数。数据保证11号烧烤店不是特殊的烧烤店。答案可能很大,输出模(20190802)后的值。

    Input
    第一行五个整数,分别表示(n,m,k,Q)

    接下来一行(m)个整数(a_i)表示特殊烧烤店的编号。

    接下来一个(n)(n)列的矩阵(mp),意义如题。

    (1leq n,Qleq 50,0leq m,kleq 10,0leq mp[i][j]leq1000)

    Output
    输出一行一个整数表示答案。

    Sample Input 1
    5 1 1 4
    5
    2 1 0 0 0
    3 1 1 0 0
    4 1 0 1 0
    5 1 0 0 1
    6 1 0 0 0

    Sample Output 1
    1

    Sample Input 2
    1 0 0 10
    26

    Sample Output 2
    14277670

    Sample Input 3
    11 2 2 10
    6 11
    1 1 0 0 0 0 1 0 0 0 0
    2 1 1 0 0 0 1 0 0 0 0
    3 1 0 1 0 0 1 0 0 0 0
    4 1 0 0 1 0 1 0 0 0 0
    5 1 0 0 0 1 1 0 0 0 0
    6 1 0 0 0 0 1 0 0 0 0
    7 1 0 0 0 0 1 1 0 0 0
    8 1 0 0 0 0 1 0 1 0 0
    9 1 0 0 0 0 1 0 0 1 0
    10 1 0 0 0 0 1 0 0 0 1
    11 1 0 0 0 0 1 0 0 0 0
    Sample Output 3
    2

    Hint

    对于样例1:你只有一种走法能在规定体力消耗内吃到至少一个特殊烧烤店:1->2->3->4->5,方案数为(1 * 1 * 1 * 1=1)

    对于样例2:要求吃到至少0个特殊烧烤店,也就是说你可以一个特殊烧烤店都不去,方案数为26^10 mod 20190802 =14277670

    emmm,这道题挺好想的QAQ,至少现在看来是这样的。

    看题目的数据范围,我们很容易知道用状压DP来解决,我们状压m个特殊的店于是就有了(dp[1<<11]),考虑到要恰好消耗(Q)点体力,所以我们要再加上一维,就变成了了(dp[1<<11][55]),但还有问题没有解决,也就是最后停留的点可能是(1-n)中的任意一点,所以我们还要再加上一维停留点:(dp[1<<11][55][55])。计算一下空间,发现差不多了,应该不用再加了

    接下来就是状态转移了,其中上面所说的三维肯定要枚举的,我们直接枚举上一个状态,然后再枚举上一个状态的最终停留点,再枚举上一个点所消耗的体力,那么要做状态转移的话肯定还要加上现在要去的点,于是就有了以下代码段:

    dp[0][0][1]=1;
    for (int i=0; i<(1<<m); i++) {
    	for (int last=1; last<=n; last++) {
    		if (vis[last] && !judge(i,1<<(vis[last]-1))) continue;
    		//vis记录的是特殊点的编号,如果上一个点是特殊点,那么一定不会和上一个特殊点状态集矛盾
    		for (int pw=0; pw<q; pw++) {
    			if (!dp[i][pw][last]) continue;//小优化
    			for (int now=1; now<=n; now++) {
    				if (!mp[last][now]) continue;//小优化
    				/*DP*/
    			}
    		}
    	}
    }
    

    然后计算一波时间复杂度。。。(O(2000*50*50*50)),emmm,让我冷静一波,感觉似乎不能优化了啊,想法也应该没什么毛病,没办法了,只能硬着头皮刚一波再说了,说不定数据跑不满(水)呢,于是就有了以上的小优化。

    接下来我们考虑转移,对于转移,应该有两种方式,第一个是现在要去的点为特殊点的时候,另一个就是非特殊点的时候,那么就有了一下转移方程:

    if (vis[now]) {
    	int sta=i|(1<<(vis[now]-1));
    	dp[sta][pw+1][now]=(dp[sta][pw+1][now]+dp[i][pw][last]*mp[last][now])%mod;
    } 
    else dp[i][pw+1][now]=(dp[i][pw+1][now]+dp[i][pw][last]*mp[last][now])%mod;
    

    最后枚举一下最终状态和落脚点就行了。。。然后你就会发现。。你似乎过不了样例????

    感觉天衣无缝啊,冷静分析一波。似乎体力的枚举不应该在里面,每次枚举消耗一个体力的时候应该跑完整个图的,那么也就是说体力的枚举应该放在最外面,然后(try)一波。。。。AC!妈妈,我终于会DP了QAQ

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const int mod=20190802;
    
    ll dp[1<<11][55][55];//状态为sta,消耗q点体力,当前点为x号点的方案数
    int b[100],mp[55][55],ok[1<<11];
    int vis[55];
    
    int digt(int x)
    {
    	int ans=0;
    	while (x){
    		if (x&1) ans++;
    		x>>=1; 
    	}
    	return ans;
    }
    
    void pre_oksta(int m,int k)
    {
    	for (int i=0; i<(1<<m); i++){
    		int nb=digt(i);
    		if (nb>=k) ok[i]=1;
    	}
    }
    
    int judge(int x,int y)
    {
    	for (int i=0; i<=10; i++)
    		if (!(x&(1<<i)) && (y&(1<<i))) return 0;
    	return 1;
    }
    
    int main(int argc, char const *argv[])
    {
    	int n,m,k,q;
    	scanf ("%d%d%d%d",&n,&m,&k,&q);
    	for (int i=1; i<=m; i++) scanf ("%d",&b[i]),vis[b[i]]=i;
    	for (int i=1; i<=n; i++)
    		for (int j=1; j<=n; j++)
    			scanf ("%d",&mp[i][j]);
    	pre_oksta(m,k);
    	dp[0][0][1]=1;
    	for (int pw=0; pw<q; pw++){//枚举体力
    		for (int i=0; i<(1<<m); i++){//枚举上一个状态
    			for (int last=1; last<=n; last++){//枚举上一个落脚点
    				if (vis[last] && !judge(i,1<<(vis[last]-1))) continue;
    				if (!dp[i][pw][last]) continue;
    				for (int now=1; now<=n; now++){//枚举现在要去的
    					if (!mp[last][now]) continue;
    					if (vis[now]){
    						int sta=i|(1<<(vis[now]-1));
    						dp[sta][pw+1][now]=(dp[sta][pw+1][now]+dp[i][pw][last]*mp[last][now])%mod;
    					} 
    					else dp[i][pw+1][now]=(dp[i][pw+1][now]+dp[i][pw][last]*mp[last][now])%mod;
    				}
    			}
    		}
    	}
    	ll ans=0;
    	for (int i=0; i<(1<<(m+1)); i++){
    		if (!ok[i]) continue;
    		for (int j=1; j<=n; j++)
    			ans=(ans+dp[i][q][j])%mod;
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
    路漫漫兮
  • 相关阅读:
    [Liferay6.2.2]AUI的小坑:input的type属性
    官方Tomcat 8.0.24 Web漏洞整改记录
    通过ajax访问Tomcat服务器web service接口时出现No 'Access-Control-Allow-Origin' header问题的解决办法
    前端开发之BOM和DOM
    前端开发之JavaScript
    前端开发之CSS
    前端开发之HTML
    python编程之进程
    python编程之操作系统基础
    python网络编程之socket
  • 原文地址:https://www.cnblogs.com/lonely-wind-/p/13395416.html
Copyright © 2011-2022 走看看