大抵是NOIP前写的最后一篇题解了吧。。。
flandre 80pts
赛时打的错解A了,然后证伪以后写了个更错的错解80pts;
考虑我们最终要求的答案是 $ a $ 数组从小到大排序后的一个后缀;
考虑怎样证明这个结论,感性理解一下就是尽量选大的然后挺对;
考虑比较严谨的证明;
如果序列中没有重复的元素,那么正确性显而易见;
考虑如果有重复的元素怎么办;
假设现在我们选了一个可重集合 $ A = { x, y, y, y, z } $,其中元素递增给出,首先我们会选 $ x $,因为它最小,然后考虑加 $ y $ 的贡献,不难发现,无论加多少 $ y $,它和加 $ x $ 的贡献是一样的,都是对原序列加了两个可以 $ +k $ 的点对,所以 $ y $ 在选 $ x $ 的前提下是越多越好的(要不就不选);
证毕;
然后上个 $ BIT $ 维护一下前面有多少个比它大的即可,时间复杂度 $ \Theta(n \log V) $,其中 $ V $ 为值域;
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
long long n, k;
struct sss{
long long a;
int id;
bool operator <(const sss &A) const {
return a < A.a;
}
}e[1000005];
vector<int> v;
namespace BIT{
inline int lowbit(int x) {
return x & (-x);
}
int tr[3000005];
inline void add(int pos, int d) {
for (int i = pos; i <= 3000000; i += lowbit(i)) tr[i] += d;
}
inline int ask(int d) {
int ans = 0;
for (int i = d; i; i -= lowbit(i)) ans += tr[i];
return ans;
}
}
int main() {
freopen("flandre.in", "r", stdin);
freopen("flandre.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> e[i].a;
e[i].id = i;
}
sort(e + 1, e + 1 + n);
long long ans = 0, sum = 0, now = 0;
for (int i = n; i >= 1; i--) {
now = now + k * (BIT::ask(3000000) - BIT::ask(e[i].a + 2000000)) + e[i].a;
if (now > ans) {
ans = now;
sum = i;
}
BIT::add(e[i].a + 2000000, 1);
}
if (!sum) {
cout << 0 << ' ' << 0;
return 0;
}
cout << ans << ' ' << n - sum + 1 << '\n';
for (int i = sum; i <= n; i++) cout << e[i].id << ' ';
return 0;
}
meirin 100pts
发现一个式子有四个 $ \sum $,然后直接做不太好做,所以考虑贡献;
这个题的 $ a $ 数组始终不变,所以我们可以从这里入手;
发现我们最终求的是 $ (\sum_{i = l}^{r} a_i) \times (\sum_{i = l}^{r} b_i) $,而 $ a $ 始终不变,所以我们可以尝试维护每个 $ b_i $ 的系数;
设 $ val_i $ 表示 $ b_i $ 的系数;
先不考虑修改,那么考虑一个 $ b_i $ 的系数 $ val_i $ 是:
\[\sum_{r = i}^{n} \sum_{j = i}^{r} a_j + val_{i - 1} - \sum_{l = 1}^{i - 1} \sum_{j = l}^{i - 1} a_j \]简单来讲就是上一个的系数减去不包含这个点的系数(右端点是 $ i - 1 $ )再加上这个点新的系数(左端点是 $ i $ );
发现前面两个 $ \sum $ 可以倒着扫一遍 $ \Theta(n) $ 处理,后面两个 $ \sum $ 可以正着扫一遍 $ \Theta(n) $ 处理,这样我们就得到了每个点的系数;
修改直接用这个区间的系数和乘要加的值然后和原来的答案相加即可,这个可以前缀和处理;
时间复杂度:$ \Theta(n + m) $;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const long long mod = 1000000007;
int n, q;
long long a[500005], b[500005], sum[500005], p[500005], val[500005], su[500005];
int main() {
freopen("meirin.in", "r", stdin);
freopen("meirin.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
sum[n] = a[n];
sum[n] = (sum[n] + mod) % mod;
for (int i = n - 1; i >= 1; i--) {
sum[i] = (sum[i + 1] + (a[i] * (n - (i + 1) + 1) % mod + a[i]) % mod) % mod;
sum[i] = (sum[i] + mod) % mod;
}
p[1] = a[1];
p[1] = (p[1] + mod) % mod;
for (int i = 2; i <= n; i++) {
p[i] = (p[i - 1] + ((a[i] * (i - 1)) % mod + a[i]) % mod) % mod;
p[i] = (p[i] + mod) % mod;
}
val[1] = sum[1];
long long ans = ((val[1] * b[1]) % mod + mod) % mod;
for (int i = 2; i <= n; i++) {
val[i] = ((sum[i] + val[i - 1]) % mod - p[i - 1]) % mod;
val[i] = (val[i] + mod) % mod;
ans = (ans + (val[i] * b[i]) % mod + mod) % mod;
}
for (int i = 1; i <= n; i++) {
su[i] = (su[i - 1] + val[i]) % mod;
}
int l, r;
long long k;
for (int i = 1; i <= q; i++) {
cin >> l >> r;
cin >> k;
long long now = ((su[r] - su[l - 1] + mod) % mod + mod) % mod;
ans = (ans + (now * k) % mod + mod) % mod;
cout << ans << '\n';
}
return 0;
}
sakuya 15pts
这种题有个套路:统计边被经过的次数;
钦定 $ 1 $ 为根;
设 $ f_{x} $ 表示 $ x $ 子树内重要点的个数,这个容易求出;
考虑一条边 $ (x, u), fa_u = x $,它的经过次数为 $ f_u \times (f_1 - f_u) \times 2 \times (m - 1)! $;
其中 $ \times 2 $ 是因为原序列有序, $ \times (m - 1)! $ 可以理解为将这两个点绑一起做个全排;
然后就得到了每条边被经过的次数,这个可以 $ DFS $ 求出,顺便也求出了初始答案;
考虑修改,发现修改只是将与一个点相连的所有边的边权 $ +k $,所以我们再开一个数组 $ a_x $ 表示与 $ x $ 这个点相连的边的经过次数之和,然后答案直接加 $ a_x \times k $ 即可;
最后不要忘了除以总方案数 $ m! $,因为求的是期望;
时间复杂度:$ \Theta(n) $;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
#define int long long
const long long mod = 998244353;
int n, m, q;
struct sss{
int t, ne;
long long w;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v, long long ww) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
e[cnt].w = ww;
}
int f[500005];
long long a[500005], ans, inv, val;
bool vis[500005];
void afs(int x, int fa) {
if (vis[x]) f[x] = 1;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa) continue;
afs(u, x);
f[x] += f[u];
}
}
void dfs(int x, int fa) {
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa) continue;
dfs(u, x);
a[x] = (a[x] + f[u] * (f[1] - f[u]) % mod * 2 % mod * val % mod) % mod;
a[u] = (a[u] + f[u] * (f[1] - f[u]) % mod * 2 % mod * val % mod) % mod;
ans = (ans + f[u] * (f[1] - f[u]) % mod * 2 % mod * val % mod * e[i].w % mod) % mod;
}
}
long long ksm(long long a, long long b) {
long long ans = 1;
while(b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
signed main() {
freopen("sakuya.in", "r", stdin);
freopen("sakuya.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
int x, y;
long long w;
for (int i = 1; i <= n - 1; i++) {
cin >> x >> y;
cin >> w;
add(x, y, w);
add(y, x, w);
}
for (int i = 1; i <= m; i++) {
cin >> x;
vis[x] = true;
}
val = 1;
for (int i = 2; i <= m - 1; i++) val = val * i % mod;
inv = val * m % mod;
inv = ksm(inv, mod - 2);
afs(1, 0);
dfs(1, 0);
cin >> q;
long long k;
for (int i = 1; i <= q; i++) {
cin >> x >> k;
ans = (ans + a[x] * k % mod) % mod;
cout << ans * inv % mod << '\n';
}
return 0;
}
红楼 ~ Eastern Dream 65pts
赛时没写正解,一是不会,二是写出来常数也大,800ms很难卡过去;
看见部分分有 $ x $ 小和 $ x $ 大的情况,于是考虑根号分治;
对于 $ x \leq \sqrt n $ 的情况,我们开一个数组 $ f_{i, j} $ 表示除以 $ i $ 余数 $ \leq j $ 的增加量,修改直接 $ \Theta(\sqrt n) $ 暴改,查询考虑 $ \Theta(\sqrt n) $ 遍历每个 $ i $,然后统计这个区间有多少整块(长度为 $ i $ ),然后两边的散块 $ \Theta(1) $ 求即可;
对于 $ x > \sqrt n $ 的情况,我们发现加的区间一共不超过 $ \Theta(\sqrt n) $ 个,所以我们考虑 $ \Theta(\sqrt n) $ 的区间加和查询;
这个不能套线段树做,对于区间加,我们可以想到差分,考虑树状数组区间修改区间查询的做法,我们也可以维护一个差分数组 $ c_i $;
然后类似树状数组,我们维护 $ c_i, c_i \times i $,然后对于一个到 $ r $ 的前缀和我们直接 $ (r + 1) \times \sum_{i = 1}^{r} c_i - \sum_{i = 1}^{r} c_i \times i $ 即可;
对于 $ (r + 1) \times \sum_{i = 1}^{r} c_i - \sum_{i = 1}^{r} c_i \times i $ 这个式子,考虑我们求的是 $ \sum_{i = 1}^{r} \sum_{j = 1}^{i} c_j $,然后考虑每个 $ c_j $ 只会加 $ r - j + 1 $ 次,提出 $ r + 1 $ 即可得到这个式子;
然后我们修改时直接暴力 $ \Theta(\sqrt n) $ 遍历所有加的区间进行上述差分数组的维护,查询时直接 $ \Theta(\sqrt n) $ 遍历到 $ l $ 的以及到 $ r $ 的块和散块即可解决这个问题,
时间复杂度:$ \Theta(n \sqrt n) $,常数较大,用了CuFeO4的快读快写;
点击查看代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include<bits/stdc++.h>
using namespace std;
namespace IO{
struct IO{
char buf[1<<16],*p1,*p2;
char pbuf[1<<16], *pp = pbuf;
#define gc() ((p1==p2&&(p2=((p1=buf)+fread_unlocked(buf,1,1<<16,stdin)),p1==p2))?EOF:*p1++)
#define pc putchar_unlocked
template<class T>
inline void read(T &x){
x = 0;bool flag = false;char s = gc();
for(;s < '0' || '9' < s;s = gc());
for(;'0' <= s && s <= '9';s = gc()) x = (x<<1)+(x<<3)+(s^48);
}
template<class T,class ...Args>
inline void read(T &x,Args&... argc){read(x);read(argc...);}
template <class T>
inline void write(T x) {
static int sta[30],top = 0;
do{sta[top++] = x % 10, x /= 10;}while(x);
while(top) pc(sta[--top] + '0');
}
inline void write(char x){pc(x);}
template <class T,class... Args>
inline void write(T x,Args... argc) {write(x);write(argc...);}
}io;
#define read io.read
#define write io.write
}using namespace IO;
int n, m;
long long f[455][455], a[500005], sum[500005];
int sq;
int st[455], ed[455], belog[500005];
long long c[500005], ci[500005], sc[455], sci[455];
inline long long w(long long l, long long r) {
long long ans = sum[r];
if (l > 0) ans -= sum[l - 1];
for (int i = 1; i <= sq; i++) {
long long L = l + (i - 1 - (l % i) + 1);
if (l % i == 0) L = l;
long long R = r - (r % i) - 1;
if (r % i == i - 1) R = r;
if (L > R) {
if ((r % i) - (l % i) == r - l) {
ans += f[i][r % i];
if ((l % i) > 0) ans -= f[i][(l % i) - 1];
} else {
ans += f[i][r % i];
ans += f[i][i - 1];
if ((l % i) > 0) ans -= f[i][(l % i) - 1];
}
continue;
}
ans += (R - L + 1) / i * f[i][i - 1];
if (R != r) ans += f[i][r % i];
if (L != l) ans += f[i][i - 1];
if ((l % i) > 0 && L != l) ans -= f[i][(l % i) - 1];
}
return ans;
}
int main() {
freopen("scarlet.in", "r", stdin);
freopen("scarlet.out", "w", stdout);
read(n, m);
for (int i = 0; i < n; i++) {
read(a[i]);
sum[i] = sum[max(0, i - 1)] + a[i];
}
sq = sqrt(n);
for (int i = 1; i <= sq; i++) {
st[i] = (i - 1) * sq + 1;
ed[i] = i * sq;
}
ed[sq] = n;
for (int i = 1; i <= sq; i++) {
for (int j = st[i]; j <= ed[i]; j++) {
belog[j] = i;
}
}
long long s, x, y, k;
for (int i = 1; i <= m; i++) {
read(s, x, y);
if (s == 1) {
read(k);
if (x <= sq) {
y = min(y, x - 1);
for (int j = 0; j <= y; j++) {
f[x][j] += (j + 1) * k;
}
for (int j = y + 1; j <= x - 1; j++) {
f[x][j] += (y + 1) * k;
}
} else {
y = min(y, x - 1);
for (int j = 0; j <= n - 1; j += x) {
c[j + 1] += k;
if (j + 1 + y + 1 <= n) c[j + 1 + y + 1] -= k;
ci[j + 1] += (j + 1) * k;
if (j + 1 + y + 1 <= n) ci[j + 1 + y + 1] -= (j + 1 + y + 1) * k;
sc[belog[j + 1]] += k;
sci[belog[j + 1]] += (j + 1) * k;
if (j + 1 + y + 1 <= n) sc[belog[j + 1 + y + 1]] -= k;
if (j + 1 + y + 1 <= n) sci[belog[j + 1 + y + 1]] -= (j + 1 + y + 1) * k;
}
}
} else {
long long ans = w(x - 1, y - 1);
long long sumc = 0, sumci = 0;
for (int j = 1; j <= belog[x] - 1; j++) {
sumc += sc[j];
sumci += sci[j];
}
for (int j = st[belog[x]]; j <= x - 1; j++) {
sumc += c[j];
sumci += ci[j];
}
ans -= (1ll * x * sumc - sumci);
for (int j = x; j <= min(y, 1ll * ed[belog[x]]); j++) {
sumc += c[j];
sumci += ci[j];
}
for (int j = belog[x] + 1; j <= belog[y] - 1; j++) {
sumc += sc[j];
sumci += sci[j];
}
if (belog[x] != belog[y]) {
for (int j = st[belog[y]]; j <= y; j++) {
sumc += c[j];
sumci += ci[j];
}
}
ans += (1ll * (y + 1) * sumc - sumci);
write(ans,'\n');
}
}
return 0;
}