传送门
CodeCraft-21 and Codeforces Round #711 (Div. 2)
A
3 3 3 的倍数各位数字求和依然是 3 3 3 的倍数,相邻 3 3 3 个数字中必有 1 1 1 个满足条件。从小到大枚举 3 3 3 个数字即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
int T;
ll N;
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
bool judge(int i)
{
ll x = N + i, t = x, s = 0;
while (t)
s += t % 10, t /= 10;
return gcd(x, s) > 1;
}
int main()
{
scanf("%d", &T);
while (T--)
{
scanf("%lld", &N);
ll res;
for (int i = 0; i < 3; ++i)
if (judge(i))
{
res = N + i;
break;
}
printf("%lld\n", res);
}
return 0;
}
B
矩形长度为 2 x 2^x 2x 的形式,若可以使用 2 x 2^x 2x 的矩形替换 ∑ y < x 2 y \sum\limits_{y<x} 2^y y<x∑2y 的多个小矩形,替换后不会使答案更差;因为 2 x 2^x 2x 长的矩形要么恰好等于 ∑ y < x 2 y \sum\limits_{y<x} 2^y y<x∑2y,要么大于 ∑ y < x 2 y \sum\limits_{y<x} 2^y y<x∑2y;容易证明,若 d = ∑ y < x 2 y − 2 x > 0 d=\sum\limits_{y<x} 2^y-2^x>0 d=y<x∑2y−2x>0,将小矩形集合中 d d d 二进制表示下为 1 1 1 的对应长度的小矩形删去,此时有 d = 0 d=0 d=0。那么对于每一单位高度的盒子,贪心地放入的可行的最长矩形。对矩形按二进制位统计进行处理,总时间复杂度 O ( N log W ) O(N\log W) O(NlogW)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005, maxlg = 20;
int T, N, W, tot, w[maxn], cnt[maxlg];
int main()
{
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &N, &W);
for (int i = 1; i <= N; ++i)
scanf("%d", w + i);
for (int i = 1; i <= N; ++i)
for (int j = maxlg - 1; j >= 0; --j)
if (w[i] >> j & 1)
{
++cnt[j], ++tot;
break;
}
int res = 0;
while (tot)
{
++res;
int t = W;
for (int i = maxlg - 1; i >= 0; --i)
while (t >= (1 << i) && cnt[i])
--cnt[i], --tot, t -= (1 << i);
}
printf("%d\n", res);
}
return 0;
}
C 补题
比赛时试图划分 N N N 或 K K K 以缩小问题规模进行状态转移,难以处理。
考虑模拟粒子的运动过程。 d p [ i ] [ j ] dp[i][j] dp[i][j] 代表初始情况下某个衰减度为 i i i 的粒子前进方向上存在 j j j 个平面最终产生的粒子总数。在下一个时间点,这个粒子在其前进方向上运动到下一个平面,它在第一个平面上产生的衰减度为 i − 1 i-1 i−1 的粒子运动到反方向的下一个平面。 d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + d p [ i − 1 ] [ N − j ] dp[i][j] = dp[i][j - 1] + dp[i - 1][N - j] dp[i][j]=dp[i][j−1]+dp[i−1][N−j]
#include <bits/stdc++.h>
using namespace std;
const int mod = 1000000007;
const int maxn = 1005, maxk = 1005;
int T, N, K, dp[maxk][maxn];
int main()
{
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &N, &K);
for (int i = 1; i <= K; ++i)
dp[i][0] = 1;
for (int j = 1; j <= N; ++j)
dp[1][j] = 1;
for (int i = 2; i <= K; ++i)
for (int j = 1; j <= N; ++j)
dp[i][j] = (dp[i][j - 1] + dp[i - 1][N - j]) % mod;
printf("%d\n", dp[K][N]);
}
}
D 补题
迭代 n n n 轮操作,每次以之前操作可以取到的数字为起点,进行至多 y i y_i yi 次,暴力求解总时间复杂度 O ( N M 2 ) O(NM^2) O(NM2),显然难以胜任。观察到第 i i i 轮操作中,前向转移是唯一的,逆向转移则不一定唯一;若 2 2 2 次不同的前向转移访问到同一个数字,那么后续的状态转移是一致的。
那么从大到小以之前操作中取到的数字 j j j 为起点,前向转移 k , k ∈ [ 0 , y i ] k,k\in[0,y_i] k,k∈[0,yi] 次,若转移到某个本轮操作可以取到的数字或大于 m m m,则停止前向转移;则第 i i i 轮操作以 j j j 为起点的前向转移除了访问到之前操作未取到的数字,至多访问 1 1 1 次之前可取到的数字。那么 n n n 轮迭代,访问到之前未取到的数字,至多 m m m 次,访问到之前可取到的数字,至多 n m nm nm 次,总时间复杂度 O ( N M ) O(NM) O(NM)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxm = 100005, bs = 100000;
int N, M, rec[maxm];
bool vs[maxm];
inline ll ceil(ll x, ll b) { return (x + b - 1) / b; }
inline ll op(int t, ll w, ll x) { return t == 1 ? w + ceil(x, bs) : ceil(w * x, bs); }
int main()
{
scanf("%d%d", &N, &M);
memset(rec, -1, sizeof(rec));
vs[0] = 1;
for (int i = 1; i <= N; ++i)
{
int t, y;
ll x;
scanf("%d%lld%d", &t, &x, &y);
for (int j = M; j >= 0; --j)
if (vs[j])
{
ll w = j;
for (int k = 1; k <= y; ++k)
{
w = op(t, w, x);
if (w > M || vs[w])
break;
vs[w] = 1, rec[w] = i;
}
}
}
for (int i = 1; i <= M; ++i)
printf("%d%c", rec[i], i == M ? '\n' : ' ');
return 0;
}
E 补题
第一道交互题。求位于同一个 S C C SCC SCC 中的两个节点 x , y x,y x,y 的 max { ∣ k x − k y ∣ } \max\{\lvert k_x-k_y\rvert\} max{∣kx−ky∣}。任意的节点对 x , y x,y x,y 只能询问一次其可达性,即询问是否存在 x → y x\rightarrow y x→y 或 y → x y\rightarrow x y→x 的路径。若已知 x → y x\rightarrow y x→y 存在可达路径,还需要了解是否存在 y → x y\rightarrow x y→x 的可达路径才能推断 x , y x,y x,y 属于同一个 S C C SCC SCC。
本题的有向图满足任意两个节点间有且仅有一条有向边。那么容易证明有向图 S C C SCC SCC 缩点后的 D A G DAG DAG 拓扑序唯一;否则,考虑拓扑序的求解过程,若拓扑序不唯一,则删除某一个节点的出边后,至少存在两个节点满足入度为 0 0 0,而两个节点间至少存在一条有向边,则至少存在一个节点入度不小于 1 1 1,与上述推导矛盾。
容易证明拓扑序唯一的 D A G DAG DAG 各节点入度随着拓扑序增大而单调递增。考虑拓扑序的求解过程,已确定拓扑序的节点删除了所有出边,则对所有其他节点入度的贡献 s s s 是一致的,而拓扑序唯一保证某次删除节点出边后,仅存在一个节点入度为 0 0 0,其余节点入度至少为 1 1 1;此时入度为 0 0 0 的节点初始时入度为 s s s,其余节点初始时入度至少为 s + 1 s+1 s+1。任意轮更新都满足上述推论。
设入度为 d e g deg deg,可以推导,若两个节点 x , y x,y x,y 满足 d e g x > d e g y deg_x>deg_y degx>degy,则存在可达路径 y → x y\rightarrow x y→x。若 x , y x,y x,y 属于同一个 S C C SCC SCC,显然满足条件;否则,根据上述 S C C SCC SCC 缩点的 D A G DAG DAG 的性质, x , y x,y x,y 也满足条件。此时对于任意节点对,就获取了某一方向路径的可达信息,此时仅需询问是否存在路径 x → y x\rightarrow y x→y。
若询问没有当回答某条路径是否可达就需要输出答案的限制,那么可以 O ( N log N ) O(N\log N) O(NlogN) 对节点按照 k x k_x kx 排序,此时同一个 S C C SCC SCC 中的节点位置相邻, O ( N ) O(N) O(N) 划分出 S C C SCC SCC 即可。而考虑这个限制条件后,只能 O ( N 2 ) O(N^2) O(N2) 枚举节点对,按照 ∣ k x − k y ∣ \lvert k_x-k_y\rvert ∣kx−ky∣ 降序排序,保证可达性询问为真时,第一个判断出位于同一个 S C C SCC SCC 的点对为答案。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 505;
struct node
{
int a, b, d;
bool operator<(const node &o) { return d < o.d; }
} P[maxn * maxn];
int N, np, deg[maxn];
inline bool judge(int a, int b)
{
char s[5];
printf("? %d %d\n", a, b);
fflush(stdout);
scanf("%s", s);
return s[0] == 'Y';
}
int main()
{
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
scanf("%d", deg + i);
for (int i = 1; i <= N; ++i)
for (int j = i + 1; j <= N; ++j)
{
int a = i, b = j;
if (deg[a] < deg[b])
swap(a, b);
P[++np] = node{a, b, deg[a] - deg[b]};
}
sort(P + 1, P + np + 1);
for (int i = np; i; --i)
if (judge(P[i].a, P[i].b))
{
printf("! %d %d\n", P[i].a, P[i].b);
return 0;
}
printf("! 0 0\n");
return 0;
}
F 补题
考虑 k = 1 k=1 k=1 的情况。必败的终止局面为所有 a i a_i ai 都移动到深度为 0 0 0 的根节点。将节点按照深度的奇偶性进行划分,可以证明偶数点对游戏的状态没有影响。若某次 A l i c e Alice Alice 移动偶数点 m m m 的值至奇数点,那么 B o b Bob Bob 在下一轮将对应的值 m m m 移动至偶数点,在对称的操作下最终将会移动到根节点,又回到 A l i c e Alice Alice 先手的状态。
若从奇数点移动 m m m 至偶数点,可看做丢弃这 m m m 个对游戏状态没有影响的礼物。将奇数点的 a i a_i ai 看做石子堆的规模,则转化为 N i m Nim Nim 博弈,若奇数点的 a i a_i ai 异或和非零,则先手必胜;反之,先手必败。
拓展到 k > 1 k>1 k>1 的情况,则以一步移动为单位,此时有 d e p ′ = ⌊ d e p / k ⌋ dep'=\lfloor dep/k\rfloor dep′=⌊dep/k⌋,在 d e p ′ dep' dep′ 为深度的意义下进行求解。
求解以树上各节点为根的情况下的奇数点异或和。为了高效的求解,需要在 D F S DFS DFS 中对统计信息进行换根操作。具体而言, D F S DFS DFS 预处理出以 i i i 为根的子树上,模 2 k 2k 2k 意义下深度为 j j j 的异或和 r e c [ i ] [ j ] rec[i][j] rec[i][j],此时以 s o n ( i ) son(i) son(i) 为根的子树上的节点深度 d d d 改变为 d + 1 d+1 d+1。然后再进行一次 D F S DFS DFS,求解以各节点为根节点时的答案,设当前信息为以 i i i 为根的树,换根时,首先将子树 s o n ( i ) son(i) son(i) 的贡献删除,此时剩余节点的深度 d d d 改变为 d + 1 d+1 d+1,接着再将子树 s o n ( i ) son(i) son(i) 的贡献加上,就得到了换根后的树的信息。
总时间复杂度 O ( N K ) O(NK) O(NK)。
#include <bits/stdc++.h>
using namespace std;
typedef vector<int> vec;
const int maxn = 100005, maxk = 22, maxe = 2 * maxn;
int N, K, K2, A[maxn], sg[maxn], rec[maxn][2 * maxk];
int tot, head[maxn], to[maxe], nxt[maxe];
inline void add(int x, int y) { to[++tot] = y, nxt[tot] = head[x], head[x] = tot; }
void dfs(int x, int f)
{
rec[x][0] = A[x];
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i];
if (y == f)
continue;
dfs(y, x);
for (int j = 1; j < K2; ++j)
rec[x][j] ^= rec[y][j - 1];
rec[x][0] ^= rec[y][K2 - 1];
}
}
void dfs2(int x, int f, vec &cur)
{
int &res = sg[x];
for (int i = K; i < K2; ++i)
res ^= cur[i];
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i];
if (y == f)
continue;
vec nxt = cur;
for (int j = 0; j < K2 - 1; ++j)
nxt[j + 1] ^= rec[y][j];
nxt[0] ^= rec[y][K2 - 1];
int t = nxt[K2 - 1];
for (int j = K2 - 2; j >= 0; --j)
nxt[j + 1] = nxt[j];
nxt[0] = t;
for (int j = 0; j < K2; ++j)
nxt[j] ^= rec[y][j];
dfs2(y, x, nxt);
}
}
int main()
{
scanf("%d%d", &N, &K);
K2 = 2 * K;
for (int i = 1, x, y; i < N; ++i)
scanf("%d%d", &x, &y), add(x, y), add(y, x);
for (int i = 1; i <= N; ++i)
scanf("%d", A + i);
dfs(1, 0);
vec t(K2);
for (int i = 0; i < K2; ++i)
t[i] = rec[1][i];
dfs2(1, 0, t);
for (int i = 1; i <= N; ++i)
printf("%d ", sg[i] > 0);
putchar('\n');
return 0;
}