Codeforces Round #761 (Div. 2) E 题解

1617E - Christmas Chocolates

题目描述:

给出一个长度为 n n n 的序列 a a a,现在可以选择一对下标 ( x , y ) (x,y) (x,y),并进行如下操作,直到满足 a x = a y a_x=a_y ax=ay

每次操作可以选择一个满足 a x ≤ 2 k a_x \leq 2^k ax2k 的非负整数 k k k,并且使 a x a_x ax 变为 2 k − a x 2^k-a_x 2kax

现在要求选择的一对下标满足其最优解(操作次数最少)在所有下标对中最大,输出这个下标对与操作次数

1 ≤ n ≤ 1 0 5 , 0 ≤ a i ≤ 1 0 9 1 \leq n \leq 10^5,0 \leq a_i \leq 10^9 1n105,0ai109

solution:

从图论的角度来思考问题,把每个整数作为一个点,当且仅当 i + j = 2 k i+j=2^k i+j=2k 次时, i i i j j j 之间会产生一条无向边。那么整张图是一棵树的结构,论证如下:

  • 结论: 对于任意 i ∈ [ 0 , 1 0 9 ] i \in [0,10^9] i[0,109],则有唯一的 j ∈ [ 0 , i ) j \in [0,i) j[0,i),使得 i + j = 2 k i+j = 2^k i+j=2k 对于某个 k k k 成立
    证明: 0 ≤ j 2 ≤ j 1 < i , i + j 1 = 2 k 1 , i + j 2 = 2 k 2 0 \leq j_2 \leq j_1 < i,i+j_1=2^{k_1},i+j_2=2^{k_2} 0j2j1<i,i+j1=2k1,i+j2=2k2,则 j 1 − j 2 = 2 k 1 − 2 k 2 = 2 k 2 ( 2 k 1 − k 2 − 1 ) j_1-j_2=2^{k_1}-2^{k_2}=2^{k_2}(2^{k_1-k_2}-1) j1j2=2k12k2=2k2(2k1k21),所以 j 1 ≡ j 2 ( m o d    k 2 ) j_1 \equiv j_2 (\mod k_2) j1j2(modk2)。因为 i ≤ 2 k 2 i \leq 2^{k_2} i2k2,所有有 j 1 = j 2 j_1=j_2 j1=j2

这是一棵以 0 0 0 为根节点,以 a i a_i ai 为叶节点的树,那么现在就转换为一个树上直径问题,虽然数据规模有 1 0 9 10^9 109,但是实际上树的高度是 l o g log log 级别的,我们用树形DP直接求直径,总的复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn)

但是这题中上述的树是为了方便抽象出来的,实际上是一棵由相关点集构成的虚树。因为当所有点都在以 0 0 0 为根的某一棵子树时,我们再以 0 0 0 为根去统计显然会贡献多余,因此我们需要处理。我们以叶子为根预先做一次 dfs,当某个节点既不出现在序列中,其子树中又没有相关点集,我们就不能用这个点更新值。

code:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn=2e6+10;

int n,x;

unordered_map<int,int> mp;
int o=0;

inline int find(int x){
    for(int i=0;i<=30;i++){
        if((1<<i)>=x) return 1<<i;
    }
}

int head[maxn],to[maxn<<1],nex[maxn<<1],tot=0;
int f[maxn],idx[maxn],back[maxn],ansu,ansv,ans;
bool avi[maxn],vis[maxn];

inline void add(int u,int v){
    to[++tot]=v;
    nex[tot]=head[u];
    head[u]=tot;
    to[++tot]=u;
    nex[tot]=head[v];
    head[v]=tot;
}

void pre_dfs(int u,int fa){
    for(int i=head[u];i;i=nex[i]){
        int v=to[i];
        if(v==fa) continue;
        pre_dfs(v,u);
        vis[u]|=vis[v];
    }
    //cout<<u<<":"<<vis[u]<<endl;
}

void dfs(int u,int fa){
    f[u]=0;
    idx[u]=u;
    for(int i=head[u];i;i=nex[i]){
        int v=to[i];
        if(v==fa) continue;
        dfs(v,u);
        if(f[v]+1+f[u]>ans&&vis[v]) ans=f[u]+f[v]+1,ansu=idx[u],ansv=idx[v];
        if(f[v]+1>f[u]&&vis[v]) f[u]=f[v]+1,idx[u]=idx[v];
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin>>n;

    for(int i=1;i<=n;i++){
        cin>>x;
        if(!mp.count(x)) mp[x]=++o;
        else{
            back[mp[x]]=i;
            vis[mp[x]]=1;
            continue;
        }
        back[mp[x]]=i;
        vis[mp[x]]=1;
        while(x){
            int y=find(x)-x;
            if(!mp.count(x)) mp[x]=++o;
            if(!mp.count(y)) mp[y]=++o;
            else{
                add(mp[x],mp[y]);break;
            }
            add(mp[x],mp[y]);
            x=y;
        }
    }
    pre_dfs(1,0);
    dfs(1,0);

    cout<<back[ansu]<<" "<<back[ansv]<<" "<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值