Educational Codeforces Round 7

题目链接:https://codeforces.com/contest/622

A - Infinite Sequence

签到题

B - The Time

签到题

C - Not Equal on a Segment

题意:给一个n个数的数组,然后若干次询问,每次询问一个(l,r,x),问区间[l,r]中有没有不等于x的数,假如有,输出任意一个。

题解:反过来dp,每个数维护“下一个与其不等或者是数组结尾”的数的位置,那么先看a[l]是不是满足a[l]!=x,若是,则输出,否则a[nxt[l]]就是最近的下一个不是x的位置,这个位置假如超过r,就不存在,否则这个位置是其中一种解。

D - Optimal Number Permutation

很有意思的一个构造。

题意:给一个2n个数的数组,由两个n个数的排列打乱而成。显然[1,n]每个数出现两次,第 (i) 个数两次出现的位置之间的差定义为 (d_i) 最小化 (sumlimits_{i=1}^{n}(n-i)|d_i+i-n|)

题解:要使得上式最小,是要通过调整每个数两次出现的间隔,构造了好几种以1开头的,发现都可以使得和为0,但是没什么规律:

n=8
1x2x3x41234xxxxx
1626384123485775

构造的思路是使得中间一段是从1开始连续自然数,但是这样不知道具体的规律是什么。

想了很久,突然想试试反过来,从大到小构造,那么

n=8
1357753182468642

非常有规律,显然这样构造也会使得和为0。

具体实现的时候,用两个deque从大到小两头push,然后再for一遍输出。假如要复杂度严格线性的算法,可以直接1357...这样输出。

注意是n-1那个中间不需要加东西,n-2那个中间放个n。

*E - Ants in Leaves

题意:给一棵树,每个叶子有一只蚂蚁,蚂蚁都往根节点爬,但是除了根节点以外的所有节点同时只能容纳1只蚂蚁。求最短要多少时间才能全部爬到根节点。

题解:根节点很特殊,那么就先去掉根节点,剩下的一片森林中,每棵树的树根也是每次只能完成1个单位的吞吐,和其他节点相同。然后一个大胆的观察出现了:对该子树下的所有的叶子按深度从小到大排序,然后按这些深度逐个吐出叶子上面的蚂蚁。然后赋值对于i>=2,赋值dep[i]=max(dep[i], dep[i-1]+1),恰好就把整个序列离散化完成了。答案就是离散化之后的各个子树的dep[n]的最大值+1。

const int MAXN = 5e5;
vector<int> G[MAXN + 5];
int dep[MAXN + 5], top;

void dfs(int u, int p, int d) {
    if(G[u].size() == 1)
        dep[++top] = d;
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs(v, u, d + 1);
    }
}

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n - 1; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }

    int ans = 0;
    for(auto &v : G[1]) {
        top = 0;
        dfs(v, 1, 0);
        sort(dep + 1, dep + 1 + top);
        for(int i = 2; i <= top; ++i)
            dep[i] = max(dep[i], dep[i - 1] + 1);
        ans = max(ans, dep[top] + 1);
    }
    printf("%d
", ans);
    return;
}

*F - The Sum of the k-th Powers

题意:求[1,n]的正整数的k次方和。1<=n<=1e9,0<=k<=1e6。即 (sumlimits_{i=1}^{n}i^k)

题解:易知k次方和是一个k+1次多项式,求出[1,k+2]个点,可以 (O(k^2)) 插值出k+1次多项式。假如选出的点的横坐标是连续整数,可以 (O(k)) 插值出k+1次多项式。

详见:拉格朗日插值

拉格朗日插值: (f(x)=sumlimits_{i=1}^{n}y_ifrac{prodlimits_{j=1,j eq i}^{n}x_j-x}{prodlimits_{j=1,j eq i}^{n}x_j-x_i})

这个模板传入的参数:需要插值n次多项式,传入x[0]~x[n]共n+1个点(*x就是&x[0]),y同理,求出横坐标为xi的函数值。

先线性预处理出差的前缀积和后缀积(其实前缀积可以不用存下来,转移的时候直接乘上去就可以,但是后缀积建议存下来,因为不一定可能通过好的办法求出乘法逆元(甚至乘法逆元不存在)),然后就可以直接求出分子的值。分母必定是两个阶乘的乘积,而且其正负号相间。

例如,假设这一段连续的正整数为:

([1,2,3,4,5,6])

那么分母的值分别是:

((2-1)(3-1)(4-1)(5-1)(6-1)=+0!5!)
((1-2)(3-2)(4-2)(5-2)(6-2)=-1!4!)
((1-3)(2-3)(4-3)(5-3)(6-3)=+2!3!)
((1-4)(2-4)(3-4)(5-4)(6-4)=-3!2!)
((1-5)(2-5)(3-5)(4-5)(6-5)=+4!5!)
((1-6)(2-6)(3-6)(4-6)(5-6)=-5!0!)

以此类推。

ll qpow(ll x, ll n) {
    ll res = 1;
    while(n) {
        if(n & 1)
            res = res * x % MOD;
        x = x * x % MOD;
        n >>= 1;
    }
    return res;
}
 
ll x[1000005], y[1000005];
ll s1[1000005], s2[1000005];
ll ifac[1000005];
 
ll lagrange(int n, ll *x, ll *y, ll xi) {
    ll ans = 0;
    s1[0] = (xi - x[0]) % MOD, s2[n + 1] = 1;
    for (int i = 1; i <= n; i++)
        s1[i] = 1ll * s1[i - 1] * (xi - x[i]) % MOD;
    for (int i = n; i >= 0; i--)
        s2[i] = 1ll * s2[i + 1] * (xi - x[i]) % MOD;
    ifac[0] = ifac[1] = 1;
    for (int i = 2; i <= n; i++)
        ifac[i] = -1ll * MOD / i * ifac[MOD % i] % MOD;
    for (int i = 2; i <= n; i++)
        ifac[i] = 1ll * ifac[i] * ifac[i - 1] % MOD;
    for (int i = 0; i <= n; i++) {
        (ans += 1ll * y[i] * (i == 0 ? 1 : s1[i - 1]) % MOD * s2[i + 1] % MOD
                * ifac[i] % MOD * (((n - i) & 1) ? -1 : 1) * ifac[n - i] % MOD) %= MOD;
    }
    return (ans + MOD) % MOD;
}
 
void test_case() {
    ll n, k;
    scanf("%lld%lld", &n, &k);
    for(int i = 1; i <= k + 2; ++i) {
        x[i] = i;
        y[i] = qpow(i, k);
        y[i] = (y[i] + y[i - 1]) % MOD;
    }
    printf("%lld
", lagrange(k + 1, x + 1, y + 1, n));
}
原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12529360.html