"蔚来杯"2022牛客暑期多校训练营1
A.Villages: Landlin
数轴上有1个发电站和n-1个建筑,发电站位于\(x_s\)位置,能够与距离\(r_s\)以内的建筑相连。第\(i\)个建筑位于\(x_i\),能与距离\(r_i\)以内的电线杆直接相连。电线杆之间相连需要使用电线,问最少需要多长的电线可以使所有建筑都有能源?
(注意建筑可以传递能量)
这题相当于有n个区间\([x_s-r_s,x_s+r_s],[x_i-r_i,x_i+r_i]\),若区间交集非空则相交。问使得区间全部相交所需要添加的最小区间长度。我们将数据读入,按左端点排序,扫描时不断更新右端点,需要添加区间时更新答案即可。
/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
int f = 1; char ch = getchar();
for (; ch < '0' || ch>'9'; ch = getchar()) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
return x * f;
}
inline void print(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) print(x / 10);
putchar(x % 10 + '0');
}
const int N = 2e5;
struct Segment {
int l, r;
Segment(int _l = 0, int _r = 0) { l = _l, r = _r; }
bool operator<(const Segment& ots)const { return l < ots.l; }
};
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int n = read(0);
vector<Segment>segments;
for (int i = 1; i <= n; i++) {
int x = read(0), radius = read(0);
segments.push_back(Segment(x - radius, x + radius));
}
sort(segments.begin(), segments.end());
ll Ans = 0, MaxR = -lMax;
bool Flag = 0;
for (auto segment : segments) {
if (Flag && MaxR < segment.l)
Ans += segment.l - MaxR;
MaxR = max(MaxR, 1ll * segment.r), Flag |= 1;
}
printf("%lld\n", Ans);
return 0;
}
B.Spirit Circle Observation
给定长度为\(n\)的字符串s,求满足条件的二元组(A,B)的个数
- A,B是s的子串
- A,B长度相同
- A+1=B(数字意义上)
即使两个子串相同,只要它们的位置不同便认为是不同子串
\(1\leqslant n\leqslant 4\times 10^5\)
字符串难难,咕咕咕
C.Grab the Seat!
有一个二维平面,屏幕是线段\((0,1)\sim (0,m)\),有\(n\times m\)个座位,有\(k\)个人在座位上,第\(i\)个人坐标为\((x_i,y_i)\)。有\(q\)次操作,每次修改一个人的坐标后,求到屏幕视线不被任何人挡住的座位个数
对于每个人而言,他们所挡住的位置是起点为(0,1),(0,m)的两条射线在该点处相交后之间的区域。如果我们对于每个点求出这些挡住区域的并集即可求出答案。
这样的区域实际上构成了一个折线图,对于每个\(y\)都存在一个分界点\(t\),满足\(x\in[1,t)\)是合法的,\(x\in[t,n]\)则是非法的。
考虑如何维护这样一个折线图,实际上每条折线线段的延长线都是经过屏幕两端的,故我们将其分成两类,如果按照\(y\)降序或者升序依次加入点,不难发现,一旦高斜率的线段加入,便会碾压低斜率的线段,故我们对\(y\)枚举一遍,维护最大斜率即可
/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_set>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
int f = 1; char ch = getchar();
for (; ch < '0' || ch>'9'; ch = getchar()) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
return x * f;
}
inline void print(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) print(x / 10);
putchar(x % 10 + '0');
}
const int N = 2e5;
unordered_set<int>points[N + 10];
int Row[N + 10];
pii A[N + 10];
bool Check(pii A, pii B) { return 1ll * B.second * A.first > 1ll * A.second * B.first; }
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int n = read(0), m = read(0), k = read(0), q = read(0);
for (int i = 1; i <= k; i++) {
int x = read(0), y = read(0);
points[y].insert(x);
A[i] = MK(x, y);
}
for (int i = 1; i <= q; i++) {
int p = read(0);
points[A[p].second].erase(A[p].first);
int x = read(0), y = read(0);
A[p] = MK(x, y);
points[y].insert(x);
pii Now = MK(0, 0);
for (int j = 1; j <= m; j++) {
if (j == 1) {
Row[j] = n + 1;
for (auto p : points[j])
Row[j] = min(Row[j], p);
continue;
}
for (auto p : points[j])
if (!Now.second || Check(Now, MK(p, j - 1)))
Now = MK(p, j - 1);
Row[j] = !Now.second ? n + 1 : (int)ceil(1.0 * (j - 1) * Now.first / Now.second);
}
Now = MK(0, 0);
for (int j = m; j >= 1; j--) {
if (j == m) {
Row[j] = min(Row[j], n + 1);
for (auto p : points[j])
Row[j] = min(Row[j], p);
continue;
}
for (auto p : points[j])
if (!Now.second || Check(Now, MK(p, m - j)))
Now = MK(p, m - j);
Row[j] = min(Row[j], min(!Now.second ? n + 1 : (int)ceil(1.0 * (m - j) * Now.first / Now.second), n + 1));
}
ll Ans = 0;
for (int j = 1; j <= m; j++)
Ans += Row[j] - 1;
printf("%lld\n", Ans);
}
return 0;
}
D.Mocha and Railgun
给定一个圆和严格圆内一点\(p\),以\(p\)为中点发射长度为\(2d\)的电磁炮,已知\(p\)到圆周距离严格小于\(d\),问最大能摧毁的圆弧长度?
几何题,手画后不难发现,当发射基座位于线段\(Op\)上时,可以摧毁的圆弧最长
/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
int f = 1; char ch = getchar();
for (; ch < '0' || ch>'9'; ch = getchar()) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
return x * f;
}
inline void print(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) print(x / 10);
putchar(x % 10 + '0');
}
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T = read(0);
while (T--) {
double radius = read(0), x = read(0), y = read(0), d = read(0);
double distance = sqrt(sqr(x) + sqr(y));
double alpha = acos((distance - d) / radius);
double beta = acos((distance + d) / radius);
double len = (alpha - beta) * radius;
printf("%.10lf\n", len);
}
return 0;
}
E.LTCS
定义一棵带点权的有根树\(T_a\)是\(T_b\)的子序列当且仅当:
- \(T_a\)的点到\(T_b\)的点存在一个单射\(F\)
- 对于\(T_a\)中所有的点有\(c_{a,u}=c_{b,F(u)}\)
- 如果\(u\)在\(T_a\)中是\(v\)的祖先,则\(F(u)\)在\(T_b\)中是\(F(v)\)的祖先
- 如果\(u\)在\(T_a\)中是\(v,w\)的父亲,则在\(T_b\)中\(F(u)\)在\(F(v),F(w)\)的简单路径上
求两棵以1为根的树\(T_1,T_2\)的最大公共子序列的大小
dp+二分图匹配,咕咕咕
F.Cut
给定1到\(n\)的排列\(a_1,a_2,...,a_n\),有\(m\)次以下三种操作:
- 将\(a_l,a_{l+1},...,a_r\)升序排序
- 将\(a_l,a_{l+1},...,a_r\)降序排序
- 询问\(a_l,a_{l+1},...,a_r\)中最长的奇偶交替子序列
\(1\leqslant n,m\leqslant 10^5\)
线段树合并,咕咕咕
G.Lexicographical Maximum
给定\(n\),求\(1\sim n\)中字典序最大的字符串
显然,答案除去最后一位则全为9(也可能为空)。故对\(n\)进行判断,若\(n\)除去最后一位均为9,则输出\(n\);否则输出\(|n|-1\)个9
(代码略)
H.Fly
\(n\)种物品每种有无限个,第\(i\)个物品的体积为\(a_i\),求解选择物品总体积不超过\(M\)的方案数
此外存在\(k\)个限制,第\(i\)个限制形如第\(b_i\)个物品的所选数量二进制表示从高到低的低\(c_i\)位必须为0
\(1\leqslant n,a_i,\sum a_i\leqslant 10^4\),\(0\leqslant M\leqslant 10^{18}\),\(1\leqslant k\leqslant 5\times 10^3\)
DP + 多项式,咕咕咕
I.Chiitoitsu
初始有13张麻将牌,相同牌至多出现两次。每次从牌堆摸牌,若凑成七对子可直接胡牌,否则将其替换手里的任意一张牌或者直接打出。问在最优策略下,将手牌摸成七对子的期望轮数
首先明确一点,用摸到的牌替换手里的牌一定不会使答案更优,除非手里已经有一张一样的牌;其次,一旦有一对相同的牌在手中,我们边不再动它,故实际上摸牌轮数是由手中的单牌数决定的
此外,在最优策略下,手中的单牌必定在牌堆中还存有3个
考虑DP,设\(F[i][j]\)表示手中有\(i\)个单牌,牌堆还剩\(j\)张牌,将手中的牌摸成对子所需要的期望轮数,则转移方程为:
前者为摸到手中存在的单牌的情况,后者为没有摸到手中单牌的情况。注意当手上仅剩一张牌时,前面的状态就不需要考虑了
最后我们输出\(F[c][136-13]\)即可,\(c\)为初始手中的单牌数量
/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
int f = 1; char ch = getchar();
for (; ch < '0' || ch>'9'; ch = getchar()) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
return x * f;
}
inline void print(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) print(x / 10);
putchar(x % 10 + '0');
}
const int N = 34, P = 1e9 + 7;
int F[N + 10][(N << 2) + 10], Inv[(N << 2) + 10];
void prepare() {
Inv[0] = Inv[1] = 1;
for (int i = 2; i <= N << 2; i++) Inv[i] = 1ll * (P - P / i) * Inv[P % i] % P;
//for (int i = 1; i <= N << 2; i++) F[0][i] = 1;
for (int i = 1; i <= N; i += 2) {
for (int j = i * 3; j <= N << 2; j++) {
int temp = i == 1 ? 0 : F[i - 2][j - 1];
F[i][j] = 3ll * i % P * Inv[j] % P * temp % P;
if (j > i * 3)
F[i][j] = (F[i][j] + 1ll * (j - 3 * i) * Inv[j] % P * F[i][j - 1] % P) % P;
(++F[i][j]) %= P;
}
}
}
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
prepare();
int T = read(0);
for (int Time = 1; Time <= T; Time++) {
printf("Case #%d: ", Time);
char s[30];
scanf("%s", s);
int len = strlen(s);
map<string, int>msi;
for (int i = 0; i < len; i += 2) {
string temp;
temp += s[i], temp += s[i + 1];
msi[temp]++;
}
int Cnt = 0;
for (auto p : msi)
Cnt += p.second == 1;
printf("%d\n", F[Cnt][(N << 2) - 13]);
}
return 0;
}
J.Serval and Essay
给定一张\(n\)个点\(m\)条边的无重边无自环的有向图,初始可选择一个点染黑,若一个点的入点全都染黑则该点也可以染黑,求最大化黑点数
考虑通过\(x\)确定\(y\)这种关系将\(x,y\)合并,如果\(x\)能够将\(y\)染色,那么我们就不必再考虑\(y\)了,将所有\(y\)指出的边改为\(x\)指出即可
当\(x,y\)合并后,原本\(y\)指向的边就变成了\(x\)指向,如果两者指向的边在\(t\)处重合,便会合并出很多\(x\ \mathrm{to}\ t\)的边,我们维护一个set用以自动去重;此外,这个时候的\(t\)也是被\(x\)所决定的,因此我们若在合并\(y\)时出现这种情况,\(t\)也是需要继续合并的
合并次数显然是\(O(n)\)的,并且合并的是两个点的出边集合,因此我们采用启发式合并
/*program from Wolfycz*/
#include<map>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lMax 1e18
#define MK make_pair
#define iMax 0x7f7f7f7f
#define sqr(x) ((x)*(x))
#define pii pair<int,int>
#define UNUSED(x) (void)(x)
#define lowbit(x) ((x)&(-x))
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
template<typename T>inline T read(T x) {
int f = 1; char ch = getchar();
for (; ch < '0' || ch>'9'; ch = getchar()) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
return x * f;
}
inline void print(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) print(x / 10);
putchar(x % 10 + '0');
}
const int N = 2e5;
int Fa[N + 10], Sz[N + 10];
set<int>Frm[N + 10], Nxt[N + 10];
int Find(int x) { return x == Fa[x] ? x : Fa[x] = Find(Fa[x]); }
void Merge(int x, int y) {
x = Find(x), y = Find(y);
if (x == y) return;
if (Nxt[x].size() < Nxt[y].size()) swap(x, y);
Sz[Fa[y] = x] += Sz[y];
vector<pair<int, int>>temp;
for (auto p : Nxt[y]) {
Nxt[x].insert(p);
Frm[p].erase(y);
Frm[p].insert(x);
if (Frm[p].size() == 1)
temp.push_back(make_pair(x, p));
}
for (auto [x, y] : temp)
Merge(x, y);
}
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T = read(0);
for (int Case = 1; Case <= T; Case++) {
int n = read(0);
for (int i = 1; i <= n; i++) Sz[Fa[i] = i] = 1;
for (int i = 1; i <= n; i++) {
int k = read(0);
for (int j = 1; j <= k; j++) {
int x = read(0);
Frm[i].insert(x);
Nxt[x].insert(i);
}
}
for (int i = 1; i <= n; i++)
if (Frm[i].size() == 1)
Merge(*Frm[i].begin(), i);
int Ans = 0;
for (int i = 1; i <= n; i++) {
Ans = max(Ans, Sz[i]);
Frm[i].clear();
Nxt[i].clear();
}
printf("Case #%d: %d\n", Case, Ans);
}
return 0;
}
K.Villages: Landcircles
给定二维平面\(n\)个圆,可在圆外任意处添加额外的点,连接点与点,点与圆,圆与圆的花费为所连线段长度,询问将\(n\)个圆连通的最小费用
防AK题,神仙知识,咕咕咕咕咕咕