七月腾飞营结业考试 题解报告
七月腾飞营结业考试 题解报告
前言
挂分挂的一塌糊涂
得分: \(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\) 能源供应
可以发现 对于任意一天 当是否开火确定以后 这一天的最优花费是固定的 可以直接算
-
如果火炉开着
哪怕不用 同样需要有 \(D\) 的代价 所以不用白不用
-
当 \(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) \]对这两个东西取较小的
当然要满足后面那一项是正的
-
当 \(K < v_i\) 时
只用火炉
\[\max(D, Ka_i) \]
其实根本不用分类讨论的
直接对于这三个式子取最小值即可 记为 \(c_i\)
-
-
如果火炉没开
只能使用风力发电
\[v_ia_i \]记为 \(d_i\)
对于是否开火 可以通过 \(dp\) 枚举火炉的状态进行转移
状态: \(f_{i, 0/1}\) 表示当前第 \(i\) 天 这一天火炉 开 / 关 时满足需求的最小代价
转移:
在每一次修改之后重新做 \(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)\) 的
对于上面那一坨转移式子构造矩阵乘法:
这玩意儿是满足结合律的 但是我并不会证明 所以直接扯过来用了
通过线段树维护区间广义矩阵乘 可以做到每次 \(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;
}