CSP 2020-S 题解

写在前面

挂了。
noip 还是要打的。
先来补题了,补游记看心情。

A

\[\Huge \mathbb{RIP} \]

\[\texttt{the problem setter} \]

\[(1582/10/5 \sim 1582/10/14) \]

正解

参考:这位神仙的提交记录

\(Q\le 10^5\),允许在每次询问进行一个 \(O(1000)\) 的暴力。
暴力可知 \(1582.10.5\) 的儒略历是 \(2299161\),以该日期为分界线处理。

对于 \(r<2299161\),依次计算剩下的天数中有几个 \(4\) 年、\(1\) 年、\(1\) 月,再加上天数。
对于 \(r\ge 2299161\),先将日期转化为 \(1583.1.1\),再依次计算剩下的天数中有几个 \(400\) 年、\(4\) 年、\(1\) 年、\(1\) 月,再加上天数。

复杂度上界约为 \(O(4\times 10^7)\)

//知识点:模拟 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kInf = 1e9 + 10;
const LL D[15] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
//=============================================================
LL r, d, m, y;
//=============================================================
inline LL read() {
  LL f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
bool Judge(LL y_) {
  if (y_ <= 1582ll) return 1ll * (y_ % 4ll == 0);
  return 1ll * (y_ % 400ll == 0) || (y_ % 4ll == 0 && y_ % 100ll != 0ll);
}
void Solve1() {
  d = 1, m = 1, y = -4712;
  LL cnt = (r / (3ll * 365ll + 366ll));
  y += 4ll * cnt, r -= cnt * (3ll * 365ll + 366ll);
  for (int i = 1; i <= 3; ++ i) {
    LL delta = 365ll + Judge(y);
    if (delta > r) break;
    ++ y;
    r -= delta;
  }
  for (int i = 1; i <= 12; ++ i) {
    LL delta = D[i] + (i == 2 && Judge(y));
    if (delta > r) break;
    ++ m;
    r -= delta;
  }
  d += r;
}
LL Solve2() {
  r -= 2299161ll;
  d = 15, m = 10, y = 1582;
  if (r <= 16ll) return d += r;
  d = 1, m ++, r -= 17ll;
  if (r < 30ll) return d += r;  
  m ++, r -= 30ll;
  if (r < 31ll) return d += r;
  m = 1, y ++, r -= 31ll;
  
  LL cnt = r / (1ll * (97 * 366 + 303 * 365));
  y += 400ll * cnt, r -= cnt * 1ll * (97 * 366 + 303 * 365);
  for (int i = 1; i <= 400; ++ i) {
    LL delta = 365ll + 1ll * Judge(y);
    if (delta > r) break;
    ++ y;
    r -= delta;
  }
  for (int i = 1; i <= 12; ++ i) {
    LL delta = D[i] + 1ll * (i == 2 && Judge(y));
    if (delta > r) break;
    ++ m;
    r -= delta;
  }
  return d += r;
}
//=============================================================
int main() {
  int T = read();
  while (T --) {
    r = read();
    if (r < 2299161ll) {
      Solve1();
    } else {
      LL koishi = Solve2();  
    }
    
    if (y <= 0) {
      printf("%lld %lld %lld BC\n", d, m, -y + 1);
    } else {
      printf("%lld %lld %lld\n", d, m, y);
    }
  }
  return 0;
}

暴力

//知识点:模拟 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <cctype>
#include <cstring>
#define LL long long
const int N = 100000 + 10;
const LL D[15] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
//==================================================
LL d = 1, m = 1, y = -4713;
//==================================================
LL read() {
  LL f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = 10 * w + ch - '0';
  return f * w;
}
void SolveBC() {
  ++ d;
  if (d > D[m]) {
    if (m == 2 && 
        d == 29 && 
        (- y - 1ll) % 4 == 0) {
      int koishi;
    } else {
      ++ m;
      d = 1;  
    }
  }
  if (m > 12) {
    ++ y;
    m = 1;
  }
  if (y == 0) y = 1;
}
void Solve1() {
  ++ d;
  if (d > D[m]) {
    if (m == 2 && 
        d == 29 && 
        y % 4 == 0) {
      int koishi;
    } else {
      ++ m;
      d = 1;  
    }
  }
  if (m > 12) {
    ++ y;
    m = 1;
  }
  
  if (5 == d && m == 10 && y == 1582) {
    d = 15;
  }
}

void Solve2() {
  bool yes = false;
  if (y % 400 == 0) yes = true;
  if (y % 4 == 0 && y % 100 != 0) yes = true;
  ++ d;
  if (d > D[m]) {
    if (m == 2 && d == 29 && yes) {
      int koishi;
    } else {
      ++ m;
      d = 1;  
    }
  }
  if (m > 12) {
    ++ y;
    m = 1;
  }
}
//==================================================
int main() {
//  freopen("julian.in", "r", stdin);
//  freopen("julian.out", "w", stdout);
  int T;
  scanf("%d", &T);
  while (T --) {
    LL r = read();
    {
      d = 1, m = 1, y = -4713;
      while (r --) {
        if (y < 0) SolveBC();
        else if (y <= 1582) Solve1();
        else Solve2();
      }
      if (y < 0) {
        printf("%lld %lld %lld BC\n", d, m, -y);
      } else {
        printf("%lld %lld %lld\n", d, m, y);
      }
      continue ;
    }
  }
  return 0;
}
/*
1721424
2299161
*/

B

正解

给定的要求,实际上是对动物编号二进制中某一位 01 情况的限制。
若某条件满足,则 存在 一种动物的二进制该位为 1。
一种原来不存在的动物 不能 加入动物园的条件是该动物满足了 原来不满足 的一条要求,即其某二进制位上为 1。

可以通过位运算简单得到多少位上可以为 1,设有 \(k'\) 位,答案即为 \(2^{k'} - n\)

注意特判答案为 \(2^{64}\) 的情况。\(2^{64}-1\) 可以通过 ull 溢出得到。

时间复杂度 \(O(n + m)\)

//知识点:瞎搞
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <iostream>
#define ULL unsigned long long
const int N = 1e6 + 10;
//==================================================
int n, m, c, k, num, noneed[N];
ULL havea, havec, ans;
//==================================================
int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = 10 * w + ch - '0';
  return f * w;
}
//==================================================
int main() {
  n = read(), m = read(), c = read(), k = read();
  for (int i = 1; i <= n; ++ i) {
    ULL a;
    std::cin >> a;
    havea |= a;
  }
  for (int i = 1; i <= m; ++ i) {
    int p = read(), q = read();
    if (! ((1ull << (1ull * p)) & havea)) { //所有q 不相同 
      num += ! noneed[p];
      noneed[p] = true; //第 p 位上为 1 是不合法的,会加入新饲料 
    }
  }
  if ((k - num) == 64) {
    if (! n) {
      printf("18446744073709551616\n");
      return 0;
    } else {
      ans = 0 - n; 
    }
  } else {
    ans = (1ull << (1ull * k -num)) - 1ull * n; 
  }
  std::cout << ans;
  return 0;
}

暴力

//
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <cctype>
#include <cstring>
#define LL long long
#define ULL unsigned long long
const int N = 2e6 + 10;
//==================================================
int n, m, c, k, ans, a[N];
int p[N], q[N];
bool visa[N], visc[N];
//==================================================
int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = 10 * w + ch - '0';
  return f * w;
}
//==================================================
int main() {
  n = read(), m = read(), c = read(), k = read();
  for (int i = 1; i <= n; ++ i) {
    a[i] = read();
    visa[a[i]] = true;
  }
  for (int i = 1; i <= m; ++ i) {
    p[i] = read(), q[i] = read();
    for (int j = 1; j <= n; ++ j) {
      if ((a[j] >> p[i]) & 1) {
        visc[q[i]] = true;
        break;
      }
    }
  }
  
  for (int i = 0, lim = (1 << k) - 1; i <= lim; ++ i) {
    if (visa[i]) continue ;
    int flag = 1;
    for (int j = 1; j <= m; ++ j) {
      if ((i >> p[j]) & (! visc[q[j]])) {
        flag = 0;
        break;
      }
    }
    ans += flag;
  }
  printf("%d\n", ans);
  return 0;
}

C

正解

参考:又一位神仙的提交记录
参考代码中没有判 \(a_i=0\) 的情况,牛客数据比较水卡过了(

先根据拓扑序记忆化搜索,求得每个操作 3 的乘法增量 mul,即会令各元素变为之前的多少倍。
完成上述过程后,可顺便得到乘法操作对原数的影响。

再考虑加法操作,再在拓扑序上 DP,求得每种操作被调用多少次,注意倒序进行,通过调用者更新被调用者。
某加法操作的贡献为调用次数 + 影响到它乘法操作的次数。
两者贡献量相同,在代码中用 cnt 记录了两者的和。

最后还原原数即可,时间复杂度 \(O(n+m)\)

//知识点:拓扑排序,DP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#define LL long long
const LL mod = 998244353;
const int N = 1e5 + 10;
//=============================================================
int n, m, q_num, t[N], pos[N], c[N], into[N], q[N];
LL a[N], val[N], cnt[N];
LL mul[N];
std::vector <int> v[N];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) 
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  } 
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Prepare() {
  n = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  m = read();
  for (int i = 1; i <= m; ++ i) {
    t[i] = read();
    if (t[i] == 1) {
      pos[i] = read(), val[i] = read();
    } else if (t[i] == 2) {
      val[i] = read();
    } else if (t[i] == 3) {
      c[i] = read();
      for (int j = 1; j <= c[i]; ++ j) { //
        int v_ = read();
        v[i].push_back(v_);
        into[v_] ++;
      }
    }
  }
}
LL Dfs(int u_) {
  if (mul[u_] != -1) return mul[u_];
  if (t[u_] != 3) {
    mul[u_] = (t[u_] == 1) ? 1 : val[u_];
    return mul[u_];
  }
  mul[u_] = 1;
  for (int i = c[u_] - 1; i >= 0; -- i) {
    int v_ = v[u_][i];
    mul[u_] = mul[u_] * Dfs(v_) % mod;
  }
  return mul[u_];
}
void Topsort() {
  std::queue <int> q;
  for (int i = 1; i <= m; ++ i) {
    if (! into[i]) {
      q.push(i);
    }
  }
  while (! q.empty()) {
    int u_ = q.front();
    q.pop();
    LL prod = 1;
    for (int i = c[u_] - 1; i >= 0; -- i) {
      int v_ = v[u_][i];
      cnt[v_] = (cnt[v_] + prod * cnt[u_] % mod) % mod;
      into[v_] --;
      if (! into[v_]) {
        q.push(v_);
      }
      prod = prod * mul[v_] % mod;
    }
  }
  for (int i = 1; i <= m; ++ i) {
    if (t[i] == 1) {
      a[pos[i]] = (a[pos[i]] + val[i] * cnt[i] % mod) % mod;
    }
  }
}
//=============================================================
int main() {
  Prepare();
  memset(mul, -1, sizeof (mul));
  for (int i = 1; i <= m; ++ i) {
    if (! into[i]) {
      mul[i] = Dfs(i);
    }
  }
  
  q_num = read();
  for (int i = 1; i <= q_num; ++ i) q[i] = read();
  LL prod = 1;
  for (int i = q_num; i; i --) {
    cnt[q[i]] = (prod + cnt[q[i]]) % mod;
    prod = prod * mul[q[i]] % mod;
  }
  for (int i = 1; i <= n; ++ i) {
    a[i] = a[i] * prod % mod;
  }
  Topsort();
  for (int i = 1; i <= n; ++ i) printf("%lld ", a[i]);
  return 0;
}

暴力

通过递归实现,分治了 seg 的情况。

//
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <vector>
#define LL long long
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const LL mod = 998244353;
//==================================================
int n, m, a[N], t[N];
int pos[N], val[N]; //不依赖 
int alltim, prod, tim[N];
//==================================================
int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = 10 * w + ch - '0';
  return f * w;
}
namespace Sub1 {
  const int SN = 1010;
  std::vector <int> g[SN]; //依赖 
  
  void Modify(int now_) {
    int lim = g[now_].size();
    for (int i = 0; i < lim; ++ i) {
      int opt = g[now_][i];
      if (t[opt] == 3) Modify(opt);
      if (t[opt] == 1) {
        a[pos[opt]] = 1ll * (1ll * a[pos[opt]] + 1ll * val[opt]) % mod;
      }
      if (t[opt] == 2) {
        for (int j = 1; j <= n; ++ j) {
          a[j] = 1ll * a[j] * val[opt] % mod;
        }
      }
    }
  }
  void Solve() {
    for (int i = 1; i <= m; ++ i) {
      t[i] = read();
      if (t[i] == 1) {
        pos[i] = read(), val[i] = read();
      } else if (t[i] == 2) {
        val[i] = read();
      } else {
        int c = read();
        for (int j = 1; j <= c; ++ j) {
          int nowg = read();
          g[i].push_back(nowg);
        }
      }
    }
    
    int q = read();
    for (int i = 1; i <= q; ++ i) {
      int opt = read();
      if (t[opt] == 3) Modify(opt);
      if (t[opt] == 1) {
        a[pos[opt]] = 1ll * (1ll * a[pos[opt]] + 1ll * val[opt]) % mod;
      }
      if (t[opt] == 2) {
        for (int j = 1; j <= n; ++ j) {
          a[j] = 1ll * a[j] * val[opt] % mod;
        }
      }
    }
    for (int i = 1; i <= n; ++ i) {
      printf("%d ", a[i]);
    }
  }
}

namespace Seg {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  int tag[N << 2], sum[N << 2];
  void Build(int now_, int L_, int R_) {
    tag[now_] = 1;
    if (L_ == R_) {
      sum[now_] = a[L_];
      return ;
    }
    Build(ls, L_, mid);
    Build(rs, mid + 1, R_);
  }
  void Pushdown(int now_) {
    if (tag[now_] == 1) return ;
    sum[ls] = 1ll * sum[ls] * tag[now_] % mod;
    sum[rs] = 1ll * sum[rs] * tag[now_] % mod;
    tag[ls] = 1ll * (1ll * tag[ls] * tag[now_]) % mod;
    tag[rs] = 1ll * (1ll * tag[rs] * tag[now_]) % mod;
    tag[now_] = 1;
  }
  void Modify1(int now_, int L_, int R_, int pos_, int val_) {
    if (L_ == R_) {
      sum[now_] = 1ll * (1ll * sum[now_] + 1ll * val_) % mod;
      return ;
    }
    Pushdown(now_);
    if (pos_ <= mid) Modify1(ls, L_, mid, pos_, val_);
    else Modify1(rs, mid + 1, R_, pos_, val_);
  }
  void Modify2(int val_) {
    Pushdown(1);
    tag[1] = (tag[1] * val_) % mod;
  }
  void Query(int now_, int L_, int R_) {
    if (L_ == R_) {
      printf("%d ", sum[now_]);
      return ; 
    }
    Pushdown(now_);
    Query(ls, L_, mid);
    Query(rs, mid + 1, R_);
  }
  #undef ls
  #undef rs
  #undef mid
}
//==================================================
int main() {
//  freopen("call.in", "r", stdin);
//  freopen("call.out", "w", stdout);
  n = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  m = read();
  
  if (n <= 1000 && m <= 1000) {
    Sub1::Solve();
    return 0;
  }
  
  for (int i = 1; i <= m; ++ i) {
    t[i] = read();
    if (t[i] == 1) {
      pos[i] = read(), val[i] = read();
    } else if (t[i] == 2) {
      val[i] = read();
    } else {
      int c = read();
    }
  }
  
  Seg::Build(1, 1, n);
  int q = read();
  while (q --) {
    int opt = read();
    if (t[opt] == 3) continue ;
    if (t[opt] == 1) {
      Seg::Modify1(1, 1, n, pos[opt], val[opt]);
    }
    if (t[opt] == 2) {
      Seg::Modify2(val[opt]);
    }
  }
  Seg::Query(1, 1, n);
  return 0;
}
/*
5
1 2 3 4 5 
2
1 1 1
2 2
3
1 2 1
*/

D

20pts

手玩几组小样例可得结论。

第三条蛇若可以连吃则吃,答案为 1。
否则吃掉第一条后就会被第二条吃掉,不吃更好,答案为 3。

//
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <cctype>
#include <cstring>
#define LL long long
const int N = 1e5 + 10;
//==================================================
struct Sna {
  int pos, a;
} ori[N], a[N];
int n;
//==================================================
int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = 10 * w + ch - '0';
  return f * w;
}
//==================================================
int main() {
  int T = read();
  for (int nowT = 1; nowT <= T; ++ nowT) {
    if (nowT == 1) {
      n = read();
      for (int i = 1; i <= n; ++ i) {
        ori[i] = a[i] = (Sna) {i, read()};
      }  
    } else {
      int k = read();
      for (int i = 1; i <= k; ++ i) {
        int p = read();
        ori[p].a = read();
      }
      for (int i = 1; i <= n; ++ i) {
        a[i] = ori[i];
      }
    }
    
    int ans = n;
    if (n == 3) {
      if (a[3].a - a[1].a >= a[2].a) {
        ans = 1;
      } else {
        ans = 3;
      }
      printf("%d\n", ans);
      continue ;
    }
  }
  return 0;
}

70pts

在每一个回合中,若发生吞噬,则只会是最牛逼的蛇吃掉最傻逼的蛇。则操作的序列是固定的,考虑构造出这个序列,并根据这个序列求得第一个操作不进行的回合,即得答案。

考虑如何构建操作序列。
\(n=3\) 时,按照上述 20pts 思路进行特判。
\(n>3\) 时,需要一种支持查询最大值,最小值,动态插入删除的数据结构,平衡树简单维护即可,这里偷懒写了 multiset。

再考虑如何求得第一个操作不进行的回合。
站在最牛逼的蛇的角度想,只有它在吃掉最傻逼蛇后的所有回合中 都会不被吃掉,操作才会进行。
这里的所有回合指该回合之后,直到第一个一定不会进行操作的回合。
考虑倒序枚举操作序列,标记所有蛇是否被削除,枚举到被删除蛇时判断即可。

答案为 第一个 不会进行的回合时剩下的蛇的数量。
实现上有些细节,详见代码。

//知识点:贪心,单调性 
/*
By:Luckyblock
70pts 
*/
#include <algorithm>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <set>
#define LL long long
const int N = 1e6 + 10;
//==================================================
int n, o_num, a[N], opt[N]; //opt:删除顺序
bool suc[N], del[N]; 
//sucess:第 i 次是否成功,del:第 i 个是否已被删除 

struct Snake { //蛇蛇兄弟 
  int val, id;
  bool operator < (const Snake &sec_) const {
    if (val != sec_.val) return sec_.val > val;
    return sec_.id > id;
  }
};
std::multiset <Snake> s[11];
//==================================================
int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = 10 * w + ch - '0';
  return f * w;
}
void Init(int nowt_) {
  o_num = 0;
  memset(suc, 0, sizeof (suc));
  memset(del, 0, sizeof (del));
  
  if (nowt_ == 1) {
    n = read();
    for (int i = 1; i <= n; ++ i) a[i] = read();
  } else {
    int k = read();
    for (int i = 1; i <= k; ++ i) {
      int p = read();
      a[p] = read();
    }
  }
}
void Solve(int now_, int lth_) {
  Snake fir = *s[now_].begin(), las = *s[now_].rbegin();
  if (s[now_].size() == 3) { //n = 3 特殊情况 
    std::multiset <Snake>::iterator it = s[now_].begin();
    ++ it;
    if (las.val - fir.val < (*it).val || 
        (las.val - fir.val == (*it).val && las.id < (*it).id)) {
      suc[lth_] = false;
    } else {
      suc[lth_] = suc[lth_ + 1] = true;
      opt[++ o_num] = fir.id;
      opt[++ o_num] = (*it).id;
      del[fir.id] = del[(*it).id] = true;
    }
    return ;
  }
  
  s[now_].erase(s[now_].find(las));
  s[now_].erase(s[now_].begin());
  s[now_].insert((Snake) {las.val - fir.val, las.id});
  Solve(now_, lth_ + 1);
  
  if (!del[las.id]) { //本次最大值在之后不会被删除。
    suc[lth_] = true;
    opt[++ o_num] = fir.id; //
    del[fir.id] = true;
  } else { //本次删除不成功,清除记录的之后的删除操作。 
    for (int i = 1; i <= o_num; ++ i) del[opt[i]] = false;
    o_num = 0;
  }
}
//==================================================
int main() {
  int T = read();
  for (int nowt = 1; nowt <= T; ++ nowt) {
    Init(nowt);
    for (int i = 1; i <= n; ++ i) {
      s[nowt].insert((Snake) {a[i], i});
    }
    Solve(nowt, 0);
    for (int i = 0; i < n; ++ i) {
      if (! suc[i]) { //找到 **第一个** 不成功的位置 
        printf("%d\n", n - i);
        break ;
      }
    }
  }
  return 0;
}

100pts

这个单调性的证明还不是很懂,之后再补。
贴俩神仙的博客,可以来这里学习:

题解 贪吃蛇 - fight for dream - 洛谷博客
营业日志 2020.11.7 一次信息不对称引发的 CSP2020 T4 讨论 - EntropyIncreaser 的博客 - 洛谷博客

写在后面

这就算暂时告一段落了= =

posted @ 2020-11-08 19:45  Luckyblock  阅读(2424)  评论(4编辑  收藏  举报