Educational Codeforces Round 37 所有题目整理!

A.Water The Garden

int ct = a / p;
if (a % p != 0) ++ct;
等价于
ct  = (a + p - 1) / p;

B. Tea Queue

solution:按照题意模拟即可

C. Swap Adjacent Elements

题意:给定1-n的一个排列,存在一些i,其中1<=i < n,a[i]可以和a[i+1]交换, 询问是否可以通过一系列交换操作,使得序列升序。

思路:首先考虑数字1,假设其位置为pos1,必然需要从pos1位置通过交换移到1位置,需要a[1]、a[2]….a[pos-1]均可交换;然后考虑数字2,假设初始位置pos2,可能由于之前数字1的移动,导致数字2在pos2的基础上右移,但是由于交换是可逆的,必然存在一种方式,可逆的从现位置移动到pos2位置,因此我们只需要考虑是否可以从pos2位置通过交换移动到2位置,需要a[2]…a[pos2-1]均可交换。
对于任意的数字k,只需要保证a[k]…..a[posk-1]均可交换即可

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f3f3f3f3f
#define N 250037
#define pb push_back
#define mp make_pair
#define ff first
#define ss second
int n, m;
int a[N], sum[N];
char s[N];
bool cal(int l, int r)  {
    return sum[r] - sum[l - 1] == r - l + 1;
}
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", a + i);
    scanf("%s", s + 1);
    for(int i = 1; i < n; i++)  {
        sum[i] = sum[i - 1];
        if (s[i] == '1') sum[i]++;
    }
    bool res = true;
    for(int i = 1; i <= n; i++) {
        if (a[i] == i) continue;
        if (a[i] < i && !cal(a[i], i - 1)) res = false;
        if (a[i] > i && !cal(i, a[i] - 1)) res = false;
    }
    if (res) puts("YES");  else puts("NO");
    return 0;
}

D. Tanks

Solution
由于勺子每次可以改变k的水量,因此我们首先考虑是否可以构造出m的水量,其中v≡m(mod k),我们可以通过朴素的01背包预处理出来O(nk),然后通过f数组倒推出对应的方案。
假设此方案需要t个tank,将这t个tank的水分都集中到一个tank上,其余n-t的tank水分集中到一个tank上,假设这两个坦克编号分别为now1和now2,其中a[now1]%k必定为m,a[now1]的水量与目标v只差若干个k。
如果a[now1]的水量多,直接将多的水量移到now2上就可以;如果他少的话,将a[now2]的水量移到a[now1]上,尽量移,不够移的则无解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5010;
int n, k, V;
bool f[N][N];
int a[N], b[N];
int Cal(int x, int y)  {
    int ct = a[x] / k;
    if (a[x] % k != 0) ++ct;
    a[y] += a[x]; a[x] = 0;
    return ct;
}
struct node {
    int cnt, x, y;
} ans[N];
int cur = 0;
int main(){
    scanf("%d%d%d", &n, &k, &V);
    int sum = 0;
    for(int i = 1; i <= n; i++)  scanf("%d", a + i), b[i] = a[i] % k, sum += a[i];
    if (sum < V) {  puts("NO");  return 0;  }
    f[0][0] = true;
    for(int i = 0; i < n; i++) for(int j = 0; j < k; j++)  if (f[i][j]) f[i + 1][j] = true, f[i + 1][(j + b[i + 1]) % k] = true;
    if (!f[n][V % k]) {  puts("NO");  return 0;  }
    vector<int> s;
    int res = V % k;
    for(int i = n; i >= 1; i--)  {
        if (f[i-1][res]) continue;
        s.push_back(i);
        res -= b[i];
        if (res < 0) res %= k, res += k;
    }
    int top = s.size(), now1, now2 = -1;
    if (top == 0) {
        ans[++cur] = (node)  { Cal(1, 2), 1, 2};
        now1 = 1;
    } else now1 = s[0];
    for(int i = 1; i < top; i++)  ans[++cur] = (node){Cal(s[i], now1), s[i], now1};
    for(int i = 1; i <= n; i++)  if (i != now1 ) { now2 = i;  break;  }
    for(int i = 1; i <= n; i++)  if (i != now1 && i != now2 && a[i]) ans[++cur] = (node){Cal(i, now2), i, now2};
    if (a[now1] < V)  {
       //printf("test %d  %d %d  %d\n", now1, now2, a[now1], a[now2]);
        int res = (V - a[now1]) / k;
        int tot = min(a[now2] / k, res);
        if (tot < res) {  puts("NO");  return 0;  }
        ans[++cur] = (node){tot, now2, now1};
        a[now1] += tot * k;
        a[now2] -= tot * k;
    }
    else if (a[now1] > V)  {
       // printf("test %d  %d %d  %d\n", now1, now2, a[now1], a[now2]);
        int res = (a[now1] - V) / k;
        ans[++cur] = (node){res, now1, now2};
    }
    puts("YES");
    for(int i = 1; i <= cur; i++)  if (ans[i].cnt) printf("%d %d %d\n", ans[i].cnt, ans[i].x, ans[i].y);
    return 0;
}

E Connected Components?

题意:给定无向图,求其补图的联通块数目及每个联通块的点数量
思路:很nb的做法…..设原图边集为E,点集为V
初始我们任意拿出一个点x,遍历点集V,对于点y,若(x, y) 不属于E,则(x, y)存在于补图中,因此我们将y加入到此联通块中,及压入队列中,将一系列的y及x从V中删除;然后继续拿队列的其他节点遍历V,直至队列为空。此时相当于找到了一个联通块,队列中出现过的元素个数即为联通块点数量。
为了降低复杂度,我们用链表来维护点集,复杂度O(V+E)
首先每个点最多进队列一次,队列的每个元素遍历点集V可能会造成较高的复杂度,但是我们从另一个方向上去考虑,点集V的每个元素x最多会被枚举多少次? x被枚举多次是因为枚举完以后没有删除导致的,那什么时候枚举完x后,不会从链表中删除x呢? 当队列元素与x在原图联边时..x
不会被删除,因此x最大枚举次数就是原图中x的连边数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 350010;
int n, m;
vector<int> adj[N];
struct node {
    int pre, nxt;
} l[N];
bool vis[N], f[N];
vector<int> ans;
void del(int x)  {
    l[l[x].pre].nxt = l[x].nxt;
    l[l[x].nxt].pre = l[x].pre;
}
void bfs(int st)  {
    queue<int> q;
    q.push(st);
    f[st] = true;
    int cnt = 0;
    while(!q.empty())  {
        int x = q.front();
        cnt++;
        q.pop();
        for(auto v : adj[x]) vis[v] = true;
        for(int i = l[0].nxt; i != 0; i = l[i].nxt) if (!vis[i] && !f[i])  q.push(i), del(i), f[i] = true;
        for(auto v : adj[x]) vis[v] = false;
    }
    ans.push_back(cnt);
}
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        adj[x].push_back(y);
        adj[y].push_back(x);
    }
    for(int i = 1; i <= n; i++) l[i].pre = i - 1, l[i - 1].nxt = i;
    l[n].nxt = 0;
    for(int i = 1; i <= n; i++)  if (!f[i])  bfs(i);
    sort(ans.begin(), ans.end());
    printf("%d\n", ans.size());
    printf("%d", ans[0]);
    for(int i = 1; i < ans.size(); i++)  printf(" %d", ans[i]);
    return 0;
}

F. SUM and REPLACE

思路:线段树的套路题,当一种操作在多次重复后不对序列产生影响时,用这种暴力打标记的方法就好。举个例子,对一个数字开根号,常数次以后就会变成数字一,那么之后的开根号操作就不会产生影响。
这道题目我们可以对于线段树的每个节点i,打一个标记vis[i],代表这个节点对应的区间[l, r],replace 操作是否还会产生影响;对于每个replace 操作, 如果当前区间vis值为1,直接return掉就好,否则分层replace,当分到叶子层的时候,判断一下vis值,上调整的时候update vis值即可

#include <bits/stdc++.h>
using namespace std;
#define lson x << 1, l, mid
#define rson x << 1 | 1, mid + 1, r
#define ls x << 1
#define rs x << 1 | 1
typedef long long ll;
const int N = 350010;
struct node {
    ll sum;
    bool delta;
} tree[N << 2];
int a[N], n, m;
int d[1000007];
int ql, qr;
void pushUp(int x)  {
    tree[x].sum = tree[ls].sum + tree[rs].sum;
    tree[x].delta = tree[ls].delta & tree[rs].delta;
}
void build(int x, int l, int r)  {
    if (l == r)  {
        tree[x].sum = a[l];
        tree[x].delta = (d[a[l]] == a[l]);
        return;
    }
    int mid = (l + r) >> 1;
    build(lson), build(rson);
    pushUp(x);
}
void update(int x, int l, int r)  {
    node& e = tree[x];
    if (ql <= l && qr >= r)  {
        if (e.delta)  return;
        if (l == r) {
            e.sum = d[e.sum];
            e.delta = (d[e.sum] == e.sum);
            return;
        }
    }
    int mid = (l + r) >> 1;
    if (ql <= mid) update(lson);
    if (qr > mid)  update(rson);
    pushUp(x);
}
ll query(int x, int l, int r)  {
    if (ql <= l && qr >= r)  return tree[x].sum;
    ll ans = 0;
    int mid = (l + r) >> 1;
    if (ql <= mid) ans += query(lson);
    if (qr > mid) ans += query(rson);
    return ans;
}
int main(){
    for(int i = 1; i <= 1000000; i++)  for(int j = i; j <= 1000000; j += i) d[j]++;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", a + i);
    build(1, 1, n);
    while(m--)  {
        int t;
        scanf("%d%d%d", &t, &ql, &qr);
        if (t == 1)  update(1, 1, n);
        else cout << query(1, 1, n) << endl;
    }
    return 0;
}

G. List Of Integers

题目:考虑满足y>x,gcd(p, y) = 1条件下的第k小的y
思路:通过gcd(p, y)=1 可以读到满满的容斥气息(可能因为我比较辣鸡…没有读到),我们二分答案y,然后利用容斥算出x,mid之间满足gcd(p, y)=1的数量,与k比较一下可以判断得到答案是大了还是小了;
注意一点,我们找的是num>=k对应的最小的y

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 350010;
int n, m;
ll cal(ll mid, vector<ll> f)  {
    ll res = 0;
    int n = f.size();
    for(int i = 0; i < (1 << n); i++)  {
        ll tot = 1, k = 1;
        for(int j = 0; j < n; j++) if (i >> j & 1) tot *= f[j], k *= -1;
        res += k * (mid / tot);
    }
  // printf("%lld->%lld   %d\n", mid, res, n);
    return res;
}
int main(){
    int T;
    scanf("%d", &T);
    while(T--)  {
        ll x, p, k;
        scanf("%lld%lld%lld", &x, &p, &k);
        vector<ll> f;
        int tot = sqrt(p);
        for(int i = 2; i <= tot; i++) if (p % i == 0){
            f.push_back(i);
            while(p % i == 0) p /= i;
        }
        if (p > 1) f.push_back(p);
        ll sub = cal(x, f);
        ll l = x + 1, r = 1e12;
        ll ans = -1;
        while(l <= r)  {
            ll mid = (l + r) >> 1;
            if (cal(mid, f) - sub >= k) {
                ans = mid;
                r = mid - 1;
            }
            else l = mid + 1;
        }
        printf("%lld\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值