题目链接: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\),意义如题。
\(1\leq n,Q\leq 50,0\leq m,k\leq 10,0\leq 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<
然后计算一波时间复杂度。。。\(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
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<=k) ok[i]=1;
}
}
int judge(int x,int y)
{
for (int i=0; i<=10; i++)
if (!(x&(1<