Codeforces Round #633 (Div. 1)

A. Powered Addition (CF 1338 A)

题目大意

给定一个\(n\)个数字的数组\(a\),对于每一个正整数\(x\),你可以选择若干个数字\(i_1,i_2,i_3,...,i_k\),使得\(a_{i_j}=a_{i_j}+2^{x-1},\ 1 \leq j \leq k\)。当然你也可以不进行选择。求最小的数字\(T\),使得当\(x\)取过了\(1\)~\(T\)进行操作后,数组\(a\)是一个非递减数组。

解题思路

从左到右,如果\(a_i>a_{i+1}\),我们就把\(a_{i+1}\)变成\(a_i\),并记录最大的\(a_i-a_{i+1}\),最大值在二进制下的位数即是答案。

操作就很简单,差值在二进制下第\(i\)位是1就在\(x=i\)的时候选择那个数就好了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ++ii) {
        int n;
        read(n);
        int ans=0;
        int qwq=-1e9-7;;
        for(int u,i=1;i<=n;++i){
            read(u);
            if (qwq<=u) qwq=u;
            else{
                ans=max(ans,qwq-u);
            }
        }
        int cnt=0;
        while(ans){
            ans>>=1;
            ++cnt;
        }
        write(cnt,'\n');
    }
    return 0;
}


B. Edge Weight Assignment (CF 1338 B)

题目大意

给定一棵无权树,要求对边赋一个正权值,使得任意两个叶子节点之间的路径权值异或和为\(0\)。求所赋的不同权值数的最小值和最大值。

解题思路

如果任意两个叶子节点之间的路径长度是偶数,那么我们对全部边赋\(1\)即可,最小为\(1\)

如果存在奇数长度的,注意到\(1\bigoplus 2\bigoplus 3=0\)。我们把不与叶子节点相连的边赋为\(1\),是奇数长度的那对叶子节点\(a,b\)相连的边,一个赋\(2\),一个赋\(3\),那其他叶子节点对于这两个叶子节点,如果是偶数路径,则与其叶子节点相连的边的权值一样,奇数路径则相反(\(2\)\(3\)\(3\)\(2\))。

对于不是\(a,b\)的一对点\(c,d\),它们的边权异或和\((c,d)\)一定为\(0\),因为边权异或和可以拆成\((c,d)=(c,a)\bigoplus(a,d)\),因为\(c,d\)\(lca\)\(a\)的边权异或了两次抵消掉了。而在前面的构造里我们知道\((c,a)=0,(a,d)=0\),所以\((c,d)=0\)。也即最小为\(3\)

判断奇数长度的,从一个叶子节点搜(初始深度为\(1\))发现有深度是偶数深度的叶子节点即可得知有奇数长度。

至于最大的,我们可以设想,除了叶子节点相连的边,其他的边都填不同的数,然后选择一个叶子节点\(a\),其边也填一个不同的数,然后对于其他叶子节点\(b\),其边填(a,fa[b])的值(fa[b]表示与叶子节点相连的边的另外一个节点)。对于不是叶子节点\(a\)的点\(b,c\),其\((b,c)\)也一定为\(0\),证明也如同上面的方法,将\((b,c)=(b,a)\bigoplus(a,c)\)

由于可以填的数无穷大,我们可以证明 (猜想) 这一定可以做到的。

所以,只有那些一个节点连了多个叶子节点的那些边的权值一定相等外,其他的都可以不一样。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

void DFS(int u,int fa,int deep[],vector<int> edge[]){
    deep[u]=deep[fa]+1;
    for(auto v:edge[u]){
        if (v==fa) continue;
        DFS(v,u,deep,edge);
    }
}

int dfs(int u,int fa,bool &qwq,int &ans,int deep[],vector<int> edge[],int st){
    int cnt=0;
    int aa=0;
    deep[u]=deep[fa]+1;
    for(auto v:edge[u]){
        if (v==fa) continue;
        ++cnt;
        aa+=dfs(v,u,qwq,ans,deep,edge,st);
    }
    if (!cnt){
        if (!(deep[u]&1)) qwq=true;
        return 1;
    }
    if (fa==st) ++aa;
    ans-=max(0,aa-1);
    return 0;
}

int main(void) {
    int n;
    read(n);
    vector<int> edge[n+1];
    for(int u,v,i=1;i<n;++i){
        read(u);
        read(v);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    int deep[n+1]={0};
    DFS(1,1,deep,edge);
    int st=0,dest=0;
    for(int i=1;i<=n;++i)
        if (dest<deep[i]){
            dest=deep[i];
            st=i;
        }
    deep[st]=0;
    bool qwq=false;
    int ans=n-1;
    dfs(st,st,qwq,ans,deep,edge,st);
    printf("%d %d\n",(qwq?3:1),ans);
    return 0;
}


C. Perfect Triples (CF 1338 C)

题目大意

有一个有无穷无尽个正整数的数组\(s\),其构造方法如下:

  • \(a<b<c\)
  • \(a,b,c \notin s\)
  • \(a\bigoplus b\bigoplus c=0\)
  • (a,b,c)是可选中的字典序最小的一组
  • 把a,b,c依次加入\(s\)数组的末尾
  • 重复第一步

现有\(t\)组询问,每组询问一个数\(n\),问你数组中第\(n\)个数(从\(1\)开始)是多少。

解题思路

打表大法好

注意到\(1\bigoplus 2\bigoplus 3=0\)

这恰好是\(4\)进制。于是我们可以把数转成四进制(二进制下俩俩合并),三个数看成一组,对于这一组的数,四进制下每一位我们就可以单独考虑。

对于最高位,由于\(a<b<c\),所以最高位上一定是\(1、2、3\)

然后对于以下的每一位,只有四种情况:

  • \(0\ 0\ 0\)
  • \(1\ 2\ 3\)
  • \(2\ 3\ 1\)
  • \(3\ 1\ 2\)

这四种情况也是按照字典序从小到大排好的,其余的情况会出现重复数字。

按照字典序从小到大构造的三元组的方法就很明显啦,对于三个数的每一位,依次取遍四种情况,然后到下一位。

这也才造成我们打的表里面,第一个数以\(4^n\)个分组的结果。

给定\(n\)就相当于问第\(\dfrac{n-1}{3}\)组(从\(0\)开始)的第\((n-1)\%3\)位是多少。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

void work(LL id,LL i,pair<LL,LL> &ans){
    if (i<0) return;
    if (i==0) {
        LL rank=id;
        if (rank==1){
            ans.first^=(1ll<<(i));
            ans.second^=(1ll<<(i+1));
        }else if (rank==2){
            ans.first^=(1ll<<(i+1));
            ans.second^=((1ll<<(i))^(1ll<<(i+1)));
        }else if (rank==3){
            ans.first^=((1ll<<(i))^(1ll<<(i+1)));
            ans.second^=(1ll<<(i));
        }
        return;
    }
    LL cnt=(1ll<<(i));
    work(id%cnt,i-2,ans);
    LL rank=id/cnt;
    if (rank==1){
        ans.first^=(1ll<<(i));
        ans.second^=(1ll<<(i+1));
    }else if (rank==2){
        ans.first^=(1ll<<(i+1));
        ans.second^=((1ll<<(i))^(1ll<<(i+1)));
    }else if (rank==3){
        ans.first^=((1ll<<(i))^(1ll<<(i+1)));
        ans.second^=(1ll<<(i));
    }
}

int main(void) {
    int t;
    read(t);
    while(t--){
        LL n;
        read(n);
        LL id=(n-1)/3;
        LL pos=(n-1)%3;
        pair<LL,LL> ans;
        ans.first=0;
        ans.second=0;
        for(int i=0;true;i+=2){
            if (id<(1ll<<i)){
                work(id,i-2,ans);
                ans.first^=(1ll<<(i));
                ans.second^=(1ll<<(i+1));
                break;
            }else id-=(1ll<<i);
        }
        printf("%lld\n",pos==0?ans.first:(pos==1?ans.second:(ans.first^ans.second)));
    }
    return 0;
}


不能打\(div2\)但又虚\(div1\)......\(qwq\)

posted @ 2020-04-15 23:24  ~Lanly~  阅读(281)  评论(0编辑  收藏  举报