// // // // // // // // // // // // // //

七月腾飞营结业考试 题解报告

七月腾飞营结业考试 题解报告

前言

挂分挂的一塌糊涂

得分: \(25 + 0 + 5 + 16 = 46\)

评测: \(RE + CE + TLE + RE\)

于是就没事了

题解

\(T1\) K 子琪

统一每一条直线上面点的个数 通过组合数计算

先将每个点的横纵坐标减一 再同时除以 \(\gcd\) 将贡献累积到 \(\gcd = 1\) 的位置 统计个数

需要同时求大量的 \(\gcd\) 通过递推预处理

头一次知道 \(\gcd\) 还可以递推算

for(int i = 0; i ^ n; i++)
    for(int j = 0; j ^ m; j++)
        if(!i || !j) _g[i][j] = i | j;
        else _g[i][j] = i > j ? _g[i - j][j] : _g[i][j - i];

组合数的计算需要预处理阶乘以及阶乘逆元

也是头一次知道这玩意也可以递推算(虽然我写的 \(O(n \log mod)\) 的快速幂就是了

for(int i = 1; i ^ n + 1; i++) fac[i] = fac[i - 1] * i % mod;
inv[n] = power(fac[n], mod - 2);
for(int i = n; i; i--) inv[i - 1] = inv[i] * i % mod;

再特判一下 \(k = 1\) 的情况就可以做了

代码

/*
  Source: K子棋
  思路很精妙 坐标减一 除以gcd统计一条直线上点的个数
  gcd 通过递推求出 预处理阶乘以及逆元算组合数
  特判 k = 1 
*/
#include<cstdio>
#include<cstring>
#include<map>
#define pt putchar(' ')
#define pn putchar('\n')
#define int long long
#define Max(x, y) ((x) > (y) ? (x) : (y))
/*-----------------------------------------------------------------------*/
const int B = 5e3 + 7;
const int mod = 998244353;
const int INF = 0x3f3f3f3f;
/*-----------------------------------------------------------------------*/
void File() {
	freopen("connectk.in", "r", stdin);
	freopen("connectk.out", "w", stdout);
}
/*-----------------------------------------------------------------------*/
int n, m, K, A, a, b, p, l, fac[B], inv[B], ans, kcnt, _g[B][B], mp[B][B];
/*-----------------------------------------------------------------------*/
inline int read() {
	int x = 0, f = 0; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = 1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return f ? -x : x;
}
void print(int x) {if(x > 9) print(x / 10); putchar(x % 10 ^ 48);}
void Print(int x) {if(x < 0) putchar('-'), x = -x; print(x);}
/*-----------------------------------------------------------------------*/
int power(int _a, int _b) {int res = 1; for(; _b; _a = _a * _a % mod, _b >>= 1) if(_b & 1) res = res * _a % mod; return res;}
void print_mp() {
	for(int i = 0; i ^ n; i++) {for(int j = 0; j ^ m; j++) Print(mp[i][j]), pt; pn;}
}
int C(int n, int m) {if(!n || !m || n == m) return 1; return fac[n] * inv[m] % mod * inv[n - m] % mod;}
/*-----------------------------------------------------------------------*/
signed main() {
	File();
	n = read(); m = read(); K = read(); A = read(); a = read(); b = read(); p = read(); l = read();
	fac[0] = 1; a %= p; A %= p; b %= p;
	for(int i = 0; i ^ n; i++) for(int j = 0; j ^ m; j++) a = (A * a % p + b) % p, mp[i][j] = a <= l;
	if(K == 1) {
		kcnt = 0;
		for(int i = 0; i ^ n; i++) for(int j = 0; j ^ m; j++) kcnt = (kcnt + mp[i][j]) % mod;
		Print(kcnt); return 0;
	}
	for(int i = 1; i ^ Max(n, m) + 1; i++) fac[i] = fac[i - 1] * i % mod, inv[i] = power(fac[i], mod - 2);
	for(int i = 0; i ^ n; i++) for(int j = 0; j ^ m; j++) if(!i || !j) _g[i][j] = i | j; else _g[i][j] = i > j ? _g[i - j][j] : _g[i][j - i];
	for(int i = n - 1, g; ~i; i--) for(int j = m - 1; ~j; j--) if((i || j) && _g[i][j] != 1) g = _g[i][j], mp[i / g][j / g] += mp[i][j], mp[i][j] = 0;
	for(int i = 0; i ^ n; i++) for(int j = 0; j ^ m; j++) if(_g[i][j] == 1 && mp[0][0] + mp[i][j] >= K) ans = (ans + C(mp[0][0] + mp[i][j], K)) % mod;
	Print(ans);
	return 0;
}

据说这道题连图都不用存 可以直接枚举直线 但是我并不会


\(T2\) 联邦

这个题本来我是想放到第四题的, 但是我想告诉你们, 题目不一定是先易后难的. —— hzk

还没改

先去学圆方树

缩点成圆方树 之后在树上做 \(dp\)


\(T3\) 能源供应

可以发现 对于任意一天 当是否开火确定以后 这一天的最优花费是固定的 可以直接算

  1. 如果火炉开着

    哪怕不用 同样需要有 \(D\) 的代价 所以不用白不用

    1. 当 \(K \geq v_i\) 时

      我们尽量少的使用火炉 但是还是要充分利用 \(D\) 的花费

      \[\lfloor \frac DK \rfloor \times K + v_i \times \left(a_i - \lfloor \frac DK \rfloor \right) \\ \left(\lfloor \frac DK \rfloor + 1\right) \times K + v_i \times \left( a_i - \lfloor \frac DK \rfloor - 1\right) \]

      对这两个东西取较小的

      当然要满足后面那一项是正的

    2. 当 \(K < v_i\) 时

      只用火炉

      \[\max(D, Ka_i) \]

    其实根本不用分类讨论的

    直接对于这三个式子取最小值即可 记为 \(c_i\)

  2. 如果火炉没开

    只能使用风力发电

    \[v_ia_i \]

    记为 \(d_i\)

对于是否开火 可以通过 \(dp\) 枚举火炉的状态进行转移

状态: \(f_{i, 0/1}\) 表示当前第 \(i\) 天 这一天火炉 开 / 关 时满足需求的最小代价

转移:

\[f_{i, 0} = \min \left(f_{i - 1, 0} + d_i, f_{i - 1, 1} + d_i \right)\\ f_{i, 1} = \min \left(f_{i - 1, 0} + C + c_i, f_{i - 1, 1} + c_i \right)\\ \]

在每一次修改之后重新做 \(dp\)

其实也可以只对修改的那一天以及之后的进行 \(dp\)

时间复杂度: \(O(nq)\)

期望得分: \(60\ Pts\)

代码

/*
  Source: 能源供应
*/
#include<cstdio>
#include<cstring>
#define pt putchar(' ')
#define pn putchar('\n')
#define int long long
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*----------------------------------------------------------*/
const int B = 2e5 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f3f3f3f3f;
/*----------------------------------------------------------*/
inline void File() {
	freopen("energy.in", "r", stdin);
	freopen("energy.out", "w", stdout);
}
/*----------------------------------------------------------*/
int n, C, D, K, v[B], a[B], q, c[B], d[B], f[B][2];
/*----------------------------------------------------------*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void print(int x) {if(x > 9) print(x / 10); putchar(x % 10 ^ 48);}
void Print(int x) {if(x < 0) putchar('-'), x = -x; print(x);}
/*----------------------------------------------------------*/
void work() {
	memset(f, 0, sizeof f); f[0][1] = C;
	for(int i = 1; i ^ n + 1; i++)
		f[i][0] += Min(f[i - 1][0] + d[i], f[i - 1][1] + d[i]), f[i][1] = Min(f[i - 1][0] + C + c[i], f[i - 1][1] + c[i]);
	Print(Min(f[n][0], f[n][1])); pn;
}
/*----------------------------------------------------------*/
signed main() {
    File();
	n = read(); C = read(); D = read(); K = read(); 
	for(int i = 1; i ^ n + 1; i++) v[i] = read();
	for(int i = 1; i ^ n + 1; i++) a[i] = read();
	for(int i = 1; i ^ n + 1; i++)
	{
		int tmp1 = INF, tmp2 = INF;
		if(D / K <= a[i]) tmp1 = Max(D, D / K * K) + v[i] * (a[i] - D / K);
		if(D / K + 1 <= a[i]) tmp2 = Max(D, (D / K + 1) * K) + v[i] * (a[i] - D / K - 1);
		c[i] = Min(Max(D, K * a[i]), Min(tmp1, tmp2)); d[i] = v[i] * a[i];
	}
	q = read();
	while(q--)
	{
		int x = read(), y = read(); v[x] = y;
		int tmp1 = INF, tmp2 = INF;
		if(D / K <= a[x]) tmp1 = Max(D, D / K * K) + v[x] * (a[x] - D / K);
		if(D / K + 1 <= a[x]) tmp2 = Max(D, (D / K + 1) * K) + v[x] * (a[x] - D / K - 1);
		c[x] = Min(Max(D, K * a[x]), Min(tmp1, tmp2)); d[x] = v[x] * a[x];
		work();
	}
	return 0;
}
/*
5 8 1 4
6 7 2 2 7
7 5 8 5 2

*/

根据数据可以猜想满分的复杂度应该是 \(O(q \log n)\) 的

对于上面那一坨转移式子构造矩阵乘法:

\[\begin{Vmatrix} f_{i - 1, 0} & f_{i - 1, 1} \end{Vmatrix} \bigotimes \begin{Vmatrix} d_i & C + c_i \\ d_i & c_i \end{Vmatrix} \\= \begin{Vmatrix} \min(f_{i - 1, 0} + d_i, f_{i - 1, 1} + d_i) & \min(f_{i - 1, 0 + C + c_i}, f_{i - 1, 1} + c_i) \end{Vmatrix} \\= \begin{Vmatrix} f_{i, 0} & f_{i, 1} \end{Vmatrix} \]

这玩意儿是满足结合律的 但是我并不会证明 所以直接扯过来用了

通过线段树维护区间广义矩阵乘 可以做到每次 \(O(\log n)\) 的修改

总复杂度: \(O(q \log n)\)

期望得分: \(100\ Pts\)

注意代码的常数 第一次提交由于常数过大被卡到 \(80\ Pts\)

代码

/*
  Source: 能源供应
  对于每一天来说 当是否开火确定以后 这一天的最优花费是固定的
  通过 dp 枚举火机的状态 直接转移 
  注意数据比较大 极大值要取的足够大 
  需要线段树维护广义矩阵乘进行优化 
*/
#include<cstdio>
#include<cstring>
#define pt putchar(' ')
#define pn putchar('\n')
#define int long long
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*----------------------------------------------------------*/
const int B = 2e5 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f3f3f3f3f;
/*----------------------------------------------------------*/
inline void File() {
	freopen("energy.in", "r", stdin);
	freopen("energy.out", "w", stdout);
}
/*----------------------------------------------------------*/
int n, C, D, K, v[B], a[B], q, c[B], d[B], f[B][2];
/*----------------------------------------------------------*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void print(int x) {if(x > 9) print(x / 10); putchar(x % 10 ^ 48);}
void Print(int x) {if(x < 0) putchar('-'), x = -x; print(x);}
/*----------------------------------------------------------*/
//void work() {
//	memset(f, 0, sizeof f); f[0][1] = C;
//	for(int i = 1; i ^ n + 1; i++)
//		f[i][0] += Min(f[i - 1][0] + d[i], f[i - 1][1] + d[i]), f[i][1] = Min(f[i - 1][0] + C + c[i], f[i - 1][1] + c[i]);
//	Print(Min(f[n][0], f[n][1])); pn;
//}
namespace Seg {
	#define ls(x) x << 1
	#define rs(x) x << 1 | 1
	#define mid (t[p].l + t[p].r >> 1)
	struct M {
		int a[3][3];
//		M() {memset(a, 63, sizeof a);}
		void RE(int i) {a[0][0] = d[i]; a[0][1] = C + c[i]; a[1][0] = d[i]; a[1][1] = c[i];}
	};
	struct node {int l, r; M sum;} t[B << 2];
	M operator * (M x, M y) {
		M z;
		z.a[0][0] = Min(x.a[0][0] + y.a[0][0], x.a[0][1] + y.a[1][0]);
		z.a[0][1] = Min(x.a[0][0] + y.a[0][1], x.a[0][1] + y.a[1][1]);
		z.a[1][0] = Min(x.a[1][0] + y.a[0][0], x.a[1][1] + y.a[1][0]);
		z.a[1][1] = Min(x.a[1][0] + y.a[0][1], x.a[1][1] + y.a[1][1]);
//		for(int i = 0; i ^ 3; i++)
//			for(int k = 0; k ^ 3; k++)
//				for(int j = 0; j ^ 3; j++)
//					z.a[i][j] = Min(z.a[i][j], x.a[i][k] + y.a[k][j]);
		return z;
	}
	node operator + (node x, node y) {
		node z; z.l = x.l; z.r = y.r;
		z.sum = x.sum * y.sum;
		return z;
	}
	void build(int p, int l, int r) {
		t[p].l = l; t[p].r = r; if(l == r) {t[p].sum.RE(l); return ;}
		build(ls(p), l, mid); build(rs(p), mid + 1, r); t[p] = t[ls(p)] + t[rs(p)];
	}
	void up_date(int p, int pos) {
		if(t[p].l == pos && pos == t[p].r) {t[p].sum.RE(pos); return ;}
		if(pos <= mid) up_date(ls(p), pos); else up_date(rs(p), pos); t[p] = t[ls(p)] + t[rs(p)];
	}
}
/*----------------------------------------------------------*/
signed main() {
    File();
	n = read(); C = read(); D = read(); K = read(); 
	for(int i = 1; i ^ n + 1; i++) v[i] = read();
	for(int i = 1; i ^ n + 1; i++) a[i] = read();
	for(int i = 1; i ^ n + 1; i++)
	{
		int tmp1 = INF, tmp2 = INF;
		if(D / K <= a[i]) tmp1 = Max(D, D / K * K) + v[i] * (a[i] - D / K);
		if(D / K + 1 <= a[i]) tmp2 = Max(D, (D / K + 1) * K) + v[i] * (a[i] - D / K - 1);
		c[i] = Min(Max(D, K * a[i]), Min(tmp1, tmp2)); d[i] = v[i] * a[i];
	}
	Seg::build(1, 1, n);
	q = read();
	while(q--)
	{
		int x = read(), y = read(); v[x] = y;
		int tmp1 = INF, tmp2 = INF;
		if(D / K <= a[x]) tmp1 = Max(D, D / K * K) + v[x] * (a[x] - D / K);
		if(D / K + 1 <= a[x]) tmp2 = Max(D, (D / K + 1) * K) + v[x] * (a[x] - D / K - 1);
		c[x] = Min(Max(D, K * a[x]), Min(tmp1, tmp2)); d[x] = v[x] * a[x];
//		work();
		Seg::up_date(1, x); Print(Min(Seg::t[1].sum.a[0][0], Seg::t[1].sum.a[0][1])); pn;
	}
	return 0;
}
/*
5 8 1 4
6 7 2 2 7
7 5 8 5 2

*/

\(T4\) 小 K 的数列

其实这道题还木有通过 分数停留在 \(84\) 分 卡不过去了...

对区间 \([l, r]\) 若有 \(l > 1\) 且 \(a[l - 1] < \max(l, r)\) 那么区间 \([l - 1, r]\) 一定优于 \([l, r]\)

所以只需要考虑两边的数都比区间中的最大值大的区间 即 使每个值作为其所在区间的最大值的最大区间

这样的区间一共有 \(n\) 个 以较大值为树根构建笛卡尔树之后 一个区间对应的就是该树上的一点为根的子树

对于每一次询问向上暴力修改 \(b\) 只会增加 所以一旦遇到合法的点就不需要在向上找

建树的时候是按照较大的 \(a\) 为树根建树 所以找到的第一个合法位置一定是最小的

时间复杂度

建树复杂度 \(O(n)\)

预处理复杂度 \(O(n)\)

单次修改的复杂度比较玄学 最多是树高

如果合法的点非常少 每次的修改的点又非常靠下 修改时增加的值又比较小 复杂度会比较大

最坏的情况:

\(a\) 单调递增 所有的 \(b\) 均为 \(1\) 使 \(V = 10^{18}\) 每次修改 \(1\) 号点 每次加一

这样的时间复杂度为 \(O(nq)\)

实测得分为 \(84\ Pts\) 吸氧 \(88\ Pts\)

代码

/*
  Source: 小K的数列
  对区间 [l, r] 若有 l > 1 且 a[l - 1] < max(l, r) 那么考虑区间 [l - 1, r] 一定优于 [l, r]
  所以只需要考虑两边的数都比区间中的最大值大的区间 即 使每个值作为其所在区间的最大值的最大区间 
  这样的区间一共有 n 个 以较大值为树根构建笛卡尔树之后 一个区间对应的就是该树上的一点为根的子树
  对于每一次询问向上暴力修改 找到的第一个位置一定是最小的 
*/
#include<cstdio>
#include<cstring>
#define pt putchar(' ')
#define pn putchar('\n')
#define ll long long 
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
/*----------------------------------------------------------*/
const int B = 3e5 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*----------------------------------------------------------*/
inline void File() {
	freopen("sequence.in", "r", stdin);
	freopen("sequence.out", "w", stdout);
}
/*----------------------------------------------------------*/
int n, a[B], b[B], q, max, ans = INF;
ll V;
/*----------------------------------------------------------*/
inline ll read() {
	ll x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void Print(ll x) {if(x < 0) putchar('-'), x = -x; if(x > 9) Print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------------------------*/
namespace CT {
	#define ls(x) t[x].l
	#define rs(x) t[x].r
	#define fa(x) t[x].fa
	struct node {int l, r, fa; ll sum; bool flag;} t[B];
	int rt, st[B], top;
	void build() {
		for(int i = 1; i ^ n + 1; i++)
		{
			int k = top;
			while(k && a[st[k]] < a[i]) k--;
			if(k) rs(st[k]) = i, fa(i) = st[k]; if(k < top) ls(i) = st[k + 1], fa(st[k + 1]) = i;
			st[++k] = i; top = k;
		}
	}
	void dfs0(int p) {
		if(ls(p)) dfs0(ls(p)); if(rs(p)) dfs0(rs(p));
		t[p].sum = t[ls(p)].sum + t[rs(p)].sum + 1ll * b[p];
		if(t[p].sum >= V) {t[p].flag = 1; ans = Min(ans, a[p]);}
	}
	void up_date(int p, int k) {
		if(t[p].flag || !p) return ;
		t[p].sum += k; if(t[p].sum >= V) {t[p].flag = 1; ans = Min(ans, a[p]);}
		up_date(fa(p), k);
	}
}
/*----------------------------------------------------------*/
int main() {
	n = read(); V = read();
	for(int i = 1; i ^ n + 1; i++) {a[i] = read(); if(a[i] > max) max = a[i], CT::rt = i;}
	for(int i = 1; i ^ n + 1; i++) b[i] = read();
	CT::build(); CT::dfs0(CT::rt);
	q = read();
	while(q--)
	{
		int x = read(), y = read();
		CT::up_date(x, y);
		Print(ans == INF ? -1 : ans); pn;
	}
	return 0;
}

只需要将每次的修改操作降为 \(O(\log n)\) 级别就可以通过

下面贴标程

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int MAXN = 300005;

int n,q,ans,top;
int S[MAXN];
int a[MAXN];
int b[MAXN];
int l[MAXN];
int r[MAXN];
int pos[MAXN];
int fa[MAXN][20];

bool vis[MAXN];

ll V;
ll sum[MAXN];

void modify(int p,int x)
{
	while (p <= n)
	{
		sum[p] += x;
		p += p & -p;
	}
}

ll query(int p)
{
	ll res = 0;
	while (p >= 1)
	{
		res += sum[p];
		p -= p & -p;
	}
	return res;
}

int main()
{
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	scanf("%d%lld",&n,&V);
	for (int i = 1;i <= n;i++)
	{
		scanf("%d",&a[i]);
		pos[a[i]] = i;
	}
	for (int i = 1;i <= n;i++)
	{
		scanf("%d",&b[i]);
		modify(i,b[i]);
		if (b[i] < 0 || b[i] > 1e9)
			return 0;
	}
	a[0] = a[n + 1] = 1e9;
	S[++top] = 0;
	for (int i = 1;i <= n;i++)
	{
		while (a[S[top]] < a[i])
			top--;
		l[i] = S[top];
		S[++top] = i;
	}
	S[top = 1] = n + 1;
	for (int i = n;i >= 1;i--)
	{
		while (a[S[top]] < a[i])
			top--;
		r[i] = S[top];
		S[++top] = i;
	}
	for (int i = 1;i <= n;i++)
	{
		fa[i][0] = (a[l[i]] < a[r[i]] ? l[i] : r[i]);
		if (a[i] == n)
			fa[i][0] = 0;
	}
	for (int i = n;i >= 1;i--)
	{
		int u = pos[i];
		for (int j = 1;j <= 18;j++)
			fa[u][j] = fa[fa[u][j - 1]][j - 1];
	}
	ans = 1e9;
	for (int i = 1;i <= n;i++)
		if (query(r[i] - 1) - query(l[i]) >= V)
		{
			vis[i] = 1;
			ans = min(ans,a[i]);
		}
	scanf("%d",&q);
	while (q--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		modify(x,y);
		int u;
		if (!vis[x])
		{
			do
			{
				u = x;
				for (int j = 18;j >= 0;j--)
					if (fa[u][j] && !vis[fa[u][j]])
						u = fa[u][j];
				if (query(r[u] - 1) - query(l[u]) >= V)
				{
					vis[u] = 1;
					ans = min(ans,a[u]);
				}
			}while (u != x && vis[u]);
		}
		printf("%d\n",ans == 1e9 ? -1 : ans);
	}
	return 0;
}
posted @ 2021-07-22 10:26  Blank_space  阅读(104)  评论(4)    收藏  举报
// // // // // // //