首页 > 其他分享 >[赛记] NOIP2024加赛8

[赛记] NOIP2024加赛8

时间:2024-11-28 17:24:03浏览次数:6  
标签:赛记 int sum long ans 加赛 500005 NOIP2024 mod

大抵是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;
}

标签:赛记,int,sum,long,ans,加赛,500005,NOIP2024,mod
From: https://www.cnblogs.com/PeppaEvenPig/p/18574672

相关文章

  • NOIP2024 前集训:NOIP2024加赛 8
    前言music《铃芽之旅》dudududududududududududududududududududududududududududududududududududududududududududududududududududududududududududududududu于你的生命之中红与蓝的线缠绕交错......
  • NOIP2024
    DAY-3早上模拟赛人机不写。下午和晚上一直在复习yyc的组合问题。晚上vp了PublicNOIP#7,只会A,B。我的\(\rmLinux\)下环境配置:\(\rm.vimrc\):ignoremap[[]<LEFT>ignoremap(()<LEFT>ignoremap{{}<LEFT>ignoremap"""<LEFT>\(\rmg+\):......
  • NOIP2024 游记
    Porlosmomentosdifíciles,yaentiendoquelaflormásbellaserásiempreparamí.Lydia-F.I.R飞儿乐团Porlosmomentosdifícilesyaentiendoquelaflormásbellaserásiempreparamí.Lydia迷离的眼眶为何流浪心碎的海洋受了伤连微笑都彷......
  • 『模拟赛』NOIP2024加赛8
    Rank唐A.flandre签。比较显然,由于\(k\ge0\),所以最终的序列一定为不降序列。首先将原序列升序排序,当任取一个子序列时,容易发现最后一个数越大一定越优,那么最后一个数取到最大值时,倒数第二个数一定越大越优,以此类推,最终取出的序列一定是原序列的一个后缀。我们倒序枚举逐......
  • NOIP2024加赛8
    NOIP2024加赛8T1flandre第4个样例没给全,说明这可以直接猜结论首先我们假设选定了$x$个数,那么我们肯定是把他们从小到大排好序依次放,这样才能使整体效果最大。然后我们考虑怎么选这些数。首先正的肯定都要,然后就是负的,然后你就猜排好序后选择的区间一定是连续的。证明:......
  • NOIP2024加赛8
    NOIP2024加赛8前言挂分历程开T1,T1这么水?20分钟写+调直接交。开T2,T2这么水?10分钟读题+拆贡献。哎,好像可以\(O(n)\)?哎,好像可以\(O(n\logn)\)?一小时调完T2。哎,大样例跑3s,卡常。卡了2h,没卡过。开T4,直接把T2粘过来,半小时调过小样例。我去,我怎么没开T3。开T3,......
  • NOIP2024加赛8
    NOIP2024加赛8题目来源:2023NOIPA层联测32\(T1\)HZTG5781.flandre\(100pts\)先将\(\{a\}\)升序排序并去重后,由调整法可知选取的数一定是一段后缀。正数的贡献肯定是无脑全加上,难点在于负数中多次出现的数的选择。不妨钦定答案序列中选择的最小的数,通过需要加......
  • NOIP2024 加赛 8
    骗你的,没写。不过这场分比较高,前三道切的都挺顺,T4也拿了暴力分。T3和题解的处理办法不太一样,具体就是没有统计每条边的贡献,树上DP求的是子树内的答案,处理修改的时候也不一样。就挂个代码吧。#include<bits/stdc++.h>usingnamespacestd;#defineintlonglongusing......
  • [75] (NOIP集训) NOIP2024 加赛 8
    A.flandre我的做法是,所有数离散化之后扔进桶里,去枚举选择\([i,+\infty)\)内的数的贡献,在所有的\(i\)里取一个最大值作为答案lbtl指出可能存在最优答案是选择\([i+1,+\infty)\)内的所有数与值为\(i\)的部分数的数据和lbtl交涉后尝试构造一组相同元素只选后一半的数据......
  • NOIP2024加赛8
    状态很不好,恼了。虚拟机太卡了,根本交不上去。flandre发现选取的肯定是从大到小排序后的一个后缀,然后就做完了,时间复杂度\(O(n\logn)\)。点此查看代码#include<bits/stdc++.h>usingnamespacestd;#definerep(i,s,t,p)for(inti=s;i<=t;i+=p)#definedrep(i,s,t,p......