Educational Codeforces Round 125 (Rated for Div. 2) A - E

什么时候能教育场不再被教育呢~~~

https://codeforces.com/contest/1657

A

  • ( 0 , 0 ) (0,0) (0,0)出发,想要到 ( x , y ) (x,y) (x,y),且每次移动的欧几里得距离需要是一个整数,问最少的移动次数

显然我们可以第一次移动到 ( x , 0 ) (x,0) (x,0),然后再移动到 ( x , y ) (x,y) (x,y),这样最多移动两次,然后再判断有没有可能一次或者不用移动即可,这题我居然还想了一下怎么证明两步以内能移动过去 . . . . . . ...... ......

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int t;
  cin >> t;
  while(t--){
    int x, y;
    cin >> x >> y;
    int tmp = sqrt(x * x + y * y);
    if(x == 0 && y == 0) cout << 0 << '\n';
    else if(tmp * tmp == x * x + y * y){
      cout << 1 << '\n';
    }else{
      cout << 2 << '\n';
    }
  }
  return 0;
}

B

  • 因为这两个操作必须要做一个,所以显然如果加上 x ≤ b x\leq b xb,那么我们就加上,否则就减去 y y y,然后把所有数字都加在一起即可
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int t;
  cin >> t;
  while(t--){
    int n;
    ll b, x, y;
    cin >> n >> b >> x >> y;
    ll now = 0;
    ll sum = 0;
    for(int i=0;i<n;i++){
      if(now + x <= b){
        now += x;
      }else{
        now -= y;
      }
      sum += now;
    }
    cout << sum << '\n';
  }
  return 0;
}

C

  • 给你一个字符串,每次进行一个操作,删除最短的长度大于等于2的前缀,这个前缀需要满足以下两个条件之一, 1 ◯ \text{\textcircled{1}} 1前缀是一个完整的括号,其实就是 ( ) () () 2 ◯ \text{\textcircled{2}} 2前缀是一个长度至少为2的回文串。如果不满足条件就结束,问结束的时候执行操作的次数和剩余字符的数量
  • 开头两个字符只有四种情况 ( ) , ) ( , ( ( , ) ) (),)(,((,)) (),)(,((,)),其中 ( ) , ( ( , ) ) (),((,)) (),((,))都是可以直接删除的,只有 ) ( )( )(,这个我们需要找到能让它构成回文的,显然应该是 ) ) ),所以只要找到下一个 ) ) ),这之间的部分都可以被删掉,然后继续操作即可,可以直接循环,也可以用栈
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int t;
  cin >> t;
  while(t--){
    int n;
    cin >> n;
    string s;
    cin >> s;
    int c = 0;
    int l = 0;
    int r = n;
    bool f = true;
    for(int i=1;i<n;){
      if(!f){
        while(i < n){
          if(s[i] == ')') break;
          i += 1;
        }
        if(i < n){
          r -= i - l + 1;
          c += 1;
        }
        i += 2;
        f = true;
        continue;
      }
      if(s[i - 1] == s[i] || (s[i - 1] == '(' && s[i] == ')')){
        c += 1;
        r -= 2;
        i += 2;
      }else{
        f = false;
        l = i - 1;
        i += 1;
      }
    }
    cout << c << ' ' << r << '\n';
  }
  return 0;
}

D

  • n n n种单位,且你有 C C C个硬币,每个单位有三个属性 c i , d i , h i c_i,d_i,h_i ci,di,hi分别表示第 i i i个单位的雇佣费、每次能够对敌人造成的伤害、生命值;然后有 m m m种怪物,每个怪物有两种属性, D j , H j D_j,H_j Dj,Hj分别表示每次能够对我方造成的伤害和生命值,现在对于每一个怪物,让你选择如何雇佣单位,使得花费尽量少的硬币,且能够杀死怪物,如果杀不死就输出 − 1 -1 1,否则输出需要的最少硬币数
  • 注意怪物和我方同时攻击,杀死怪物的意思是我方击杀怪物的时间严格小于怪物击杀我方的时间,怪物的血量和我方的血量都是固定的,不会因为人数的增加而发生变化,但是我方的伤害会随着人数的增加而增加,每次只能使用硬币雇佣同一种单位
  • 那么题意理解清楚以后,由于数据范围暴力无法解决,那么我们仔细分析一下,设需要雇佣第 i i i个单位的数量是 k k k,设怪物的伤害和血量分别为 D , H D,H D,H,因为每个怪物是等价的,我们只需要分析一个即可,那么如果想杀死这个怪物应该有 H k × d i < h i D \frac{H}{k\times d_i}\lt \frac{h_i}{D} k×diH<Dhi,也就是 k > H × D d i × h i k\gt \frac{H\times D}{d_i\times h_i} k>di×hiH×D,把 k k k并到 d i d_i di里面去,得到 d i × h i > H × D d_i\times h_i\gt H\times D di×hi>H×D,可以发现这里面 d i × h i d_i\times h_i di×hi是一个整体
  • 因为 C C C范围没有那么大,考虑设 a [ i ] a[i] a[i]表示花费 i i i个硬币对应的最大 d × h d\times h d×h是多少,如果求出这个数组,那么我们就在这个数组里找大于 D × H D\times H D×H的第一个数看它需要多少硬币即可,这就是最少的硬币数量
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e6 + 10;

ll a[N], b[N];
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, C;
  cin >> n >> C;
  for(int i=0;i<n;i++){
    int c;
    ll d, h;
    cin >> c >> d >> h;
    b[c] = max(b[c], d * h);
  }
  for(int i=1;i<=C;i++){
    if(b[i] > 0){
      for(int j=1;i*j<=C;j++){
        a[i * j] = max(a[i * j], b[i] * j);
      }
    }
  }
  for(int i=1;i<=C;i++){
    a[i] = max(a[i - 1], a[i]);
  }
  int m;
  cin >> m;
  for(int i=0;i<m;i++){
    ll d, h;
    cin >> d >> h;
    int p = upper_bound(a + 1, a + C + 1, d * h) - a;
    if(p <= C) cout << p << " \n"[i == m - 1];
    else cout << -1 << " \n"[i == m - 1];
  }
  return 0;
}

E

  • 有一个有 n n n个节点的完全无向图,且每条边的边权范围是 [ 1 , k ] [1,k] [1,k],如果和 1 1 1号节点相邻的所有边的边权和等于这个完全无向图的最小生成树的边权和,那么就说这个图是美丽的,现在问有 n n n个节点,每条边的边权范围是 [ 1 , k ] [1,k] [1,k]的美丽图的数量
  • d p [ i ] [ j ] dp[i][j] dp[i][j]表示和1相连的点的边权 ≤ i \leq i i,且这些点一共有 j j j个(包括1)的方案数,那么也就是说还有 n − j n-j nj个点没有连边,接下来我们考虑如何连边,以及权值如何分配,首先从 n − j n-j nj个元素中选择 k k k个需要一个组合数,这样就选择好点了,然后我们考虑边权,因为和1相连的点已经确定,那么只能在这 k k k个点之间连边或者在 j − 1 j-1 j1个点和 k k k个点之间连边,设 w w w为题目给定的最大边权,则边权范围为 [ i + 1 , w ] [i+1,w] [i+1,w],因为要保证最小生成树只能在1附近,其他点周围就不能有 [ 1 , i ] [1,i] [1,i]之间的数,这中间一共有 w − i w-i wi个数,,那么状态转移方程可以写成下面的形式
    d p [ i + 1 ] [ j + k ] = ∑ j = 1 j + k ≤ n d p [ i ] [ j ] × ( n − j k ) × ( w − i ) k × ( k − 1 ) 2 + ( j − 1 ) × k dp[i+1][j+k]=\sum_{j=1}^{j+k\leq n}{dp[i][j]\times \dbinom{n-j}{k} \times(w-i)^{\frac{k\times(k-1)}{2}+(j-1)\times k}} dp[i+1][j+k]=j=1j+kndp[i][j]×(knj)×(wi)2k×(k1)+(j1)×k
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 255;

ll C[N + 5][N + 5];
ll dp[N + 5][N + 5];// dp[i][j]表示边权<=i, 和1直接相连的点构成的连通块大小为j
const ll MOD = 998244353;
ll fastpow(ll base, ll power){
  ll ans = 1;
  while(power > 0){
    if(power & 1) ans = ans * base % MOD;
    base = base * base % MOD;
    power >>= 1;
  }
  return ans;
}
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, w;
  cin >> n >> w;
  C[0][0] = 1ll;
  for(int i=1;i<=N;i++){
    C[i][0] = 1ll;
    for(int j=1;j<=i;j++){
      C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
      C[i][j] %= MOD;
    }
  }
  dp[0][1] = 1ll;
  for(int i=0;i<=w;i++){// 边权<=i  
    for(int k=0;k<=n;k++){// 选择额外的k个节点
      for(int j=1;j+k<=n;j++){// 和1直接相连的点构成连通块大小为j
        dp[i + 1][j + k] += dp[i][j] * C[n - j][k] % MOD * fastpow(w - i, k * (k - 1) / 2 + (j - 1) * k) % MOD;
        dp[i + 1][j + k] %= MOD;
      }
    }
  }
  cout << dp[w][n];
  return 0;
}

F

2 − s a t 2-sat 2sat,这个建图还没太搞懂,研究一下再写

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值