状态压缩:其实是把一个状态弄成二进制放在dp转移量里,运用位运算操作。
CSP—收集卡牌所涉及的主要是状态压缩与记忆化搜索,有关这方面的题,接触的并不多。记忆化搜索经常伴随着状态压缩,防止TLE,计算过的不用再次计算,当然更快了。
因为数组开成了三维,会出现MLE
#include<bits/stdc++.h>
using namespace std;
const int N = 16, M = 5;
int n, k;
double p[N];
double f[1 << N][N][M * N]; //这个f中的left是可有可无的因为,col集合就表示了还有多少个卡片未抽中
//已抽到集合col,还剩下left个卡未抽,目前的硬币数为coins的期望抽卡次数
double dp(int col, int left,int coins)
{
if(f[col][left][coins]>=0)
return f[col][left][coins];//记忆化搜索
if(left * k <= coins) //兑换,无需再进行抽卡 ,返回0,
return f[col][left][coins] = 0;
f[col][left][coins] = 0;
for(int i = 0; i < n; i ++)
if(col & (1 << i))//如果抽到的已经有的卡牌,得到硬币
f[col][left][coins] += p[i] * (dp(col, left, coins + 1) + 1); //不要忘记+1
else //抽到没有的卡牌
f[col][left][coins] += p[i] * (dp(col | (1 << i), left-1, coins) + 1);//不要忘记+1
return f[col][left][coins];
}
int main()
{
ios::sync_with_stdio(false);
double res = 0;
//读入
cin >> n >> k;
for (int i = 0; i < n; i ++)
cin >> p[i];
memset(f, -1, sizeof f);
for(int i = 0; i < n; i ++)
res += p[i]*(dp(1 << i, n - 1, 0) + 1); //不要忘记+1
//输出
printf("%.10lf\n",res);
}
AC的代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N = 16, M = 5;
int n, k;
double p[N];
double f[1 << N][M * N]; //如果不把left去掉会出现MLE,所以去掉left
//已抽到集合col,还剩下left个卡未抽,目前的硬币数为coins的期望抽卡次数
double dp(int col, int left,int coins)
{
if(f[col][coins]>=0)
return f[col][coins];//记忆化搜索
if(left * k <= coins) //兑换,无需再进行抽卡 ,返回0,
return f[col][coins] = 0;
f[col][coins] = 0;
for(int i = 0; i < n; i ++)
if(col & (1 << i))//如果抽到的已经有的卡牌,得到硬币
f[col][coins] += p[i] * (dp(col, left, coins + 1) + 1); //不要忘记+1
else //抽到没有的卡牌
f[col][coins] += p[i] * (dp(col | (1 << i), left-1, coins) + 1);//不要忘记+1
return f[col][coins];
}
int main()
{
ios::sync_with_stdio(false);
double res = 0;
//读入
cin >> n >> k;
for (int i = 0; i < n; i ++)
cin >> p[i];
memset(f, -1, sizeof f);
for(int i = 0; i < n; i ++)
res += p[i]*(dp(1 << i, n - 1, 0) + 1); //不要忘记+1
//输出
printf("%.10lf\n",res);
}
洛谷—单词游戏有关这道题,看别人的代码了解了快写,快读(看别人的代码才能学习,但是也太痛苦了)。算法标签为 状态压缩和记忆搜索。很经典的状态压缩+记忆搜索。
#include<bits/stdc++.h>
using namespace std;
const int N = 17;
int f[N][1 << N];//最后一个是第i个word
string word[N];
int n;
int dp(int tail, int coll)
{
if(f[tail][coll])
return f[tail][coll]; //记忆化搜索
// f[tail][coll] = 0; 这个也没有必要
for(int i = 1; i <= n ; i++)
if((word[i][0] == word[tail][word[tail].size()-1]) && ! (coll & (1 << (i-1))) )
f[tail][coll] = max(f[tail][coll], dp(i, coll | (1 << (i-1))) );
return f[tail][coll] += word[tail].size();
}
int main()
{
int res = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i ++)
cin >> word[i];
// memset(f, -1, sizeof f);其实无必要
for(int i = 1; i <= n; i++)
res = max(res, dp(i, 1 << (i-1)));
cout << res << endl;
return 0;
}
洛谷—吃奶酪;这可是我写出的第一个状压+记忆化搜索,庆祝一下。这三道题大同小异。
#include<bits/stdc++.h>
using namespace std;
const int N = 15;
double f[1 << N][N]; //最后经过N,且走过了集合1<< N
int n;
double dist[N][N];
pair<double, double> xy[N];
//计算两点距离
inline double calDis(pair<double, double> xy1, pair<double, double> xy2)
{
return sqrt((xy1.first-xy2.first)*(xy1.first-xy2.first) + (xy1.second-xy2.second)*(xy1.second-xy2.second));
}
double dp(int col, int tail)
{
if(f[col][tail]>=0)
return f[col][tail];//记忆化搜索,防止TLE
f[col][tail] = 0x3f3f3f3f;
for(int i = 0; i < n; i ++)
if(!((1 << i) & col)) //这个点未走过
f[col][tail] = min(f[col][tail], dp(col | (1 << i), i) + dist[tail][i]);
f[col][tail]= f[col][tail] == 0x3f3f3f3f? 0:f[col][tail]; //请好好看看该怎么写,被自己无语到
return f[col][tail];
}
int main()
{
double res = 0x3f3f3f3f;
ios::sync_with_stdio(false);
cin >> n;
for(int i = 0; i < n; i ++)
cin >> xy[i].first >> xy[i].second;
//计算距离矩阵
for(int i = 0; i < n; i ++)
for(int j = 0; j < i; j ++)
dist[j][i] = dist[i][j] = calDis(xy[i], xy[j]);
memset(f,-1, sizeof f); //必须的
//老鼠从0,0开始
//从每个点出发找最小值
for(int i = 0; i < n; i ++)
res = min(res, dp(1 << i, i) + calDis(xy[i],make_pair(0,0)));
//输出
printf("%.2lf\n",res);
return 0;
}