11.26 CW 模拟赛 赛时记录
看题
也是给他绑上 \(\rm{Subtask}\) 了, 这我骗鸡毛分啊
感冒也是非常难受, 但是事已至此, 先读题吧
题目背景好看爱看
\(\rm{T1}\)
图论题, 好玩
\(\rm{T2}\)
大概也是不会做, 再说
\(\rm{T3}\)
难绷, 考虑高档暴力
\(\rm{T4}\)
这个肯定是暴力都难打
今天也是 \(30 \rm{min} + 1 \rm{h} + 15 \rm{min} + 1 \rm{h} 15 \rm{min}\)
\(\rm{T1}\)
先分析题意,
给定一张无重边, 无环的无向图
求加若干边之后, 图中最长链能达到多长
首先发现最后的图一定包含了全部的 \(n\) 个点, 因为如果不包含点 \(i\) , 那么选上点 \(i\) 之后, 最长的链一定会变得更长
容易的, 我们发现原图应该是一个森林, 那么我们需要把原图中的森林连接起来, 使得最长链最长, 符合直觉的, 我们应该去连接每颗树的直径
那么好, 这题结束了
首先读入是显然的, 对于每一个连通块, 两遍 \(\rm{dfs}\) 求直径的两端
对于第 \(i\) 个连通块, 我们考虑将 \(i - 1\) 连通块的右侧 (这里的左右人为指定) , 和 \(i\) 的左侧连接起来, 对于 \(i = 1/n\) 情况特殊处理
\(\rm{T2}\)
只过了 \(25 \rm{min}\) , 冲冲冲
\(\rm{T1}\) 多半是大众分, 当然我多半拿不到大众分就是了 (确实没拿到)
转化题意
对于序列 \(s\) , 找出一段区间 \([L, R]\) , 使得区间长度至少为 \(k\) 的前提下, 令所有数的 \(\gcd\) 为 \(g\) , 求 \(\displaystyle g \times \sum_{i = L}^{R} s_i \to \max\)
题意其实给的很清楚了, 不太需要转化
区间长是 \(10^6\) 级别的, 不好处理
暴力的做法是, 考虑对于一个固定的 \(R\) , 我们向前走, 记录 \(\sum\) 值和 \(\gcd\) , 可以做到 \(\mathcal{O} (n ^ 2)\) 的时间复杂度, \(30 \rm{pts}\)
我们还需要优化, 这里优化的点很明确, 我们需要利用之前的计算结果, 不能再次重复计算
但是并不好利用之前的计算结果, 考虑神秘优化
注意到当右端点确定, \(\gcd\) 的趋势是单调不增, 我们考虑预处理出所有 \(\gcd\) 的变化区间的最左侧点, 只考虑最左端点即可, 期望上是 \(\mathcal{O} (n \sqrt{n})\) 的, 可以拿到 \(60 \rm{pts}\)
一会再回来想一想
对于右端点的每一次拓张, 我们向左枚举到第一个值为当前 \(s_i\) 因数的最左侧点, 中间的全部都要并过来, 即在栈中弹出, 如果左边没有因数, 弹出完了之后插入一个 \(1\) , 特别的, 判断一下 \(s_i\) 和当前栈头的关系, 看 \(s_i\) 是否需要单开区间
这个可以拿栈处理
\(\rm{T3}\)
转化题意
对于序列 \(s\) , 找出多少个区间 \([L, R]\) 满足其中有一半以上的元素相同
对于 \(\rm{Subtask} 1, 2\) , \(\mathcal{O} (n ^ 2 \log n)\) 算法可以解决
对于 \(\rm{Subtask} 3\) ,
我们先预处理出两种数在区间中的出现次数的差出现次数的前缀和, 然后就简单了
代码
没有好实现的, 按分值打
\(\rm{T1}\)
写的差不多, 看看会不会挂
#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e5 + 20;
#define FILE_IO
int n, m;
class Graph_Class
{
private:
public:
/*并查集*/
struct DSU_struct
{
int fa[MAXN];
void fa_init() { for (int i = 1; i <= n; i++) fa[i] = i; }
int find(int x) {
return fa[x] = (x == fa[x]) ? x : find(fa[x]);
}
void merge(int u, int v) {
int fa_u = find(u), fa_v = find(v);
fa[fa_v] = fa_u;
}
} DSU;
struct node
{
int to;
int next;
} Edge[MAXN << 1];
int Edge_cnt = 0;
int head[MAXN];
void Edge_init() { for (int i = 1; i <= m * 2; i++) Edge[i].next = -1; }
void head_init() { for (int i = 1; i <= n; i++) head[i] = -1; }
void addedge(int u, int v) {
Edge[++Edge_cnt].to = v;
Edge[Edge_cnt].next = head[u];
head[u] = Edge_cnt;
}
} Graph;
class Sol_Class
{
private:
int dist[MAXN];
void dfs1(int Now, int fa, int d) {
dist[Now] = d;
for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next) {
int NowTo = Graph.Edge[i].to;
if (NowTo == fa) continue;
dfs1(NowTo, Now, d + 1);
}
}
int CalcD(int NowTree) {
for (int i = 1; i <= n; i++) {
if (Graph.DSU.find(i) == NowTree) {
dfs1(i, -1, 1);
break;
}
}
int Root = -1, maxdis = 0;
for (int i = 1; i <= n; i++) {
if (Graph.DSU.find(i) != NowTree) continue;
if (dist[i] > maxdis) maxdis = dist[i], Root = i;
}
memset(dist, 0, sizeof(dist));
dfs1(Root, -1, 1);
Root = -1, maxdis = 0;
for (int i = 1; i <= n; i++)
{
if (Graph.DSU.find(i) != NowTree) continue;
if (dist[i] > maxdis) maxdis = dist[i], Root = i;
}
return maxdis;
}
public:
bool Treevis[MAXN];
int Ans = 0;
/*分割森林, 计算直径*/
void solve()
{
for (int i = 1; i <= n; i++) {
int NowTree = Graph.DSU.find(i);
if (Treevis[NowTree]) continue;
Ans += CalcD(NowTree);
Treevis[NowTree] = true;
}
printf("%lld", Ans);
}
} Sol;
signed main()
{
#ifdef FILE_IO
freopen("chariot.in", "r", stdin);
freopen("chariot.out", "w", stdout);
#endif
scanf("%lld %lld", &n, &m);
Graph.DSU.fa_init();
Graph.head_init();
Graph.Edge_init();
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%lld %lld", &u, &v);
Graph.addedge(u, v), Graph.addedge(v, u);
Graph.DSU.merge(u, v);
}
Sol.solve();
return 0;
}
\(\rm{T2}\)
稍微复杂, 看运气了, 话说我今天 \(\rm{luogu}\) 都没打卡
优化方法可能是错的, 跑不过大样例, 只能留在后面看看能骗多少
#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e6 + 20;
#define FILE_IO
int n, k;
int h[MAXN];
int Sum[MAXN];
class Subtask_3
{
private:
/*栈*/
struct node {
int Pos;
int Val;
} ;
struct Stack_struct
{
node Stack[MAXN];
int tp = 0;
bool empty() {
return tp == 0;
}
void pop() {
tp--;
}
void push(node x) {
Stack[++tp] = x;
}
node top() {
return Stack[tp];
}
} S;
public:
/*处理*/
void solve()
{
int Ans = 0;
for (int i = 1; i <= n; i++)
{
/*处理 gcd 左端点*/
while (!S.empty()) {
node Now = S.top();
if (h[i] % Now.Val == 0) break;
S.pop();
}
if (S.empty()) S.push({1, 1});
if (h[i] != S.top().Val) S.push({i, h[i]});
/*计算答案*/
for (int j = 1; j <= S.tp; j++) {
int NowAns = (Sum[i] - Sum[S.Stack[j].Pos - 1]) * S.Stack[j].Val;
if (i - S.Stack[j].Pos + 1 < k) continue;
Ans = std::max(Ans, NowAns);
}
}
printf("%lld", Ans);
}
} S_3;
class Subtask_12
{
private:
public:
void solve()
{
int Ans = 0;
for (int i = 1; i <= n; i++) {
int Sum = 0, Gcd = h[i];
for (int j = i; j >= 1; j--) {
Sum += h[j];
Gcd = std::__gcd(Gcd, h[j]);
if (i - j + 1 >= k) Ans = std::max(Ans, Sum * Gcd);
}
}
printf("%lld", Ans);
}
} S_12;
signed main()
{
#ifdef FILE_IO
freopen("intelligence.in", "r", stdin);
freopen("intelligence.out", "w", stdout);
#endif
scanf("%lld %lld", &n, &k);
for (int i = 1; i <= n; i++)
scanf("%lld", &h[i]), Sum[i] = Sum[i - 1] + h[i];
if (n > 10000) S_3.solve();
else S_12.solve();
return 0;
}
只能赶紧冲 \(\rm{T3}\) , 优化思路能骗 \(10 \rm{pts}\) 就非常好了
\(\rm{T3}\)
没打完, 寄

浙公网安备 33010602011771号