Codeforces Round #563 (Div. 2)

本文介绍了Codeforces Round #563的两道题目,涉及数组构造和前缀异或性质。D题要求构建数组,使得任意连续区间的异或和不等于0或x,关键在于确保所有异或前缀和两两不同。E题探讨如何构造排列以最大化不同前缀的最大公约数(gcd)数量,通过调整素因子和gcd变化规律来实现。
摘要由CSDN通过智能技术生成

Codeforces Round #563 (Div. 2)
D:
题意是给定 n n n x x x ,构建这样最长的数组 a a a 满足: 1 ≤ a i ≤ 2 n 1\leq a_i \leq 2^n 1ai2n 且任意一个连续区间的异或和不等于 0 0 0 x x x
一个区间[l, r]的异或和可以由l-1的异或前缀和和r的异或前缀和异或起来表示。

  1. 连续区间异或和不为0则说明构造出的数组中,没有任何一个点的异或前缀和相等。
  2. 连续区间异或和不为 x x x 则表示,数组中每个点的异或前缀和异或上 x x x,不等于任何一个点的异或前缀和。

综合以上两点可知每一个点的异或前缀和和异或前缀和异或上x两两不等。因此可以通过已知的异或前缀和构造出整个数组。当 x > 2 n x > 2^n x>2n 时,不受限制2的影响。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6;
bool vis[N];
int p[N];
int a[N];
int main() {
    int n, x;
    scanf("%d%d", &n, &x);
    int m = 0;
    if(x>=(1<<n)) {
        // 不可能有区间异或为x,所有前缀和可用
        for(int i=1; i<(1<<n); ++i) {
            p[m++]=i;
        }
    } else {
        // 排除互斥
        vis[0]=vis[x]=true;
        for(int i=0; i<(1<<n); ++i) {
            if(vis[i]) continue;
            vis[i]=vis[i^x]=true;
            p[m++]=i;
        }
    }
    a[0]=p[0];
    for(int i=1; i<m; ++i) {
        a[i]=p[i-1]^p[i];
    }
    printf("%d\n", m);
    for(int i=0; i<m; ++i) {
        printf("%d%c", a[i], i+1==m?'\n':' ');
    }
}

E:
题意是给定一个 n n n ,需要构造一个 n n n 的排列使得不同前缀gcd的数量最多,求有多少种构造方法。
gcd从前求到后,每次遇到一个新元素,gcd要么不变要么减少,减少就多了一个不同的前缀gcd值。因此要使得不同前缀gcd的数量最多,第一个数一定是 2 x 3 y 2^x3^y 2x3y ,如果还有大于3的素因子 p p p ,可以除掉这个素因子乘上个 4 4 4 ,使得不同前缀gcd的数量增加。同时 y y y 一定小于等于1,否则可以除掉 9 9 9 再乘上 8 8 8 ,增加不同前缀gcd的数量。
因此设 dp[i][x][y] 为第 i i i 个点,当前前缀gcd为 2 x 3 y 2^x3^y 2x3y 时的方案数。每次转移要么使gcd不变,要么使gcd减少。设 f(x, y) 为1~n中是 2 x 3 y 2^x3^y 2x3y 倍数的数量,得转移方程如下:
dp[i+1][x][y]+=dp[i][x][y]*(f(x, y)-i)
dp[i+1][x-1][y]+=dp[i][x][y]*(f(x-1, y)-f(x, y))
dp[i+1][x][y-1]+=dp[i][x][y]*(f(x, y-1)-f(x, y))

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6+7;
int dp[N][20][2], p[20][2];
int n;
const int mod = 1e9+7;
// dp[i][x][y] 表示第i个数是2^x*3^y的倍数时的填充种类数。
int f(int x, int y) {
    // 有多少2^x*3^y小于等于n
    return n/p[x][y];
}
void add(int &x, int y, int z) {
    int prod = 1LL*y*z%mod;
    x = (x+prod)%mod;
}
int main() {
    scanf("%d", &n);
    int prod=1, cnt=0;
    while(prod<=n) prod*=2, ++cnt;
    prod/=2; --cnt;
    dp[1][cnt][0]=1;
    if(prod/2*3<=n) dp[1][cnt-1][1]=1;

    p[0][0]=1; p[0][1]=3;
    for(int i=1; i<20; ++i) {
        for(int j=0; j<2; ++j) {
            p[i][j]=p[i-1][j]*2;
        }
    }

    for(int i=1; i<n; ++i) {
        for(int x=0; x<20; ++x) {
            for(int y=0; y<2; ++y) {
                add(dp[i+1][x][y], dp[i][x][y], f(x, y)-i);
                if(x>0) add(dp[i+1][x-1][y], dp[i][x][y], f(x-1, y)-f(x, y));
                if(y>0) add(dp[i+1][x][y-1], dp[i][x][y], f(x, y-1)-f(x, y));
            }
        }
    }
    printf("%d\n", dp[n][0][0]);
    return 0;
}

F:
首先树剖求出重链。从根 1 1 1 开始,假设其重链的深度最大点(肯定为叶子)为 u u u x x x 到根的路径在 y y y 点与重链相交。可以得到 d ( x ) + d ( u ) − 2 × d ( y ) = d i s ( x , u ) d(x)+d(u)-2 \times d(y) =dis(x, u) d(x)+d(u)2×d(y)=dis(x,u) ,由于 d ( x ) , d ( u ) d(x), d(u) d(x),d(u) 已知,因此通过询问 d i s ( x , u ) dis(x, u) dis(x,u) 就可以得到 d ( y ) d(y) d(y) 了,然后通过重链回溯可以找到 y y y 。如果 d ( y ) d(y) d(y) 等于 d i s ( x , u ) dis(x, u) dis(x,u) ,则说明 y y y 就是 x x x 。找到 y y y 之后,可以通过 s 询问跳轻边。由于一条路径上的重链和轻边都是 O ( l o g n ) O(logn) O(logn) 级别的,因此重链轻边都最多为 17。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
int fa[N], son[N], sz[N], d[N], dx, dy;
bool xisy, xsony;
vector<int> adj[N];
void dfs1(int u, int p, int deep) {
    fa[u]=p;
    d[u]=deep;
    sz[u]=1;
    for(int v : adj[u]) {
        if(v==p) continue;
        dfs1(v, u, deep+1);
        sz[u]+=sz[v];
        if(son[u]==-1||sz[son[u]]<sz[v]) son[u]=v;
    }
}
void init() {
    memset(son, -1, sizeof(son));
}
int queryS(int u) {
    printf("s %d\n", u);
    fflush(stdout);
    scanf("%d", &u);
    return u;
}
int queryD(int u) {
    printf("d %d\n", u);
    fflush(stdout);
    scanf("%d", &u);
    return u;
}
void dfs(int u) {
//    printf("dfs: %d %d %d\n", u, dy);
    if(son[u]!=-1) {
        dfs(son[u]);
    } else {
        int dux=queryD(u);
        dy=(d[u]+dx-dux)/2;
        if(d[u]-dy==dux) xisy=true;
        if(d[u]-dy+1==dux) xsony=true;
    }
    if(d[u]==dy) {
        if(xisy) {
            printf("! %d\n", u);
        } else {
            int v = queryS(u);
            if(xsony) printf("! %d\n", v);
            else dfs(v);
        }
    }
}
int main() {
    int n;
    scanf("%d", &n);
    for(int i=1; i<n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    init();
    dfs1(1, 0, 0);
    dx=queryD(1);
    if(dx==0) {
        printf("! 1\n");
        fflush(stdout);
    } else {
        dfs(1);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值