Codeforces Round 976 (Div. 2)

VP 的这一场,真的唐完了。。。

A. Find Minimum Operations

题意

给你一个 \(n\)\(k\) ,每次操作可以让 \(n\) 减去一个 \(k\)\(x\) 次方,即 \(n-k^x\)。问你最少操作几次能够让 \(n\) 变成 \(0\)

思路

我们先考虑如果 \(k\)\(2\) 的情况,那么题目就转化成了 \(n\) 的二进制中 \(1\) 的个数。扩展到任意进制的话就是把 \(n\) 转化为 \(k\) 进制,每次操作可以使一个 \(k\) 进制数位上的数减 \(1\) 。于是我们只要把 \(n\) 转化为 \(k\) 进制,然后把每个数位上的数加起来即可。

代码

void solve()
{
    int n,k,ans=0;
    cin>>n>>k;
    if(k==1) {return cout<<n<<endl,void();}
    while(n)
    {
        ans+=n%k;
        n/=k;
    }
    cout<<ans<<endl;
}

B. Brightness Begins

题意

假设你有 \(n\) 个初始状态为亮的灯泡,接下来你要执行一下操作:

  • 对于每个 \(i=1,2...n\) 反转所有灯牌 \(j\) 的状态,\(j\) 是可以被 \(i\) 整除的数。

执行完所有操作后,你希望亮着的灯泡数量为 \(k\) 。现在给你一个 \(k\) ,问你 \(n\) 最小可以是多少。

思路

这种题我一般先想到打表找规律,看看每个 \(n\) 对应的最后的状态。打出来的表如下

0 
0 1 
0 1 1 
0 1 1 0 
0 1 1 0 1 
0 1 1 0 1 1 
0 1 1 0 1 1 1 
0 1 1 0 1 1 1 1 
0 1 1 0 1 1 1 1 0 
0 1 1 0 1 1 1 1 0 1 
0 1 1 0 1 1 1 1 0 1 1 
0 1 1 0 1 1 1 1 0 1 1 1 
0 1 1 0 1 1 1 1 0 1 1 1 1 
0 1 1 0 1 1 1 1 0 1 1 1 1 1 
0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 
0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 0 
0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 0 1 
0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 0 1 1 
0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 
0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 
0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 
0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 

我们发现前面的灯泡状态是固定的,然后每几个连续的 \(1\) 之间都会有一个 \(0\) 隔开,并且发现连续的 \(1\) 的数量是 \(2,4...2\cdot n\) ,一个公差为 \(2\) 的等差数列。于是我们就考虑 \(k\) 在哪一段区间内,最终的答案就是 \(k\) 所在的区间加上 \(k\) 即可。找区间的过程用个二分就行了。

代码

注释掉的内容为打表代码

void solve()
{
    // int n;
    // cin>>n;
    // vector<int> a(n+1,1);
    // for(int i=1;i<=n;i++)
    // {
    //     for(int j=1;j<=n;j++)
    //         if(j%i==0) a[j]^=1;
    // }
    // int num=0;
    // for(int i=1;i<=n;i++)  if(a[i]) num++;
    // cout<<num<<endl;
    // for(int i=1;i<=n;i++) cout<<a[i]<<' ';
    // cout<<endl;
    int k;
    cin>>k;
    int l=1,r=k;
    while(l<r)
    {
        __int128_t mid=l+r>>1;//这里要注意等差数列求和的时候要开int128,我就是因为这里WA了
        if((mid+1)*mid>=k) r=mid;
        else l=mid+1;
    }
    cout<<l+k<<endl;
}

C. Bitwise Balancing

题意

给你三个非负整数 \(b,c,d\) 让你找到一个 \(a\) 满足 \(a|b - a \&c=d\)

思路

这题我VP的时候真的是唐完了。。。
一开始判少了条件,之后忘了 \(\text{long long}\)\(\text{WA}\)\(8\) 发。
img

首先按位考虑, \(a\) 的每一位应该怎么填。枚举 \(b,c,d\) 每一位填什么,把 \(8\) 种情况全部列举出来特判一下即可。

代码

void solve()
{
    int b,c,d,flag=0;
    cin>>b>>c>>d;
    int ans=0;
    for(int i=0;i<=60;i++)
    {
        int num=1ll<<i;//这里我是真的唐完了,每次都忘记1是一个int类型,要写1ll才行
        int x=b&num,y=c&num,z=d&num;
        if(!x && !y && z) ans|=num;
        if(!x && y && z) return cout<<-1<<endl,void();
        if(x && !y && !z) return cout<<-1<<endl,void();
        if(x && y && !z) ans|=num;
    }
    cout<<ans<<endl;
}

D. Connect the Dots

题意

给你一个长度为 \(n\) 的序列和 \(m\) 次操作。操作如下:

  • 选择三个整数 \(a_i,d_i(1\leq d\leq 10),k_i\)
  • \(a_i,a_i+d_i,a_i+2d_i...a_i+k\cdot d_i\) 添加到同一个集合里。

问你 \(m\) 次操作结束后一共有多少个不同的集合。

思路

注意到, \(d\) 的范围很小,我们可以对于每一个 \(d\) 分别考虑。又因为是区间修改最后才查询,于是我一开始想的是差分,把初始位置和最后位置分别 \(+1\) , \(-1\) ,然后 \(i\)\(i-d\) 转移过来,把这两个位置放到一个集合里。但是这样有一个问题,我无法判断出当前这个区间左端点和前一个区间的右端点是不是在同一个区间里面。于是我发现可以直接记录当前这个点向后最大更新的次数,如果 \(i-d\) 还能继续向后更新,那么就把 \(i\)\(i-d\) 放到同一个集合, \(i\) 更新的次数和 \(i-d\) 更新的次数取个 \(max\) 即可。对于每个 \(d\) 都操作一下,时间复杂度 \(O(10\cdot n)\)

代码

void solve()
{
    int n,m;
    cin>>n>>m;
    vector cha(n+100,vector<int>(11));
    vector<int> fa(n+1);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int a,d,k;
        cin>>a>>d>>k;
        cha[a][d]=max(cha[a][d],k);
    }
    function<int(int)> getfa=[&](int x)
    {
        if(x==fa[x]) return x;
        return fa[x]=getfa(fa[x]);
    };
    auto merge=[&](int x,int y)
    {
        int fx=getfa(x),fy=getfa(y);
        if(fx==fy) return;
        fa[fy]=fx;
    };
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=10;j++)
        {
            if(i-j<0) continue;
            if(cha[i-j][j]) merge(i,i-j);
            cha[i][j]=max(cha[i-j][j]-1,cha[i][j]);
        }
    }
    int cnt=0;
    vector<int> vis(n+1);
    for(int i=1;i<=n;i++) if(!vis[getfa(i)]){vis[getfa(i)]=1,cnt++;}
    cout<<cnt<<endl;
}

E. Expected Power

题意

给你 \(n\) 个数 \(a_i (1\leq a_i\leq 1024)\) ,每个数都有 \(\frac{p_i}{10^4}\) 的概率添加到多集 \(S\) 中。 \(f(S)\) 表示 \(S\) 集合中所有数的异或和,让你求出 \((f(S))^2\) 的期望,结果对 \(10^9+7\) 取模。

思路

一遇到这种异或的问题我就想到拆位,很可惜这题不是。注意到 \(a_i\) 的数据范围很小,我们可以枚举 \(f(S)\) 的值,不会超过 \(1024\) 。然后计算概率,对于每个 \(a_i\)\(p_i\) 的概率选和 \(p_i\) 的概率不选,那么转移方程就出来了 \(dp_{i,j}+=dp_{i-1,j}\cdot (1-p_i)+dp_{i-1,j\oplus a_i}\cdot p_i\)
然后每个 \(i\) 都是从 \(i-1\) 转移而来,可以用滚动数组优化空间。时间复杂度 \(O(1024\cdot n)\)

代码

void solve()
{
    int n,inv=qpow(10000,mod-2);
    cin>>n;
    vector<int> a(n+1),p(n+1),dp(1024);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) cin>>p[i];
    dp[0]=1;
    for(int i=1;i<=n;i++)
    {
        vector<int> g(1024);
        for(int j=0;j<1024;j++)
        {
            g[j]=(g[j]+dp[j]*(10000-p[i])%mod)%mod;
            g[j]=(g[j]+dp[j^a[i]]*p[i]%mod)%mod;
        }
        for(int j=0;j<1024;j++) g[j]=g[j]*inv%mod;
        dp=g;
    }
    int ans=0;
    for(int i=0;i<1024;i++)
    {
        int temp=i*i%mod;
        ans+=temp*dp[i]%mod;
        ans%=mod;
    }
    cout<<ans<<endl;
}
posted @ 2024-10-01 14:56  杨丶老爹  阅读(70)  评论(0编辑  收藏  举报