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\) 最小化 \(\sum\limits_{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\n", ans);
return;
}
*F - The Sum of the k-th Powers
题意:求[1,n]的正整数的k次方和。1<=n<=1e9,0<=k<=1e6。即 \(\sum\limits_{i=1}^{n}i^k\) 。
题解:易知k次方和是一个k+1次多项式,求出[1,k+2]个点,可以 \(O(k^2)\) 插值出k+1次多项式。假如选出的点的横坐标是连续整数,可以 \(O(k)\) 插值出k+1次多项式。
详见:拉格朗日插值
拉格朗日插值: \(f(x)=\sum\limits_{i=1}^{n}y_i\frac{\prod\limits_{j=1,j\neq i}^{n}x_j-x}{\prod\limits_{j=1,j\neq 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\n", lagrange(k + 1, x + 1, y + 1, n));
}