9.25题解
T1
正常模拟即可,下面 code 的判断保证时间晚的答案不会被更新。
#include <iostream>
using namespace std;
int n, ans, maxn = -1, r[100050];
int main()
{
cin >> n;
for(int i = 0, a, b;i < n;++i)
{
cin >> a >> b, r[a] += b;
if(r[a] > maxn)
maxn = r[a], ans = a;
}
cout << ans;
return 0;
}
T2
$10^{18}$,带模拟妥妥 T 飞。
我们知道,一次课表循环结束后,如果当前体力足够再进行一次这个循环,那么下一次循环将会重复这次循环。
所以我们就得到了上课方案:
- 扫一遍课表,找到可行的方案。
- 一直重复这个方案,统计答案,直到体力不够为止。
- 返回 1,找新的可行方案。
- 如果 1 中没有可上的课,跳出循环。
我们就得到了 code:
#include <iostream>
#define int long long
using namespace std;
int n, t, a[1050], sum, cnt, ans;
signed main()
{
cin >> n >> t;
for(int i = 0;i < n;++i)
cin >> a[i];
while(1)
{
sum = cnt = 0;
for(int i = 0;i < n;++i)
if(t >= sum + a[i]) sum += a[i], ++cnt;
if(!cnt) break;
while(t >= sum) t -= sum, ans += cnt;
}
cout << ans;
return 0;
}
然而还是 T 飞的。
我们知道,减法统计个数,等价于除法。
#include <iostream>
#define int long long
using namespace std;
int n, t, a[1050], sum, cnt, ans;
signed main()
{
cin >> n >> t;
for(int i = 0;i < n;++i)
cin >> a[i];
while(1)
{
sum = cnt = 0;
for(int i = 0;i < n;++i)
if(t >= sum + a[i]) sum += a[i], ++cnt;
if(!cnt) break;
ans += t / sum * cnt, t %= sum;
}
cout << ans;
return 0;
}
T3
下面依旧分割线划分结论。
其实刀里面有很多都是没有意义的,要先去掉。
结论1:在硬度最大的刀中,耐久最大的刀一定有意义
废话。
结论2:在硬度相同的刀中,只有耐久最大的刀有意义
如果两把刀硬度一样,耐久一大一小,
那么耐久小的刀能切的,耐久大的也能切,切的还比耐久小的多,
所以耐久小的就没必要用了。
结论3:若 a 硬度 < b 硬度,则当且仅当 a 耐久 > b 耐久时,a 有意义
如果一把刀硬度比别的小,那么如果耐久也比别的小,
它能切的别的刀也能切,而且别的刀切得还多,
所以只有当它耐久比别的刀大的时候,它才有意义。
我们现在要去掉没有意义的刀。
显而易见,硬度大的刀的优先级肯定较高,
所以我们先按硬度第一关键字,耐久第二关键字排序。
接下来,既然硬度越来越低,根据结论3,耐久就要越来越高。
根据结论一,排序后的第一把刀肯定要选。
也就是找一个包含第一把刀的耐久上升子序列即可。
结论2是满足的,因为:
假设原来刀序列为 p,$p[i,j]$ 区间内的刀硬度一样,耐久降序。
- 如果 p[i] 没有选入上升序列,那么后面的刀耐久 < p[i],也不可能选入上升序列。
- 如果 p[i] 选入上升序列,那么后面的刀耐久 < p[i],不可能跟在 p[i] 的后面。
所以,当硬度相同时,这个方案只会选择耐久最大的刀,满足结论2。
我们现在要给每个瓜找刀。
我们先考虑简单的给每个瓜 lower_bound
。
这个方法的缺陷在于,中间是不能换刀的,一把刀爆炸之前不能用另一把。
但我们可以假设换刀之前用的也是新刀,旧刀就没有用过。
那么每次找的刀就不应该找当前的瓜,而是找当前刀切过最硬的瓜。
这里就会有一个问题:如果新刀的耐久 < 旧刀切过的瓜,不可能把旧刀的瓜算到新刀上,怎么处理?
这种情况就可以不管,直接按照耐久正常炸掉处理(反正都是炸)。
#include <iostream>
#include <utility>
#include <algorithm>
#include <functional>
using namespace std;
pair<int, int> p[300050], q[300050];int n, m, a[300050], cnt = 1, ans = 1, n_max = -1, n_nj;
bool cmp(pair<int, int> a, int b) {return a.first < b;}
int main()
{
cin >> n >> m;
for(int i = 0;i < n;++i)
cin >> a[i];
for(int i = 0;i < m;++i)
cin >> p[i].first >> p[i].second;
sort(p, p + m, greater<pair<int, int> >());
for(int i = 0;i < m;++i)
{
if(!i) q[i] = p[i];
else if(q[cnt - 1].second < p[i].second) //找上升序列
q[cnt++] = p[i];
}
reverse(q, q + cnt);
for(int i = 0;i < n;++i)
{
n_max = max(n_max, a[i]);++n_nj;
pair<int, int> *t = lower_bound(q, q + cnt, n_max, cmp);
if(t -> second < n_nj) //同时处理新刀不符合题意 & 耐久炸掉
++ans, n_nj = 1, n_max = a[i];
}
cout << ans;
return 0;
}
T4
设 $dp[i][j][0/1]$ 表示 $i$ 星球属于 $[i-j+1,i]$ 这个势力(0/1 分别表示一个势力)。
那我们讨论 $i$ 星球与之前的势力是否相同:
- 不同,即 $j=1$:
这个状态可以通过 $dp[i-1][j](l≤j≤r)$ 的状态得到,
也就是枚举 $j$,$dp[i][1][0/1]=dp[i-1][j][1/0]$(势力不同)$+a_i/b_i$(根据 $dp[i][1][0/1]$ 的第三维下标决定。)
- 相同,即 $2≤j≤n$
这些状态的值可以由 $dp[i-1][j-1]$ 的状态得到。
因为如果势力相同,那之前($i-1$)的势力长度就是 $j-1$。
所以 $dp[i][j][0/1]=dp[i-1][j-1][0/1]+a_i/b_i$。
#include <iostream>
#include <cstring>
#define inf 1000000007
using namespace std;
int n, l, r, a[1050], b[1050], ans = inf, dp[1050][1050][2];
int main()
{
cin >> n >> l >> r;
for(int i = 1;i <= n;++i)
cin >> a[i];
for(int i = 1;i <= n;++i)
cin >> b[i];
for(int i = 1;i <= n;++i)
for(int j = 1;j <= n;++j)
dp[i][j][0] = dp[i][j][1] = inf;
dp[1][1][0] = a[1];
dp[1][1][1] = b[1];
for(int i = 2;i <= n;++i)
{
for(int j = l;j <= r;++j)
{
dp[i][1][0] = min(dp[i][1][0], dp[i - 1][j][1] + a[i]);
dp[i][1][1] = min(dp[i][1][1], dp[i - 1][j][0] + b[i]);
}
for(int j = 2;j <= r;++j)
{
dp[i][j][0] = dp[i - 1][j - 1][0] + a[i];
dp[i][j][1] = dp[i - 1][j - 1][1] + b[i];
}
}
for(int i = l;i <= r;++i)
ans = min(ans, min(dp[n][i][0], dp[n][i][1]));
if(ans > 1000000) cout << "NO ANSWER";
else cout << ans;
return 0;
}