[赛记] NOIP2024加赛8

大抵是NOIP前写的最后一篇题解了吧。。。

flandre 80pts

赛时打的错解A了,然后证伪以后写了个更错的错解80pts;

考虑我们最终要求的答案是 a 数组从小到大排序后的一个后缀

考虑怎样证明这个结论,感性理解一下就是尽量选大的然后挺对;

考虑比较严谨的证明

如果序列中没有重复的元素,那么正确性显而易见;

考虑如果有重复的元素怎么办;

假设现在我们选了一个可重集合 A=x,y,y,y,z,其中元素递增给出,首先我们会选 x,因为它最小,然后考虑加 y 的贡献,不难发现,无论加多少 y,它和加 x 的贡献是一样的,都是对原序列加了两个可以 +k 的点对,所以 y 在选 x 的前提下是越多越好的(要不就不选);

证毕;

然后上个 BIT 维护一下前面有多少个比它大的即可,时间复杂度 Θ(nlogV),其中 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

发现一个式子有四个 ,然后直接做不太好做,所以考虑贡献

这个题的 a 数组始终不变,所以我们可以从这里入手;

发现我们最终求的是 (i=lrai)×(i=lrbi),而 a 始终不变,所以我们可以尝试维护每个 bi系数

vali 表示 bi 的系数;

先不考虑修改,那么考虑一个 bi 的系数 vali 是:

r=inj=iraj+vali1l=1i1j=li1aj

简单来讲就是上一个的系数减去不包含这个点的系数(右端点是 i1 )再加上这个点新的系数(左端点是 i );

发现前面两个 可以倒着扫一遍 Θ(n) 处理,后面两个 可以正着扫一遍 Θ(n) 处理,这样我们就得到了每个点的系数;

修改直接用这个区间的系数和乘要加的值然后和原来的答案相加即可,这个可以前缀和处理;

时间复杂度:Θ(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 为根;

fx 表示 x 子树内重要点的个数,这个容易求出;

考虑一条边 (x,u),fau=x,它的经过次数为 fu×(f1fu)×2×(m1)!

其中 ×2 是因为原序列有序, ×(m1)! 可以理解为将这两个点绑一起做个全排;

然后就得到了每条边被经过的次数,这个可以 DFS 求出,顺便也求出了初始答案;

考虑修改,发现修改只是将与一个点相连的所有边的边权 +k,所以我们再开一个数组 ax 表示与 x 这个点相连的边的经过次数之和,然后答案直接加 ax×k 即可;

最后不要忘了除以总方案数 m!,因为求的是期望;

时间复杂度:Θ(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 大的情况,于是考虑根号分治

对于 xn 的情况,我们开一个数组 fi,j 表示除以 i 余数 j增加量,修改直接 Θ(n) 暴改,查询考虑 Θ(n) 遍历每个 i,然后统计这个区间有多少整块(长度为 i ),然后两边的散块 Θ(1) 求即可;

对于 x>n 的情况,我们发现加的区间一共不超过 Θ(n) 个,所以我们考虑 Θ(n) 的区间加和查询;

这个不能套线段树做,对于区间加,我们可以想到差分,考虑树状数组区间修改区间查询的做法,我们也可以维护一个差分数组 ci

然后类似树状数组,我们维护 ci,ci×i,然后对于一个到 r 的前缀和我们直接 (r+1)×i=1rcii=1rci×i 即可;

对于 (r+1)×i=1rcii=1rci×i 这个式子,考虑我们求的是 i=1rj=1icj,然后考虑每个 cj 只会加 rj+1 次,提出 r+1 即可得到这个式子;

然后我们修改时直接暴力 Θ(n) 遍历所有加的区间进行上述差分数组的维护,查询时直接 Θ(n) 遍历到 l 的以及到 r 的块和散块即可解决这个问题,

时间复杂度:Θ(nn),常数较大,用了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;
}
posted @   Peppa_Even_Pig  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示