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 的博客 - 洛谷博客。
写在后面
这就算暂时告一段落了= =