CSP 2020-S 题解
写在前面
挂了。
noip 还是要打的。
先来补题了,补游记看心情。
A
正解
参考:这位神仙的提交记录。
,允许在每次询问进行一个 的暴力。
暴力可知 的儒略历是 ,以该日期为分界线处理。
对于 ,依次计算剩下的天数中有几个 年、 年、 月,再加上天数。
对于 ,先将日期转化为 ,再依次计算剩下的天数中有几个 年、 年、 年、 月,再加上天数。
复杂度上界约为 。
复制复制//知识点:模拟 /* 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,设有 位,答案即为 。
注意特判答案为 的情况。 可以通过 ull
溢出得到。
时间复杂度 。
//知识点:瞎搞 /* 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
正解
参考:又一位神仙的提交记录。
参考代码中没有判 的情况,牛客数据比较水卡过了(
先根据拓扑序记忆化搜索,求得每个操作 3 的乘法增量 mul
,即会令各元素变为之前的多少倍。
完成上述过程后,可顺便得到乘法操作对原数的影响。
再考虑加法操作,再在拓扑序上 DP,求得每种操作被调用多少次,注意倒序进行,通过调用者更新被调用者。
某加法操作的贡献为调用次数 + 影响到它乘法操作的次数。
两者贡献量相同,在代码中用 cnt
记录了两者的和。
最后还原原数即可,时间复杂度 。
//知识点:拓扑排序,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
在每一个回合中,若发生吞噬,则只会是最牛逼的蛇吃掉最傻逼的蛇。则操作的序列是固定的,考虑构造出这个序列,并根据这个序列求得第一个操作不进行的回合,即得答案。
考虑如何构建操作序列。
当 时,按照上述 20pts 思路进行特判。
当 时,需要一种支持查询最大值,最小值,动态插入删除的数据结构,平衡树简单维护即可,这里偷懒写了 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 的博客 - 洛谷博客。
写在后面
这就算暂时告一段落了= =
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效