数轴上有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; }
给定长度为\(n\)的字符串s,求满足条件的二元组(A,B)的个数
即使两个子串相同,只要它们的位置不同便认为是不同子串
\(1\leqslant n\leqslant 4\times 10^5\)
字符串难难,咕咕咕
有一个二维平面,屏幕是线段\((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; }
给定一个圆和严格圆内一点\(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; }
定义一棵带点权的有根树\(T_a\)是\(T_b\)的子序列当且仅当:
求两棵以1为根的树\(T_1,T_2\)的最大公共子序列的大小
dp+二分图匹配,咕咕咕
给定1到\(n\)的排列\(a_1,a_2,...,a_n\),有\(m\)次以下三种操作:
\(1\leqslant n,m\leqslant 10^5\)
线段树合并,咕咕咕
给定\(n\),求\(1\sim n\)中字典序最大的字符串
显然,答案除去最后一位则全为9(也可能为空)。故对\(n\)进行判断,若\(n\)除去最后一位均为9,则输出\(n\);否则输出\(|n|-1\)个9
(代码略)
\(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 + 多项式,咕咕咕
初始有13张麻将牌,相同牌至多出现两次。每次从牌堆摸牌,若凑成七对子可直接胡牌,否则将其替换手里的任意一张牌或者直接打出。问在最优策略下,将手牌摸成七对子的期望轮数
首先明确一点,用摸到的牌替换手里的牌一定不会使答案更优,除非手里已经有一张一样的牌;其次,一旦有一对相同的牌在手中,我们边不再动它,故实际上摸牌轮数是由手中的单牌数决定的
此外,在最优策略下,手中的单牌必定在牌堆中还存有3个
考虑DP,设\(F[i][j]\)表示手中有\(i\)个单牌,牌堆还剩\(j\)张牌,将手中的牌摸成对子所需要的期望轮数,则转移方程为:
\[F[i][j]=\frac{3i}{j}\times F[i-2][j-1]+\frac{j-3i}{j}\times F[i][j-1]+1 \]前者为摸到手中存在的单牌的情况,后者为没有摸到手中单牌的情况。注意当手上仅剩一张牌时,前面的状态就不需要考虑了
最后我们输出\(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; }
给定一张\(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; }
给定二维平面\(n\)个圆,可在圆外任意处添加额外的点,连接点与点,点与圆,圆与圆的花费为所连线段长度,询问将\(n\)个圆连通的最小费用
防AK题,神仙知识,咕咕咕咕咕咕