Codeforces Round 911 (Div. 2) 总结

第一次在赛场上敲莫反,还好最后调出来了!

A

题意:你在Minecraft里挖了一些一格的坑(同一列),问你用几桶水可以填满它(可以造无限水)。
解法:找大于 \(2\) 的连续段,有的话就是两桶,没有的话挖了几次就是几桶。

B

题意:\(a,b,c\) 三个数,每次操作可以选定其中一个数加一,另外两个数各自减一。问对于每一个数,能否通过若干次操作仅使它变为 \(0\),其他两个数不为 \(0\)
解法:
通过手玩发现一些性质:
1、可以将每个数都变成小于等于 \(2\) 的非 \(0\) 数。
2、一次操作所有数的奇偶性发生改变。
所以利用性质1可以知道我们只要将所有数化为小于等于 \(2\) 的数,讨论 \(1\) 的个数即可。
由性质2可知只要对所有数模 \(2\)\(1\)即可得到它们对应的小于等于 \(2\) 的数。
再手玩一下只考虑只有 \(2\)\(1\) 的答案:
221:001
211:100
111:111(222和111等价)
所以结论是当三个数奇偶性相同时全部可以满足题意,奇偶性不同时只有独特的那个数符合题意。
结论为什么是对的?
充分性在上面的过程中已经体现。
必要性(符合题意的有且仅有奇偶性独特的那个数)证明可以考虑性质2,奇偶性独特的那个数成为 \(0\) 时另外两数必不为 \(0\)
(所以这题为什么只有900分,是结论比较好猜吗?)

C

题意:给出一棵二叉树,每个节点上有标记LRU分别表示走向左儿子、走向右儿子、走向父亲。一个人从根出发,按每个节点的标记走。现在要修改最少的标记,使得他能经过某个叶子节点。
解法:可以考虑走到节点x所需的更改次数 \(f[x]\) 。很容易从父节点转移到子节点,标记所指的那个儿子继承 \(f[x]\),另一个赋值为 \(f[x]+1\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,MOD=998244353;
char p[N];
int ls[N],rs[N],res[N],ans;
void dfs(int x)
{
    if(!ls[x]&&!rs[x])
    {
        ans=min(ans,res[x]);
        return;
    }
    if(ls[x])
    {
        res[ls[x]]=res[x]+(p[x]!='L');
        dfs(ls[x]);
    }
    if(rs[x])
    {
        res[rs[x]]=res[x]+(p[x]!='R');
        dfs(rs[x]);
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T;cin>>T;
    while(T--)
    {
        int n;cin>>n;
        cin>>(p+1);
        for(int i=1;i<=n;i++)
        {
            cin>>ls[i]>>rs[i];
        }
        ans=1e9;
        dfs(1);
        cout<<ans<<endl;

        for(int i=0;i<=n;i++) p[i]=ls[i]=rs[i]=res[i]=0;
    }
}

D

题意:给定数组 \(a\) ,求:

\[\sum_{i=1}^{n}\sum_{j=i+1}^{n}\sum_{k=j+1}^{n} f(a_i,a_j,a_k) \]

定义 \(f(x,y,z)\) 表示 \(x,y,z\)中较小的两个数的最大公约数。

解法:
如果去掉对 \(k\) 的那个 \(\Sigma\) 的话就变成莫反裸题了,因此这个题大概率也可以用莫反解决。

假设数组a中的数两两不同。
首先考虑将数组 \(a\) 放进桶里,柿子可以转化为:

\[\sum_{i=1}^{m}\sum_{j=i+1}^{m}\gcd(i,j)\cdot t[i]\cdot t[j]\cdot count(j+1,m) \]

\(m\)\(a\) 中出现的最大值,数组 \(t\) 是桶,\(count\) 用于统计区间内 \(a_i\) 出现次数。
运用一下莫反:

\[\sum_{d=1}^{m}d\sum_{k=1}^{\lfloor\frac{m}{d}\rfloor}\mu(k)\sum_{i=1}^{\lfloor\frac{m}{kd}\rfloor}t[ikd]\sum_{j=i+1}^{\lfloor\frac{m}{kd}\rfloor} t[jkd]\cdot count(jkd+1,m) \]

对于后面一坨柿子可以看到 \(kd\) 是一个整体,我们可以通过枚举 \(kd\) ,再枚举倍数预处理出来,时间复杂度是调和级数约等于一只log。
但上面考虑的是数组 \(a\) 不存在两两相等的元素。
接下来还要考虑枚举到的三个数 \(x\leq y\leq z\) 的相等关系:
1、\(x=y=z\) :
\(\sum_{i=1}^{m}i\cdot\text{C}_{t[i]}^{3}\) 直接另外处理即可。

2、\(x=y<z\)
\(\sum_{i=1}^{m}i\cdot\text{C}_{t[i]}^{2}\cdot count(i+1,m)\) 和1一起处理。

3、\(x<y=z\) :
在预处理 \(kd\) 的倍数中,每一次枚举到 \(jkd\) 都会对答案产生额外贡献 \(t[ikd]\cdot \text{C}_{t[jkd]}^{2}\)
全部的柿子应改为:

\[\sum_{d=1}^{m}d\sum_{k=1}^{\lfloor\frac{m}{d}\rfloor}\mu(k)\sum_{i=1}^{\lfloor\frac{m}{kd}\rfloor}t[ikd] \sum_{j=i+1}^{\lfloor\frac{m}{kd}\rfloor} (t[jkd]\cdot count(jkd+1,m)+\text{C}_{t[jkd]}^{2}) \]

然后就可以写代码了,预处理部分可以从大到小枚举,用类似后缀和的方式存储与 \(j\) 相关的值。
整体包含两次枚举倍数,时间复杂度大约是一只log。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,MOD=998244353;
int mu[N],cnt,vis[N];
vector<int> prime;
void init()
{
    mu[1]=1;
    for(int i=2;i<N;i++)
    {
        if(!vis[i]) prime.push_back(i),mu[i]=-1;
        for(int j=0;i*prime[j]<N&&j<prime.size();j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                mu[i*prime[j]]=0;
                break;
            }
            else mu[i*prime[j]]=-mu[i];
        }
    }
}
int t[N],p[N],q[N],A[N],res[N];
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    init();
    int T;cin>>T;
    while(T--)
    {
        int n,m=0;cin>>n;
        for(int i=1;i<=n;i++) cin>>A[i],t[A[i]]++,m=max(m,A[i]);
        sort(A+1,A+n+1);
        for(int i=1,pos=1;i<=n;i++) 
        {
            while(pos<=n&&A[pos]==A[i]) pos++;
            p[A[i]]+=n-pos+1;
        }
        for(int kd=1;kd<=m;kd++)
        {
            int sum=0;
            for(int i=m/kd;i;i--)
            {
                res[kd]+=sum*t[i*kd];
                sum+=p[i*kd]+t[i*kd]*(t[i*kd]-1)/2;
            }
        }
        int ans=0;
        for(int k=1;k<=m;k++)
            for(int d=1;d*k<=m;d++)
            {
                ans+=mu[d]*k*res[k*d];
            }
        int num=0;
        for(int i=m;i;i--)
        {
            if(t[i])
            {
                ans+=t[i]*(t[i]-1)/2*num*i;
                ans+=t[i]*(t[i]-1)*(t[i]-2)/6*i;
                num+=t[i];
            }
        }
        cout<<ans<<endl;

        for(int i=0;i<=m;i++) res[i]=p[i]=t[i]=q[i]=0;
        for(int i=0;i<=n;i++) A[i]=0;
    }
}

E

缩点模板题,困了,先咕着(

F

还不会,先咕着(

总结

成功切掉了D(?),所以上了一点点分,还行,但D调的还是太久了。

posted @ 2023-12-05 23:13  napnah  阅读(37)  评论(0)    收藏  举报