CodeCraft-21 and Codeforces Round #711 (Div. 2)

163 篇文章 0 订阅
157 篇文章 1 订阅
传送门

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<x2y 的多个小矩形,替换后不会使答案更差;因为 2 x 2^x 2x 长的矩形要么恰好等于 ∑ y < x 2 y \sum\limits_{y<x} 2^y y<x2y,要么大于 ∑ y < x 2 y \sum\limits_{y<x} 2^y y<x2y;容易证明,若 d = ∑ y < x 2 y − 2 x > 0 d=\sum\limits_{y<x} 2^y-2^x>0 d=y<x2y2x>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 i1 的粒子运动到反方向的下一个平面。 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][j1]+dp[i1][Nj]


#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{kxky}。任意的节点对 x , y x,y x,y 只能询问一次其可达性,即询问是否存在 x → y x\rightarrow y xy y → x y\rightarrow x yx 的路径。若已知 x → y x\rightarrow y xy 存在可达路径,还需要了解是否存在 y → x y\rightarrow x yx 的可达路径才能推断 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 yx。若 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 xy

若询问没有当回答某条路径是否可达就需要输出答案的限制,那么可以 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 kxky 降序排序,保证可达性询问为真时,第一个判断出位于同一个 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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值