目录
前言
领导者
分析
代码
空调
分析
代码
面包店
分析
代码
前言
今天是第一部分的最后一天(主打记忆恢复术和锻炼思维),从明天开始主播会逐步更新从位运算到dp问题的常见题型。
领导者(分类讨论)
分析
通过观察奶牛成为领导者的条件,即:
-
名单中包含其品种的所有奶牛
-
名单中包含另一品种的领导者
可以明显的想到任意一种可行的方案必定有至少一个奶牛是因为其满足了包含同类所有奶牛的条件而成为的领导者。
以此为切入点不难想到对于另外一个成为领导者的奶牛可能有两种情况,即:
-
包含另一个领导者
-
包含同类所有奶牛
观察样例GGGHHHHG
,可以发现所有可能成为领导者的G
均在H
之前,换个方向想,第二个出现的品种当且仅当其包含其所有同类时才能成为领导者。
对于后出现的品种只有头部才有可能成为领导者,即:只有一种情况。
所以我们可以分类讨论可以成为领导者的头部,随后在其之前查找可能成为领导者的牛即可。
代码
#include<iostream>
using namespace std;
const int N = 200010;
int n;
char str[N];
int s1[N], s2[N], e[N];
int l;
int main()
{
cin >> n >> str + 1;
for(int i = 1; i <= n; i++)
{
s1[i] = s1[i - 1] + (str[i] == 'G');
s2[i] = s2[i - 1] + (str[i] == 'H');
} //前缀和
for(int i = 1; i <= n; i++)
{
cin >> e[i];
if(str[i] == 'G')
{
if(s1[e[i]] - s1[i - 1] == s1[n])
{
for(int j = 1; j < i; j++)
if(e[j] >= i || s2[e[j]] - s2[j - 1] == s2[n])
l++;
}
}
else
{
if(s2[e[i]] - s2[i - 1] == s2[n])
{
for(int j = 1; j < i; j++)
if(e[j] >= i || s1[e[j]] - s1[j - 1] == s1[n])
l++;
}
}
}
cout << l;
return 0;
}
空调(搜索)
分析
每台空调都有运行成本和降温度数,这让主播一开始以为这是一道背包题?
后来发现其实不是,首先主播找不到递推公式……
随后注意到每台空调降温范围都是固定的并且只有开与不开两种选择。
所以这应该是一道搜索题。
可以注意到数据范围很小。最多有2^10即1000种选择,区间操作的话就算不用差分也只有10*100的操作数,总共乘起来也不到一百万。所以直接暴力搜索就好
代码
// 搜索 + 差分
// 对于每台空调只有开与不开两个选择,最多有2 ^ 10
// 暴搜就好。
#include<iostream>
using namespace std;
const int N = 110, M = 15;
int n, m;
int s[N], s1[N], st[N];
int a[M], b[M], v[M], w[M];
int dfs(int i) //二项分布,参数表示选第几个
{
if(i == m + 1)
{
int x = 0;
for(int j = 1; j < N; j++)
{
x += s1[j];
if(x < st[j])
return 0x3f3f3f3f;
}
return 0;
}
int l = dfs(i + 1); //不使用
s1[a[i]] += w[i], s1[b[i] + 1] -= w[i];
l = min(l, dfs(i + 1) + v[i]);
s1[a[i]] -= w[i], s1[b[i] + 1] += w[i];
return l;
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i++)
{
int x, y, z;
cin >> x >> y >> z;
s[x] += z; s[y + 1] -= z; //差分处理区间操作
}
for(int i = 1; i < N; i++)
st[i] = st[i - 1] + s[i];
for(int i = 1; i <= m; i++)
cin >> a[i] >> b[i] >> w[i] >> v[i];
cout << dfs(1);
return 0;
}
面包店(公式推导 + 二分答案)
分析
初始制作时间为A * tc + B * tm
。
并且每个客人都相互独立,不互相影响,对于每一个客人有一个忍耐上限C
。
如果制作时间超过C
,客人就会生气离开。
我们可以在第1
个客人到来之前升级设备,每次升级都选择饼干或松饼使他们的制作时间减一。
问:至少升级多少次级才不会得罪每个客人。
初始主播以为这是一道贪心题(在过程中升级装备,按需求升级,最终得到的就是最优解),但是注意到只能在第1
个客人之前升级装备就和贪心无关了。
我们设饼干升级x
次,松饼升级y
次,就可以得到下面的不等式:
A * (tc - x) + B * (tm - y) <= c
可以观察到有两个变量,并不能直接解出不等式。
要引入已知,考虑使用二分答案,而使用二分要求区间必须有线性性质。
如何判断是否满足线性性质呢?使用数学归纳法。
我们设升级P
次的所有方案中仅有一种方案满足要求。
那么升级P + 1
次就至少存在一种方案满足要求。
对于P - 1
次,使用反证法设存在一种方案满足要求,即:
A * (tc - x) + B * (tc - y) <= c, x + y = p - 1
我们对x
或y
分别加一,可得两种方案:
-
A * (tc - x - 1) + B * (tc - y) <= c, x + y + 1 = p
-
A * (tc - x) + B * (tc - y - 1) <= c, x + y + 1 = p
很明显上方两种方案均满足要求,即对升级P
次来说有至少两种可行的方案,与假设不符。
所以升级p - 1
次不存在可行方案。证明完成,P
具有单调性。
所以,我们可以使用二分答案。
设P = x + y
,则y = P - x
,代入上式可得:
A * (tc - x) + B * (tm - P + x) <= c
化简
(B - A)*x <= c - A*tc - B(tm - P)
于是我们就得出了关于x
的表达式。
同时根据x
自身的范围0 ~ tc - 1
和y
的范围0 ~ tm - 1
,
可以得出两个不等式:
0 <= x <= tc - 1
0 <= P - x <= tm - 1
随后进行区间合并操作判断区间是否合法即可。
注:因为区间有可能出现负数所以我们需要使用floor
和ceil
函数。
代码
#include<iostream>
#include<cmath>
using namespace std;
typedef long long LL;
const int N = 110;
LL t, n, tc, tm;
LL a[N], b[N], c[N];
bool func(LL x)
{
LL l = max((LL)0, x + 1 - tm), r = min(tc - 1, x);
for(int i = 1; i <= n; i++)
{
double m = c[i] + (double)b[i]*x - (double)a[i]*tc - (double)b[i]*tm;
double n = b[i] - a[i];
if(n == 0 && m < 0) return false;
else if(n > 0)
r = min(r, (LL)floor(m/n));
else if(n < 0)
l = max(l, (LL)ceil(m/n));
}
if(r - l >= 0)
return true;
return false;
}
int main()
{
cin >> t;
while(t--)
{
cin >> n >> tc >> tm;
for(int i = 1; i <= n; i++) cin >> a[i] >> b[i] >> c[i];
LL l = 0, r = tc + tm - 2;
while(l < r)
{
LL m = (l + r) >> 1;
if(func(m))
r = m;
else
l = m + 1;
}
cout << r << '\n';
}
return 0;
}
题目均来自AcWing