暑期个人赛2补题
D - Kefa and Dishes
状态压缩dp
思路:看数据范围可以猜出是状态压缩dp,n和m最多18,所以我们用一个20位的二进制数来表示所有状态,f[i, j]表示此时的状态是i且最后吃的菜是j,那么接下来来考虑转移。对于每个f[i, j],我们遍历i的二进制位,假如此时j的那一位是1即这个状态是已经吃了j了,那么说明是一个合法状态,可以转移。
然后考虑可以转移给哪些状态。之后吃的一定是还没有吃过的菜,所以我们就去遍历所有菜,找到一个没吃菜k,然后将f[i, j]转移过去。方程:
f[i ^ (1 << k), k] = max(f[i ^ (1 << k), k], f[i, j] + w[j, k] + a[k])
最后因为我们只需要吃m道菜,遍历所有状态的i,找到符合的取一个max。
不要忘记初始化,f[1 << i, i]表示吃了第i道菜,它应该等于第i道菜的幸福值。
code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int n, m, k;
int a[20];
int w[20][20];
LL f[1 << 20][20];
LL ans;
int lowbit(int x)
{
return x & -x;
}
int cal(int u)
{
int cnt = 0;
while (u)
{
cnt ++ ;
u -= lowbit(u);
}
return cnt;
}
int main()
{
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
while (k -- )
{
int x, y, c;
cin >> x >> y >> c;
w[x][y] = c;
}
//cout << w[0][1] << ' ' << w[3][2] << endl;
for (int i = 1; i <= n; i ++ ) f[1 << i][i] = a[i];//初始化
n ++ ;
for (int i = 1; i < (1 << n); i ++ )
for (int j = 1; j <= n; j ++ )
if ((i >> j) & 1)//i状态确实吃了第j道菜即合法状态
{
for (int k = 0; k < n; k ++ )
if (((i >> k) & 1) == 0)//找到一个i状态下还没吃的
f[i ^ (1 << k)][k] = max(f[i ^ (1 << k)][k], f[i][j] + w[j][k] + a[k]);
}
for (int i = 1; i < (1 << n); i ++ )
if (cal(i) == m)//看状态i是不是有m个1即是不是吃了m道菜
{
for (int op = 0; op < n; op ++ )
ans = max(ans, f[i][op]);
}
cout << ans << endl;
return 0;
}
F - Vacations
贪心 / 线性dp都能做
1.贪心做法
我们从头到尾遍历,能不休息就不休息。假设第i天可以不休息,接下来分类讨论:
- i + 1 天是休息日 那么我们如果不让它休息的话,结果一定更优
- i + 1 天和第i天活动一样,因为我们不能连续两天都工作一样的,所以这两天我们一定可以挑出一天来工作,那么以我们上述贪心策略来看,不会影响到最优解。
- i + 1 天和第i天活动不一样,那么既然可以不休息,那么不休息一定会有更优的答案。
至此,贪心思路正确性证明结束,线性扫描一下即可。
code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int a[N];
int ans;
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ )//遍历一遍
{
if (!a[i]) ans ++ ;//如果是休息日,就休息
else if (a[i] == 1)//如果是活动1
{
if (a[i + 1] == 3) a[i + 1] = 2;//后面如果是3,那么就变成2
else if (a[i + 1] == 1) a[i + 1] = 0;//如果也是1,那么i+1天就休息
continue;
}
if (a[i] == 2)
{
if (a[i + 1] == 3) a[i + 1] = 1;
else if (a[i + 1] == 2) a[i + 1] = 0;
}
}
//for (int i = 1; i <= n; i ++ ) cout << a[i] << ' ';
//puts("");
cout << ans << endl;
return 0;
}
2.线性dp
f[i, j]表示第i天的状态是j的时候需要休息的最少的天数
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int f[N][4];
int a[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
memset(f, 0x3f, sizeof f);
f[0][0] = f[0][1] = f[0][2] = 0;
for (int i = 1; i <= n; i ++ )
{
f[i][0] = min(f[i - 1][0], f[i - 1][1]);
f[i][0] = min(f[i][0], f[i - 1][2]);
f[i][0] ++ ;
if (a[i] == 1)
f[i][1] = min(f[i - 1][0], f[i - 1][2]);
else if (a[i] == 2)
f[i][2] = min(f[i - 1][0], f[i - 1][1]);
else if (a[i] == 3)
{
f[i][1] = min(f[i - 1][0], f[i - 1][2]);
f[i][2] = min(f[i - 1][0], f[i - 1][1]);
}
}
//cout << f[1][2] << endl;
//cout << f[2][2] << endl;
int ans = min(f[n][0], f[n][1]);
ans = min(ans, f[n][2]);
cout << ans << endl;
return 0;
}