Avian Darts
Boring Task
Cookies
Distinct Sub-palindromes
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
ll qpow(ll a, ll b) {
a %= mod;
ll res = 1;
while (b) {
if (b & 1)res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll n;
ll Cn3 = (24 * 25 * 26) % mod;// C(n,3)
int main() {
int T;
cin >> T;
while (T--) {
cin >> n;
if (n <= 3)
cout << qpow(26, n) << endl;
else
cout << Cn3 << endl;
}
return 0;
}
Fibonacci Sum
赛中交的代码赛后疯狂T,感谢测评姬放过……
已知斐波那契有如下:
F
(
n
)
=
1
5
[
(
1
+
5
2
)
n
−
(
1
−
5
2
)
n
]
F(n)=\frac{1}{\sqrt 5}\bigg[ \bigg( \frac{1+\sqrt 5}{2} \bigg)^n- \bigg( \frac{1-\sqrt 5}{2} \bigg)^n \bigg]
F(n)=51[(21+5)n−(21−5)n]
再扩展一下每一项的k次幂
F
(
n
)
k
=
(
1
5
)
k
[
(
1
+
5
2
)
n
−
(
1
−
5
2
)
n
]
k
F(n)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[ \bigg( \frac{1+\sqrt 5}{2} \bigg)^n- \bigg( \frac{1-\sqrt 5}{2} \bigg)^n \bigg]^k
F(n)k=(51)k[(21+5)n−(21−5)n]k
其中,令
a
=
(
1
+
5
2
)
n
a=\bigg( \frac{1+\sqrt 5}{2} \bigg)^n
a=(21+5)n
b
=
(
1
−
5
2
)
n
b=\bigg( \frac{1-\sqrt 5}{2} \bigg)^n
b=(21−5)n
则有
[
(
1
+
5
2
)
n
−
(
1
−
5
2
)
n
]
k
=
(
a
n
−
b
n
)
k
\bigg[ \bigg( \frac{1+\sqrt 5}{2} \bigg)^n- \bigg( \frac{1-\sqrt 5}{2} \bigg)^n \bigg]^k=(a^n-b^n)^k
[(21+5)n−(21−5)n]k=(an−bn)k
注意到,这是一个二项式
(
a
n
−
b
n
)
k
=
C
k
0
(
a
n
)
0
(
−
b
n
)
k
+
C
k
1
(
a
n
)
1
(
−
b
n
)
k
−
1
+
.
.
.
.
.
+
C
k
k
(
a
n
)
k
(
−
b
n
)
0
=
∑
i
=
0
k
(
−
1
)
k
−
i
(
a
n
)
i
(
b
n
)
k
−
i
(a^n-b^n)^k=C^0_k(a^n)^0(-b^n)^k+C^1_k(a^n)^1(-b^n)^{k-1}+.....+C^k_k(a^n)^k(-b^n)^0\\=\sum^k_{i=0}(-1)^{k-i}(a^n)^i(b^n)^{k-i}
(an−bn)k=Ck0(an)0(−bn)k+Ck1(an)1(−bn)k−1+.....+Ckk(an)k(−bn)0=i=0∑k(−1)k−i(an)i(bn)k−i
注意一下,这里把
−
b
n
-b^n
−bn 里的负号提取出来了,用处在后面会提到
因此,
F
(
n
)
k
=
(
1
5
)
k
[
∑
i
=
0
k
(
−
1
)
k
−
i
C
k
i
(
a
n
)
i
(
b
n
)
k
−
i
]
F(n)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k(a^n)^i(b^n)^{k-i}\bigg]
F(n)k=(51)k[i=0∑k(−1)k−iCki(an)i(bn)k−i]
结合题目给出的n、c、k,用c替换上式的n
F ( c ) k = ( 1 5 ) k [ ∑ i = 0 k ( − 1 ) k − i C k i ( a c ) i ( b c ) k − i ] F(c)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k(a^c)^i(b^c)^{k-i}\bigg] F(c)k=(51)k[i=0∑k(−1)k−iCki(ac)i(bc)k−i]
然后再看看F(2c)时的式子
F
(
2
c
)
k
=
(
1
5
)
k
[
(
1
+
5
2
)
2
c
−
(
1
−
5
2
)
2
c
]
k
=
(
1
5
)
k
[
∑
i
=
0
k
(
−
1
)
k
−
i
C
k
i
(
a
2
c
)
i
(
b
2
c
)
k
−
i
]
=
(
1
5
)
k
[
∑
i
=
0
k
(
−
1
)
k
−
i
C
k
i
(
(
a
c
)
i
(
b
c
)
k
−
i
)
2
]
F(2c)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[ \bigg( \frac{1+\sqrt 5}{2} \bigg)^{2c}- \bigg( \frac{1-\sqrt 5}{2} \bigg)^{2c} \bigg]^k\\=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k(a^{2c})^i(b^{2c})^{k-i}\bigg]\\=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k\bigg((a^{c})^i(b^{c})^{k-i}\bigg)^2\bigg]
F(2c)k=(51)k[(21+5)2c−(21−5)2c]k=(51)k[i=0∑k(−1)k−iCki(a2c)i(b2c)k−i]=(51)k[i=0∑k(−1)k−iCki((ac)i(bc)k−i)2]
可以发现
F
(
c
)
F(c)
F(c) 与
F
(
2
c
)
F(2c)
F(2c) 之间,二项式里每一项的
(
a
c
)
i
(
b
c
)
k
−
i
(a^{c})^i(b^{c})^{k-i}
(ac)i(bc)k−i 翻倍了
由此推出
F
(
n
c
)
F(nc)
F(nc) 时的式子
F
(
n
c
)
k
=
(
1
5
)
k
[
∑
i
=
0
k
(
−
1
)
k
−
i
C
k
i
(
(
a
c
)
i
(
b
c
)
k
−
i
)
n
]
F(nc)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k\bigg((a^{c})^i(b^{c})^{k-i}\bigg)^n\bigg]
F(nc)k=(51)k[i=0∑k(−1)k−iCki((ac)i(bc)k−i)n]
对于各个
F
(
x
c
)
x
=
1...
n
F(xc)_{x=1...n}
F(xc)x=1...n的二项式里第
i
i
i 项,存在公比
q
=
(
a
c
)
i
(
b
c
)
k
−
i
q=(a^{c})^i(b^{c})^{k-i}
q=(ac)i(bc)k−i,可以等比公式求和统计
S ( n ) = a 1 × q n − 1 q − 1 S(n)=a_1\times\frac{q^n-1}{q-1} S(n)=a1×q−1qn−1
前面提取的负号,也是为了方便后面的求和统计
重点:
模数 p = 1 0 9 + 9 p=10^9+9 p=109+9 是一个质数
1 5 \cfrac{1}{\sqrt 5} 51 在模意义下需要借助 二次剩余 来求,即 x 2 = 5 m o d 1 0 9 + 9 x^2=5\mod {10^9+9} x2=5mod109+9
得到x的一个解: x = 5 = 383008016 x=\sqrt 5=383008016 x=5=383008016
再结合费马小定理求出斐波那契公式里的 a a a 和 b b b
a = 1 + 5 2 = 691504013 b = 1 − 5 2 = 308495997 1 5 = 276601605 a= \frac{1+\sqrt 5}{2} =691504013\\b= \frac{1-\sqrt 5}{2} =308495997\\\frac{1}{\sqrt 5}=276601605 a=21+5=691504013b=21−5=30849599751=276601605
其次
n
,
c
≤
1
0
18
n,c\le10^{18}
n,c≤1018 数据过大,需要 欧拉降幂
公式如下:
a
b
m
o
d
p
=
a
b
m
o
d
φ
(
p
)
+
φ
(
p
)
m
o
d
p
a^b \mod p=a^{b \mod \varphi(p)+\varphi(p)}\mod p
abmodp=abmodφ(p)+φ(p)modp
并且由于 1 0 9 + 9 10^9+9 109+9是个质数,可以根据欧拉函数的性质直接得出phi值就是 模数本身-1
φ ( 1 0 9 + 9 ) = 1 0 9 + 8 \varphi(10^9+9)=10^9+8 φ(109+9)=109+8
ps:如果还T的话,scanf改cin、关掉一两个数组,你就可以过去了……
// 赛后ac代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 9;
const int N = 1e5 + 10;
ll qpow(ll a, ll b) {
a %= mod;
ll res = 1;
while (b) {
if (b & 1)res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll Phi = mod - 1;//欧拉降幂
ll euler(ll a, ll b) {
return qpow(a, ((b % Phi) + Phi));
}
ll fac[N], inv[N];
void init(int n) {
fac[0] = inv[0] = 1;
for (int i = 1; i <= n; ++i) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = inv[i - 1] * qpow(i, mod - 2) % mod;
}
}
ll C(int n, int m) {
if (n < m || m < 0)return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
ll Add(ll x, ll y) {
if (x < 0)x += mod;
if (y < 0) y += mod;
return (x + y) % mod;
}
ll Mul(ll x, ll y) {
x %= mod;
y %= mod;
return x * y % mod;
}
ll n, c;
int k;
ll a = 691504013;// (1+sqrt(5))/2
ll b = 308495997;// (1-sqrt(5))/2
ll Inv5 = 276601605;// 1/sqrt(5)
ll A, B, A_B, q;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
init(100000);
int T;
cin >> T;
while (T--) {
cin >> n >> c >> k;
A = qpow(a, c);// a^c
B = qpow(b, c);// b^c
A_B = Mul(A, qpow(B, mod - 2));// A/B
q = qpow(B, k);//公比
ll res = 0, tmp;
for (int i = 0; i <= k; i++) {
//if (q == 0)continue; mod为质数 永远不会有公比为0的时候
if (q == 1) {
tmp = n % mod;
} else {
tmp = Mul(Mul(Add(euler(q, n), mod - 1), qpow(Add(q, mod - 1), mod - 2)), q);
}
tmp = Mul((C(k, i) * ((k - i) & 1 ? -1 : 1)), tmp);
res = Add(res, tmp);
q = Mul(q, A_B);
}
res = Mul(res, qpow(Inv5, k));
cout << res << endl;
}
return 0;
}
Finding a MEX
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 3;
vector<int> e[N], E[N];
int vis[N];
int a[N];
int u[N], v[N], d[N];
int n, m;
struct segTree {
#define ls (o<<1)
#define rs (o<<1|1)
struct node {
int l, r;
int sum;
} t[N << 2];
int cnt[N];
void pushup(int o) {
t[o].sum = t[ls].sum + t[rs].sum;
}
//不带数组 建树
void build(int o, int l, int r) {
t[o].l = l;
t[o].r = r;
t[o].sum = 0;
if (l == r) {
cnt[l] = 0;
return;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
int query(int o) {
if (t[o].l == t[o].r) {
return t[o].l;
}
int len = t[o].r - t[o].l + 1;
if (t[ls].sum >= (len - (len >> 1)))
return query(rs);
else return query(ls);
}
void add(int o, int pos, int val) {
if (t[o].l == t[o].r) {
cnt[t[o].l] += val;
if (cnt[t[o].l] > 0)
t[o].sum = 1;
else
t[o].sum = 0;
return;
}
int mid = t[o].l + t[o].r >> 1;
if (pos <= mid) add(ls, pos, val);
else add(rs, pos, val);
pushup(o);
}
} ST[351];
int id[N], dfn = 0;
int Hash[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
//init
dfn = 0;
for (int i = 1; i <= n; i++) {
e[i].clear();//所有边
E[i].clear();//大点连边
vis[i] = d[i] = 0;
id[i] = 0;
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i]++;//在原来的基础上+1 便于线段树维护
}
for (int i = 1; i <= m; i++) {
cin >> u[i] >> v[i];
e[u[i]].push_back(v[i]);
e[v[i]].push_back(u[i]);
d[u[i]]++;
d[v[i]]++;
}
int block = 1000;
for (int i = 1; i <= n; i++) {
// 区分 部分度数超过block的点为大点 没有超过的为小点
if (d[i] > block) {
vis[i] = 1; // 标记大点
}
}
for (int i = 1; i <= m; i++) {
if (vis[v[i]]) // 当v[i]是大点时 需要对v[i]建线段树 如果u[i]要修改权值 则v[i]对应的线段树也要修改
E[u[i]].push_back(v[i]);
if (vis[u[i]])
E[v[i]].push_back(u[i]);
}
for (int i = 1; i <= n; i++) {
if (vis[i]) {
id[i] = ++dfn; // 记录点i对应的是哪颗线段树
ST[dfn].build(1, 1, d[i] + 1);
for (int j : e[i]) {
if (a[j] <= d[i])
ST[dfn].add(1, a[j], 1);
}
}
}
int q, op, x, y;
cin >> q;
while (q--) {
cin >> op;
if (op == 1) {
//将 a[x]修改成y
cin >> x >> y;
y++;
for (int z:E[x]) {
if (vis[z]) { // 如果相连的是大点
if (a[x] <= d[z])
ST[id[z]].add(1, a[x], -1);
if (y <= d[z]) // 如果y值比点z的度数还大 显然y不可能是F(z)的答案
ST[id[z]].add(1, y, 1);
}
}
a[x] = y;
} else { //op=2
cin >> x;
if (vis[x]) {
cout << ST[id[x]].query(1) - 1 << endl;
} else {
// 用set会t
for (int i = 1; i <= d[x]; i++)
Hash[i] = 0;
for (int z:e[x]) {
if (a[z] > d[x])continue;
Hash[a[z]] = 1;
}
int res = 1;
for (int i = 1; i <= d[x]; i++)
if (!Hash[i]) {
res = i;
break;
}
cout << res - 1 << endl;
}
}
}
}
return 0;
}
Hunting Monsters
Integral Calculus
Leading Robots
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double eps = 1e-8;
const int N = 1e6 + 10;
int n;
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x < 0) return -1;
else return 1;
}
//判断小数和0是否等于
struct Line {
// y=kx+b
ll k, b;
bool operator==(const Line line) const {
return k == line.k && b == line.b;
}
bool operator<(const Line line) const {
if (b == line.b) {
return k < line.k;
}
return b > line.b;
}
} L[N];
int Stack[N], top = 0;
int check(Line l1, Line l2, Line l3) {
double x1 = (double) (l2.b - l1.b) / (double) (l1.k - l2.k); // 第一和第二的交点
double x2 = (double) (l3.b - l2.b) / (double) (l2.k - l3.k); // 新的直线和第二的交点
if (sgn(x1 - x2) > 0) {
return 1;
} else if (sgn(x1 - x2) == 0) {
return 2;
}
return 3;
// 1 (x1,y1)在 (x2,y2)的左边
// 2 (x1,y1)和(x2,y2)是同一个点
// 3 (x1,y1)在 (x2,y2)的右边
}
int vis[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
ll p, a;
cin >> T;
while (T--) {
// init
top = 0;
cin >> n;
// y=1/2at^2+p -> 2y=a(t^2)+2p
for (int i = 1; i <= n; i++) {
cin >> p >> a;
L[i].k = a;
L[i].b = p * 2;
// init
vis[i] = 0; // 用于判断是否重线
}
sort(L + 1, L + 1 + n);
for (int i = 1, first, second, x; i <= n; i++) {
if (top && L[first = Stack[top]].k >= L[i].k) { // L[i]永远追不上第一
if (L[first] == L[i]) vis[first] = 1;//同一条直线 第一也不能要
continue;
}
if (top < 2) {
// 但是也存在后面的点和第一是同一个起点 加速度比第一大 即一开始大家都是第一 后一秒L[i]超过第一
if (top == 1 && L[first].b == L[i].b) {
Stack[top] = i;
} else
Stack[++top] = i;
} else {//top>=2
// 如果第一与第二的交点 在 第二与L[i]的右边 说明L[i]比第一先成领跑
while (top > 1 && check(L[first], L[second = Stack[top - 1]], L[i]) != 3) {
first = second;
top--;//踢掉第一
}
Stack[++top] = i;
}
}
int res = 0;
for (int i = 1; i <= top; i++) {
if (!vis[Stack[i]]) {
res++;
}
}
cout << res << endl;
}
return 0;
}
Math is Simple
Minimum Index
Mow
手工除草每平米 A A A 元,半径为 r r r 圆形除草除草每平米 B B B 元,问一个多边形草地的最小除草花费
除草机能工作到的面积 = 多边形内核往里缩 r r r 米的新内核的面积 + 新内核的周长 × r \times r ×r + r 2 π r^2\pi r2π
借个别人的图 - 图片出处
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m, k;
const double eps = 1e-8;
const double pi = acos(-1.0);
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x < 0) return -1;
else return 1;
}
//判断小数和0是否等于
// ------------ 点、向量 ------------
struct Point {
double x, y;
Point() {}
Point(double _x, double _y) { x = _x, y = _y; }
void input() {
cin >> x >> y;
}//输入
Point operator-(const Point &b) const { return Point(x - b.x, y - b.y); }
Point operator+(const Point &b) const { return Point(x + b.x, y + b.y); }
double operator^(const Point &b) const { return x * b.y - y * b.x; }
//叉积
double operator*(const Point &b) const { return x * b.x + y * b.y; }
//点积
double len() { return hypot(x, y); }
//返回长度
double distance(Point p) { return hypot(x - p.x, y - p.y); }
//两点之间的距离
Point trunc(double r) {
double l = len();
if (!sgn(l)) return *this;
r /= l;
return Point(x * r, y * r);
}//化为长度为r的向量
Point rotLeft() { return Point(-y, x); }
//逆时针旋转90°
Point rotRight() { return Point(y, -x); }
//顺时针旋转90°
Point normal(const bool rsh = false) {
return rsh ? rotLeft() : rotRight();
}// 法向量
};
typedef Point Vector;
// ------------ 直线 ------------
struct Line {
Point s, e;
Vector v;
double ang;
Line() {}
Line(Point s1, Point e1) : s(s1), e(e1) {
v = e1 - s1;
ang = atan2(v.y, v.x);
}
bool operator<(const Line &L) const {
return ang < L.ang;
}//用于极角排序
int relation(Point p) {
int c = sgn((p - s) ^ (e - s));
if (c < 0) return 1; //点在直线的左侧
else if (c > 0) return 2;//点在直线的右侧
else return 3;//点在直线上
}//判断点与直线的关系
Point cross_point(Line v) {
double a1 = (v.e - v.s) ^(s - v.s);
double a2 = (v.e - v.s) ^(e - v.s);
return Point((s.x * a2 - e.x * a1) / (a2 - a1), (s.y * a2 - e.y * a1) / (a2 - a1));
}//求两直线的交点 前提:要保证直线不平行或重合
bool onLeft(Point p) {
return relation(p) == 1;
}
};
namespace Polygon { //多边形板子
// 求多边形的面积
// 顺时针还是逆时针都可 最终都会搞成逆时针
double polygon_area(vector<Point> &p) {
const int n = p.size();
double res = 0;
for (int i = 0; i < n; i++) {
res += p[i] ^ p[(i + 1) % n];
}
res /= 2;
if (sgn(res) < 0) {
reverse(p.begin(), p.end());
res *= -1;
}
return res;
}
// 半平面交求内核
vector<Point> HPI(vector<Line> L) {
const int n = L.size();
sort(L.begin(), L.end());
int first, last;//指向双端队列首尾元素
vector<Point> p(n);//两个相邻半平面的焦点
vector<Line> q(n);//模拟双端队列
vector<Point> res;//半平面交形成的凸包
q[first = last = 0] = L[0];
for (int i = 1; i < n; i++) {
//情况1 L[i]覆盖原队尾:删除尾元素 的半平面
while (first < last && !L[i].onLeft(p[last - 1])) last--;
//情况2 L[i]覆盖原队首:删除首元素的半平面
while (first < last && !L[i].onLeft(p[first])) first++;
q[++last] = L[i];//将当前的半平面加入双端队列的队尾
//极角相同的两个半平面保留左边的那个
if (sgn(q[last].v ^ q[last - 1].v) == 0) {//根据直线代表的向量判断是否平行
last--;
if (q[last].onLeft(L[i].s)) q[last] = L[i];
}
//计算队尾无用的半平面
if (first < last) p[last - 1] = q[last - 1].cross_point(q[last]);
}
//情况3 L[i]不能加入到队列: 删除队列首尾无用的半平面
while (first < last && !q[first].onLeft(p[last - 1])) last--;
if (last - first <= 1) return res;//空集
p[last] = q[last].cross_point(q[first]);//计算队列首尾的交点
return res = vector<Point>(p.begin() + first, p.begin() + last + 1);
}
}
using namespace Polygon;
ll r;//半径
ll A, B;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin >> T;
for (int cs = 1; cs <= T; cs++) {
cin >> n >> r >> A >> B;
vector<Point> p(n);
for (int i = 0; i < n; i++) {
p[i].input();
}
//全手工费用
double s1 = polygon_area(p);
double res1 = s1 * A;
vector<Line> L;
for (int i = 0; i < n; i++) {
Vector nml = (p[(i + 1) % n] - p[i]).normal(1).trunc(r);//长度为r的法向量
L.push_back({p[i] + nml, p[(i + 1) % n] + nml});// 直线向内平移r个单位 <- 重点
}
vector<Point> core = HPI(L);
if (!core.empty()) {
m = core.size();
double c = 0;//周长
for (int i = 0; i < m; i++) {
c += core[i].distance(core[(i + 1) % m]);
}
// 新的面积 = 内核面积 + 内核周长*r + 一个圆
double s2 = polygon_area(core) + c * r + pi * r * r;
res1 = min(res1, (s1 - s2) * A + s2 * B);
}
// 输出20位数字 cout写法
cout << fixed << setprecision(20) << res1 << endl;
}
return 0;
}