4.AtCoder abc288D - Range Add Query
题意:
给定n个数和k,若干次查询l,r范围内
是否可以对连续 k 个元素添加任意值 c,使子数列中的所有元素都为零,执行 0 次或多次。
思路:
因为是连续的区间加,假设sum[i]表示下标对 k取模为i的所有数的和。那每次操作就是将 sum的所有数都 + x
那最终为 0的充分条件就是 sum的所有数都是一样的。反过来,也是必要条件。
因此对于每组询问,统计该序列的下标对k取模的所有数的和,看看是否为同一个数即可。
预处理原数组的下标取模前缀和,每组询问就两个前缀和相减就得到该区间的下标取模前缀和
(答案好像没有问题,可惜时间超限,只能留到以后再解决)
#include <iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
ll a[maxn], tmp[maxn];
int main() {
ios::sync_with_stdio(false);
int n, k;
cin >> n >> k;
for (int i = 0; i < n; ++i) {
cin >> a[i];
if (i >= k) //前缀和变式
a[i] += a[i - k];
}
int q;
cin >> q;
while (q--) {
int l, r;
cin >> l >> r;
l--, r--;
memset(tmp, 0, sizeof(tmp));
int pos = r;
bool flag=0;
for (int i = 0; i < k; ++i) {
tmp[pos % k] = a[pos];
pos--;
}
pos = l - 1;
for (int i = 0; i < k && pos >= 0; ++i) {
tmp[pos % k] -= a[pos];
pos--;
}
for (int i = 1; i < k; i++){
if (tmp[i] != tmp[i-1])
flag = 1;
}
if (!flag)
cout << "YES" << '\n';
else
cout << "NO" << '\n';
}
return 0;
}
6.AtCoder abc288E - Wish List
题意:
给定n个商品,价格ai,同时给定一个长度为n的数组c
购买规则:购买序号为i的商品,其标号是未买商品的第j,其购入价格为 ai + cj
需要要买m个物品,分别是 xi
可以买不需要的物品。求购买所需物品的最小花费。
思路:
如果要买当前这个物品i,则必须要支付ai,c值是不确定的,所以我们要使c值最小,假设当前在1到i-1之间的物品
中购买了j-1个物品,因为第i个物品对于前面的物品都没有影响,所以可以在任何时候购买第i个物品,
那么要多支付的价钱为i-j+1到i的c的min
#include<iostream>
using namespace std;
typedef long long ll;
const ll inf= 1e18;
const int maxn = 5e3 + 10;
int n, m, x, vt = 0;
int a[maxn], c[maxn], must[maxn], t[maxn][maxn];
long long ans = inf, dp[maxn][maxn];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> c[i];
}
for (int i = 1; i <= m; i++) { //must标记要购买的物品
cin>>x;
must[x] = 1;
}
for (int i = 1; i <= n; i++) {
t[i][i] = c[i];
for (int j = i + 1; j <= n; j++) {
t[i][j] = min(t[i][j - 1], c[j]);
}
dp[0][i] = inf; //设为无穷大
}
for (int i = 1; i <= n; i++) {//dp[i][j]表示枚举到第i个物品,一共买了j个物品的最小花费
for (int j = 1; j <= n; j++) {
if (must[i]) // 如果当前的物品为需要的
dp[i][j] = dp[i - 1][j - 1] + a[i] + t[i - j + 1][i];
else // 如果当前的物品为不需要的
dp[i][j] = min(dp[i - 1][j], dp[i - 1][j - 1] + a[i] + t[i - j + 1][i]);
}
if (must[i]) vt = 1;
if (vt) dp[i][0] = inf;
}
for (int i = m; i <= n; i++) {
ans = min(ans, dp[n][i]);
}
cout << ans << '\n';
return 0;
}
7.CodeForces 1786C.Monsters(easy version)
题意:
给定n个怪物,血量为ai,可以若干次施展操作1:对任何一个活着的怪物造成1点伤害。
一次施展操作2:对所有活着的怪物造成1点伤害。如果至少有一只怪物因这一行动而死亡,那么就重复这一行动
求杀死所有怪物,操作1施展的最小次数
思路:
通过操作1使每个怪物血量差1
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e6 + 10;
int t,n;
ll ans;
ll a[maxn];
int main()
{
ios::sync_with_stdio(false);
cin >> t;
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a +1+ n);
ans = 0;
for (int i = 1; i <= n; i++){
if (a[i] - a[i - 1] > 1){
ans += (a[i] - a[i - 1] - 1);
a[i] = a[i - 1] + 1;
}
}
cout << ans << endl;
}
return 0;
}
CodeForces 1786E E.Monsters(hard version)
题意:
ard版本就是求数组的每一个前缀使用第一种攻击的最少次数是多少
(easy版就是求整个数组使用的第一种攻击最少是多少)。
这道题想了很久,但代码还是不能AC,于是到网上借鉴一下,发现需要用到线段树,
只能明天学一下线段树,再来看一下这道题
#include <iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int a[N];
struct Node {
int l, r, maxv, add;
}tr[N * 4];
void build(int u, int l, int r) {
if (l == r) {
tr[u] = { l, r, -l, 0 };
}
else {
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
tr[u] = { l, r, max(tr[u << 1].maxv, tr[u << 1 | 1].maxv), 0 };
}
}
void pushdown(int u) {
if (tr[u].add) {
tr[u << 1].add += tr[u].add;
tr[u << 1].maxv += tr[u].add;
tr[u << 1 | 1].add += tr[u].add;
tr[u << 1 | 1].maxv += tr[u].add;
tr[u].add = 0;
}
}
void modify(int u, int l, int r, int c) {
if (tr[u].l >= l && tr[u].r <= r) {
tr[u].maxv += c;
tr[u].add += c;
}
else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, c);
if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
tr[u].maxv = max(tr[u << 1].maxv, tr[u << 1 | 1].maxv);
}
}
int query(int u) {
if (tr[u].l == tr[u].r) return tr[u].l;
pushdown(u);
if (tr[u << 1].maxv > 0) return query(u << 1); // 左边存在大于0的值,那么取左边找
return query(u << 1 | 1); // 大于0的值在右边
}
void solve() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
build(1, 1, n);
int cnt = 0; // 当前维护的数的个数
LL s = 0; // 当前维护的数的总和
for (int i = 1; i <= n; i++) {
cnt++, s += a[i]; // 先记录
modify(1, a[i], n, 1); // a[i]的个数加1,si ~ sn都加1,si-i ~ sn-n都加1
if (tr[1].maxv > 0) { // 存在si-i大于0的数,需要删除最小的那个数
int x = query(1); // 找到最小的那个数
modify(1, x, n, -1); // 删除这个数,区间减1
cnt--, s -= x; // 删除最小的那个数
}
printf("%lld ", s - cnt * (cnt + 1ll) / 2); // 公式计算答案
}
printf("\n");
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}