没有摘要的摘要

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

前言

挂分挂的一塌糊涂

得分: \(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;
}