B Light
题意:给定一个凸多边形 { C n } \{C_n\} {Cn},在这一多边形向内延申 w w w 的范围有一堵高为 h h h 的墙,墙内多边形记为 C ′ C' C′。现在在点 L ( x , y , z ) L(x,y,z) L(x,y,z) 处有一点光源,问该点光源照射到 C ′ C' C′ 的面积。 n ≤ 2 × 1 0 3 n \leq 2\times 10^3 n≤2×103。
解法:对于凸多边形,向内缩 w w w 可以使用半平面交:对于每条原多边形的边 ( P , v ⃗ ) (P,\vec{v}) (P,v),向内缩 w w w 即是一个半平面 ( P + w ∣ v ′ ⃗ ∣ v ′ ⃗ , v ⃗ ) \left(P+\dfrac{w}{|\vec{v'}|}\vec{v'},\vec{v}\right) (P+∣v′∣wv′,v),其中 v ′ ⃗ \vec{v'} v′ 表示 v ⃗ \vec{v} v 沿逆时针方向旋转 π 2 \dfrac{\pi}{2} 2π 所得向量。
对于点光源照射部分,对于每堵墙的限制,只需要判断 L L L 是在该墙 ( P , v ⃗ ) (P,\vec{v}) (P,v) 的左侧还是右侧。若为左侧,则墙无法阻拦光线;否则墙就会有阻挡作用。只有当 z > h z>h z>h 时才会有光射入该半平面,延申长度可以使用相似三角形求出——该操作就是继续对该内缩的凸多边形继续内缩,只是这次内缩距离是由点光源到墙的距离给出的。
因而使用半平面交即可轻松求出面积。总复杂度 O ( n 2 ) \mathcal O(n^2) O(n2)。
只放上主函数:
int main()
{
int t, n;
double h, x, y, z, w;
scanf("%d", &t);
while(t--)
{
scanf("%d%lf%lf", &n, &h, &w);
auto nx = [&](int x)
{
return (x + 1) % n;
};
vector<Point> que(n);
for (int i = 0; i < n;i++)
scanf("%lf%lf", &que[i].x, &que[i].y);
scanf("%lf%lf%lf", &x, &y, &z);
Convex out = convexhull(que);
vector<Line> shrink(n);
for (int i = 0; i < n;i++)
{
shrink[i].v = out.p[nx(i)] - out.p[i];
auto dir = out.p[nx(i)] - out.p[i];
auto verdir = (Point){-dir.y, dir.x};
shrink[i].p = out.p[i] + verdir * (w / verdir.len());
}
auto inner = halfinter(shrink);
if (inner.size() <= 2)
{
printf("0\n");
continue;
}
auto newinner = inner;
Point light = (Point){x, y};
bool flag = 0;
auto ori = halfint_to_convex(inner);
double outer = ori.area();
for (auto i : inner)
{
auto res = i.toleft(light);
if (res == -1)
{
if (z <= h)
{
flag = 1;
break;
}
double light_to_wall = i.dis(light);
double verdis = light_to_wall * h / (z - h);
Point verdir = (Point){-i.v.y, i.v.x};
Line nowline;
nowline.v.x = i.v.x;
nowline.v.y = i.v.y;
nowline.p = i.p + verdir * (verdis / verdir.len());
newinner.push_back(nowline);
}
}
if(flag)
{
printf("0\n");
continue;
}
auto ans = halfinter(newinner);
auto shade = halfint_to_convex(ans).area();
printf("%.10lf\n", shade);
}
return 0;
}
C Link with Nim Game
题意:给定 n n n 堆石子 { a n } \{a_n\} {an} 玩 Nim 游戏,若该玩家必输则会尽量拖延时间,否则就会速战速决。问游戏会进行多少轮,并求出先手第一步的方案数。 n ≤ 2 × 1 0 5 n \leq 2\times 10^5 n≤2×105。
解法:判定输赢根据异或和是否为零。记 ⊕ a i = w \oplus a_i=w ⊕ai=w。
若先手必胜,则先手必然尽量取石子,使得剩余异或和为 0 0 0。对于第 i i i 堆石子,先手必然需要拿走 a i ⊕ ( a i − w ) a_i \oplus (a_i-w) ai⊕(ai−w) 个石子,才能使得异或和为 0 0 0。因而先手选择一个最大的 x = a i ⊕ ( a i − w ) x=a_i \oplus (a_i-w) x=ai⊕(ai−w) 作为第一次拿的个数,总轮数就是 ∑ a i − x + 1 \sum a_i -x+1 ∑ai−x+1(因为此时变成了后手必输结局,方案数下证),方案数就是有多少个数字满足 a i ⊕ ( a i − w ) = max ( a j ⊕ ( a j − w ) ) a_i \oplus (a_i-w) = \max(a_j \oplus (a_j-w)) ai⊕(ai−w)=max(aj⊕(aj−w))。
若先手必输,则必然会进行 ∑ a i \sum a_i ∑ai 轮——每次选取一个 l o w b i t {\rm lowbit} lowbit 最小的数字,记它的 l o w b i t \rm lowbit lowbit 为 v v v,然后取一个。此时异或值为 2 v − 1 2v-1 2v−1。后手为了保证必胜,需要让异或值回到 0 0 0,但是先手只能同样取 l o w b i t \rm lowbit lowbit 也为 v v v 的数字取一个——取任意 l o w b i t \rm lowbit lowbit 更高的数字,都会导致更高位的 1 1 1 的变化,使得异或值无法回到 0 0 0。因而只能和对手下对称棋,所以一轮都只能拿一个。
考虑先手必输情况下先手的方案数,只需要让先手取了一个石头之后后手不能取走超过一个石头即可。依次考虑 a i a_i ai 每一个为 1 1 1 的位置 v v v,分两种情况——
- 作为 l o w b i t \rm lowbit lowbit 出现。记录一下每一位作为 l o w b i t \rm lowbit lowbit 的次数,如果第一步选择该石头,则异或值变化 2 v − 1 2v-1 2v−1。
- 不作为 l o w b i t \rm lowbit lowbit 出现。那么如果别的石头 j j j 里面有以这一位作为 l o w b i t \rm lowbit lowbit 的,那么 j j j 是不可以选择的——因为后手可以从这里抓取 2 v − l o w b i t ( a i ) > 1 2v-{\rm lowbit}(a_i)>1 2v−lowbit(ai)>1 个石头,同样可以做到异或值变化 2 v − 1 2v-1 2v−1。
之所以不考虑 0 0 0 是因为 0 0 0 不会影响到减一对 l o w b i t \rm lowbit lowbit 的传递作用。
#include <bits/stdc++.h>
using namespace std;
const long long inf = 0x3f3f3f3f3f3f3f3fll;
int main()
{
int t, n;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
vector<long long> a(n);
long long sum = 0, xorsum = 0;
for (auto &x : a)
{
scanf("%lld", &x);
sum += x;
xorsum ^= x;
}
if (xorsum)
{
long long maximum = 0;
int way = 0;
for (auto i : a)
if (i > (i ^ xorsum))
{
long long now = i - (i ^ xorsum);
if (now > maximum)
{
maximum = now;
way = 1;
}
else if (now == maximum)
way++;
}
printf("%lld %d\n", sum - maximum + 1, way);
}
else
{
vector<int> ban(31, 0), cnt(31, 0);
for (auto i : a)
for (int j = 30; j >= 0;j--)
if ((i >> j) & 1)
{
if ((1 << j) == (i & (-i)))
cnt[j]++;
else
ban[j] = 1;
}
printf("%lld ", sum);
int way = 0;
for (int i = 0; i <= 30;i++)
if (!ban[i])
way += cnt[i];
printf("%d\n", way);
}
}
return 0;
}
D Link with Game Glitch
题意:给定 n n n 个物品和 m m m 种物品合成方式 ( a , b , c , d ) (a,b,c,d) (a,b,c,d): k a ka ka 个 b b b 物品合成 k c kc kc 个 d d d 物品, k ∈ R + k \in \R^+ k∈R+。原定方式会导致出现无穷多个物品,现在需要求出最大的参数 w w w,使得每个合成方式均变成 ( a , b , w c , d ) (a,b,wc,d) (a,b,wc,d)。求最大的实数 w w w。 n , m , a , c ≤ 1 × 1 0 3 n,m,a,c \leq 1\times 10^3 n,m,a,c≤1×103。
解法:将合成方式抽象成有向图,边权为 c a \dfrac{c}{a} ac,原题即是找到最大的 w w w 使得图上边权都变成 w c a \dfrac{wc}{a} awc,图上无边权乘积超过 1 1 1 的环。由于边权乘积太大,因而考虑取对数,则题意变成边权为 log c − log a \log c-\log a logc−loga,求最大的 log w \log w logw 使得图上无正环。显然 w w w 有单调性,因而二分答案然后使用 spfa 判断正环是否存在即可。复杂度 O ( n m log w ) \mathcal O(nm \log w) O(nmlogw)。
#include <bits/stdc++.h>
using namespace std;
const long double inf = 1e12;
const int M = 2000, N = 1000;
struct line
{
int from;
int to;
long double v;
int next;
};
line que[M + 5];
int cnt, headers[N + 5];
void add(int from, int to, long double v)
{
cnt++;
que[cnt].from = from;
que[cnt].to = to;
que[cnt].v = v;
que[cnt].next = headers[from];
headers[from] = cnt;
}
bool spfa(int n, long double w)
{
vector<int> vis(n + 1, 0), times(n + 1, 0);
vector<long double> dis(n + 1, -inf);
queue<int> q;
for (int i = 1; i <= n;i++)
{
q.push(i);
vis[i] = 1;
}
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = headers[u]; i; i = que[i].next)
if (dis[que[i].to] < dis[u] + w + que[i].v)
{
dis[que[i].to] = dis[u] + w + que[i].v;
times[que[i].to] = times[u] + 1;
if (vis[que[i].to] == 0)
{
vis[que[i].to] = 1;
q.push(que[i].to);
}
if (times[que[i].to] > n)
return false;
}
}
return true;
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1, b, d; i <= m;i++)
{
long double a, c;
scanf("%Lf%d%Lf%d", &a, &b, &c, &d);
add(b, d, log2(c) - log2(a));
}
long double left = 1.0 / 1000, right = 1;
for (int times = 1; times <= 50;times++)
{
long double mid = (left + right) / 2;
if (!spfa(n, log2(mid)))
right = mid;
else
left = mid;
}
printf("%.15Lf", (left + right) / 2);
return 0;
}
E Falfa with Substring
题意:给定长度
n
n
n,问长度为
n
n
n 的纯小写字母串中出现
k
k
k 次 bit
的字符串个数,需要对
k
∈
[
0
,
n
]
k \in [0,n]
k∈[0,n] 输出答案。
n
≤
1
×
1
0
6
n\leq 1\times 10^6
n≤1×106。
解法:对于恰好类问题,通常将其转化为至少或至多。
若至少出现
k
k
k 次 bit
,因为这个字符串不会重叠出现,因而可以将其绑定成一个特殊字符,等效于在
n
−
2
k
n-2k
n−2k 个位置中选择
k
k
k 个位置填放 bit
,剩余位置任选。因而至少
k
k
k 次出现的方案数为
F
k
=
(
n
−
2
k
k
)
2
6
n
−
3
k
\displaystyle F_k={n-2k\choose k}26^{n-3k}
Fk=(kn−2k)26n−3k。这部分可以
O
(
n
)
\mathcal O(n)
O(n) 的求出。
接下来考虑容斥:恰有
k
k
k 个出现的需要从
k
′
,
k
′
>
k
k',k'>k
k′,k′>k 处转移。对于某一个
k
’
k’
k’,其转移方案应当是从恰好
k
′
k'
k′ 个 bit
的方案中选择出 k
个出来作为最终恰好的部分,剩余的都要扔掉。因而恰好
k
k
k 个的方案数为
G
k
=
F
k
−
∑
k
′
=
k
+
1
n
(
k
′
k
)
G
k
′
\displaystyle G_k=F_k-\sum_{k'=k+1}^n {k' \choose k}G_{k'}
Gk=Fk−k′=k+1∑n(kk′)Gk′。
显然该式子还是没办法快速计算。注意到
F
i
=
∑
j
=
i
n
(
j
i
)
G
j
\displaystyle F_i=\sum_{j=i}^{n} {j \choose i} G_{j}
Fi=j=i∑n(ij)Gj,因而利用二项式反演得到
G
i
=
∑
j
=
i
n
(
−
1
)
j
−
i
(
j
i
)
F
j
\displaystyle G_i=\sum_{j=i}^n (-1)^{j-i}{j\choose i}F_j
Gi=j=i∑n(−1)j−i(ij)Fj。将其进行整理:
G
i
=
∑
j
=
i
n
(
−
1
)
j
−
i
(
j
i
)
F
j
=
∑
j
=
i
n
(
−
1
)
j
−
i
j
!
F
j
(
i
!
)
(
j
−
i
)
!
i
!
G
i
=
∑
j
=
i
n
(
−
1
)
j
−
i
(
j
−
i
)
!
(
j
!
F
j
)
\begin{aligned} G_i=&\sum_{j=i}^n(-1)^{j-i} {j \choose i} F_j\\ =&\sum_{j=i}^n (-1)^{j-i}\dfrac{j!F_j}{(i!)(j-i)!}\\ i!G_i=&\sum_{j=i}^n\dfrac{(-1)^{j-i}}{(j-i)!}(j!F_j) \end{aligned}
Gi==i!Gi=j=i∑n(−1)j−i(ij)Fjj=i∑n(−1)j−i(i!)(j−i)!j!Fjj=i∑n(j−i)!(−1)j−i(j!Fj)
注意到
j
−
(
j
−
i
)
=
i
j-(j-i)=i
j−(j−i)=i 为一定值,因而构成差卷积形式。使用 NTT 可以加速这一过程,总复杂度
O
(
n
log
n
)
\mathcal O(n \log n)
O(nlogn)。
F NIO with String Game
题意:给定 n n n 个串 { T n } \{T_n\} {Tn} 和一个串 S S S,有以下四种操作:
- 给 T x T_x Tx 的末尾增加一个字符;
- 给 S S S 串末尾删除 p p p 个字符;
- 给 S S S 串末尾插入 k k k 个字符;
- 查询 { T n } \{T_n\} {Tn} 中有多少个字符串字典序严格小于 S S S。
q
q
q 次操作,
n
,
q
≤
5
×
1
0
5
n,q \leq 5\times 10^5
n,q≤5×105,字符串总长小于等于
2
×
1
0
5
2\times 10^5
2×105。
解法:根据
{
T
n
}
\{T_n\}
{Tn} 离线建立 Trie,加入最终情况的
{
T
n
}
\{T_n\}
{Tn},然后将 Trie 树欧拉序化,上建立 BIT,只在
T
T
T 串末尾对应节点标
1
1
1 表示一次出现。但是初始情况下打上末尾标记的是初始的
{
T
n
}
\{T_n\}
{Tn},随着不断加入字符会将这一标记不断移动。
维护一个栈来记录 S S S,相同字符压缩存储。同时 Trie 上进行树上倍增,记录从当前节点开始沿 c h ch ch 字符走 2 k 2^k 2k 次所能到的节点。查询操作的时候,对于严格小的串,其欧拉序一定严格在当前节点前面。对于恰好在当前节点的,比较栈中长度和 Trie 树上节点对应的 T T T 串长度,相同就不计入。
一些细节:如果当前节点无对应字符出边,则倍增的时候就认为它沿这一字符出边还是走到它自己。此时为了统计 S S S 真实情况下已经失配但是还在 Trie 树上的情况,记录一个 S S S 串真实长度,对于子树下小于当前 S S S 的失配字符 c h ch ch 的字符出边,统计下对应的子树大小。
总复杂度 O ( n + q ) log n \mathcal O(n+q) \log n O(n+q)logn。
G Link with Monotonic Subsequence
题意:构造一个长度为 n n n 的排列,使得其最长上升子序列和最长下降子序列长度的最大值尽可能小。
解法:考虑如下的构造: ( k , k − 1 , k − 2 , ⋯ , 1 ) , ( 2 k , 2 k − 1 , 2 k − 2 , ⋯ , k + 1 ) , ⋯ , ( n , n − 1 , ⋯ , ⌊ n k ⌋ k + 1 ) (k,k-1,k-2,\cdots,1),(2k,2k-1,2k-2,\cdots,k+1),\cdots,\left (n,n-1,\cdots,\left \lfloor \dfrac{n}{k}\right \rfloor k+1 \right) (k,k−1,k−2,⋯,1),(2k,2k−1,2k−2,⋯,k+1),⋯,(n,n−1,⋯,⌊kn⌋k+1),则答案为 max ( k , ⌈ n k ⌉ ) \max(k,\left \lceil \dfrac{n}{k}\right \rceil) max(k,⌈kn⌉)。因而取 k = ⌈ n ⌉ k=\left \lceil \sqrt{n} \right \rceil k=⌈n⌉ 即可。
#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define IL inline
#define LL long long
#define LF long double
using namespace std;
const int N=1e6+3;
int n,a[N];
IL int in(){
char c;int f=1;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
int x=c-'0';
while((c=getchar())>='0'&&c<='9')
x=x*10+c-'0';
return x*f;
}
int main()
{
int t=in();
while(t--){
n=in();
int x=ceil(sqrt(n));
for(int i=1;(i-1)*x<n;++i){
int cnt=0;
for(int j=(i-1)*x+1;j<=min(i*x,n);++j)
a[++cnt]=j;
reverse(a+1,a+cnt+1);
for(int j=1;j<=cnt;++j) printf("%d ",a[j]);
}
puts("");
}
return 0;
}
H Take the Elevator
题意:有 n n n 个人要坐电梯,第 i i i 个人要从 a i a_i ai 坐到 b i b_i bi,电梯有容量限制 m m m,且电梯下行时想恢复上行,必须先下到 1 1 1 层。问运输完这些人最少需要多少时间。 n , m ≤ 2 × 1 0 5 n,m \leq 2\times 10^5 n,m≤2×105。
解法:将所有人分成两类:从低到高和从高到低的,分开考虑。从上到下的可以反转过来变成从下到上的。
对于电梯的一次上行,首先取出 b i b_i bi 最高的——他必然需要一次覆盖。同时维护一个电梯中人员序列,仅记录其上电梯的时刻。当该序列长度小于 m m m 时,继续取 b i b_i bi 高的——能尽量带走更多的 b i b_i bi 大的一定不劣。若电梯已满,则只能选择进电梯楼层最高的还没上之前就把人运到,因而选择最大的、同时不超过序列中 l max l_{\max} lmax 的 r r r 加入电梯。这样可以维护出一次电梯能带走多少人,记录最高楼层。
对于上行和下行均做同样的操作。电梯的一次运行必然可以同时满足一次上行和一次下行,因而对其做归并即可。
总时间复杂度 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
#include <bits/stdc++.h>
using namespace std;
int n, m, k;
vector<long long> cal(vector<pair<long long, long long>> &que)
{
sort(que.begin(), que.end());
multiset<long long> s;
for (auto i : que)
{
auto it = s.upper_bound(i.first);
if (it != s.begin())
s.erase(prev(it));
s.insert(i.second);
}
vector<long long> ans;
while(!s.empty())
{
int times = m;
ans.push_back(*s.rbegin());
while(!s.empty() && times--)
s.erase(s.find(*s.rbegin()));
}
return ans;
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
vector<pair<long long,long long>> up, down;
for (int i = 1, a, b; i <= n;i++)
{
scanf("%d%d", &a, &b);
if (a < b)
up.emplace_back(a, b);
else
down.emplace_back(b, a);
}
auto f1 = cal(up), f2 = cal(down);
long long ans = 0;
for (int i = 0; i < max(f1.size(), f2.size()); i++)
{
long long d1 = i < f1.size() ? f1[i] : 0;
long long d2 = i < f2.size() ? f2[i] : 0;
ans += 2 * (max(d1, d2) - 1);
}
printf("%lld", ans);
return 0;
}
I let fat tension
题意:给定 n n n 个 k k k 维向量 { X i } \{X_i\} {Xi},和 n n n 个 d d d 维向量 { Y i } \{Y_i\} {Yi},求 Y j ′ = ∑ i = 1 n X i ⋅ X j ∣ X i ∣ ∣ X j ∣ Y i \displaystyle Y'_j=\sum_{i=1}^n \dfrac{X_i\cdot X_j}{|X_i||X_j|} Y_i Yj′=i=1∑n∣Xi∣∣Xj∣Xi⋅XjYi。 n ≤ 1 × 1 0 4 n \leq 1\times 10^4 n≤1×104, k , d ≤ 50 k,d \leq 50 k,d≤50。
解法:
[
Y
1
′
Y
2
′
Y
3
′
⋮
Y
n
′
]
=
[
X
1
⋅
X
1
∣
X
1
∣
∣
X
1
∣
X
1
⋅
X
2
∣
X
1
∣
∣
X
2
∣
X
1
⋅
X
3
∣
X
1
∣
∣
X
3
∣
⋯
X
1
⋅
X
n
∣
X
1
∣
∣
X
n
∣
X
2
⋅
X
1
∣
X
2
∣
∣
X
1
∣
X
2
⋅
X
2
∣
X
2
∣
∣
X
2
∣
X
2
⋅
X
3
∣
X
2
∣
∣
X
3
∣
⋯
X
2
⋅
X
n
∣
X
2
∣
∣
X
n
∣
X
3
⋅
X
1
∣
X
3
∣
∣
X
1
∣
X
3
⋅
X
2
∣
X
3
∣
∣
X
2
∣
X
3
⋅
X
3
∣
X
3
∣
∣
X
3
∣
⋯
X
3
⋅
X
n
∣
X
3
∣
∣
X
n
∣
⋮
⋮
⋮
⋱
⋮
X
n
⋅
X
1
∣
X
n
∣
∣
X
1
∣
X
n
⋅
X
2
∣
X
n
∣
∣
X
2
∣
X
n
⋅
X
3
∣
X
n
∣
∣
X
3
∣
⋯
X
n
⋅
X
n
∣
X
n
∣
∣
X
n
∣
]
[
Y
1
Y
2
Y
3
⋮
Y
n
]
\begin{bmatrix} Y_1'\\ Y_2'\\ Y_3'\\ \vdots\\ Y_n'\\ \end{bmatrix}=\begin{bmatrix} \dfrac{X_1 \cdot X_1}{|X_1||X_1|} & \dfrac{X_1 \cdot X_2}{|X_1||X_2|} & \dfrac{X_1 \cdot X_3}{|X_1||X_3|} & \cdots &\dfrac{X_1 \cdot X_n}{|X_1||X_n|}\\ \dfrac{X_2 \cdot X_1}{|X_2||X_1|} & \dfrac{X_2 \cdot X_2}{|X_2||X_2|} & \dfrac{X_2 \cdot X_3}{|X_2||X_3|} & \cdots &\dfrac{X_2 \cdot X_n}{|X_2||X_n|}\\ \dfrac{X_3 \cdot X_1}{|X_3||X_1|} & \dfrac{X_3 \cdot X_2}{|X_3||X_2|} & \dfrac{X_3 \cdot X_3}{|X_3||X_3|} & \cdots &\dfrac{X_3 \cdot X_n}{|X_3||X_n|}\\ \vdots & \vdots &\vdots &\ddots &\vdots\\ \dfrac{X_n \cdot X_1}{|X_n||X_1|} & \dfrac{X_n \cdot X_2}{|X_n||X_2|} & \dfrac{X_n \cdot X_3}{|X_n||X_3|} & \cdots &\dfrac{X_n \cdot X_n}{|X_n||X_n|}\\ \end{bmatrix} \begin{bmatrix} Y_1\\ Y_2\\ Y_3\\ \vdots\\ Y_n\\ \end{bmatrix}
⎣
⎡Y1′Y2′Y3′⋮Yn′⎦
⎤=⎣
⎡∣X1∣∣X1∣X1⋅X1∣X2∣∣X1∣X2⋅X1∣X3∣∣X1∣X3⋅X1⋮∣Xn∣∣X1∣Xn⋅X1∣X1∣∣X2∣X1⋅X2∣X2∣∣X2∣X2⋅X2∣X3∣∣X2∣X3⋅X2⋮∣Xn∣∣X2∣Xn⋅X2∣X1∣∣X3∣X1⋅X3∣X2∣∣X3∣X2⋅X3∣X3∣∣X3∣X3⋅X3⋮∣Xn∣∣X3∣Xn⋅X3⋯⋯⋯⋱⋯∣X1∣∣Xn∣X1⋅Xn∣X2∣∣Xn∣X2⋅Xn∣X3∣∣Xn∣X3⋅Xn⋮∣Xn∣∣Xn∣Xn⋅Xn⎦
⎤⎣
⎡Y1Y2Y3⋮Yn⎦
⎤
考虑
X
i
′
=
X
i
∣
X
i
∣
X_i'=\dfrac{X_i}{|X_i|}
Xi′=∣Xi∣Xi,则有:
[
Y
1
′
Y
2
′
Y
3
′
⋮
Y
n
′
]
=
[
X
1
′
X
2
′
X
3
′
⋮
X
n
′
]
[
X
1
′
X
2
′
X
3
′
⋯
X
n
′
]
[
Y
1
Y
2
Y
3
⋮
Y
n
]
\begin{bmatrix} Y_1'\\ Y_2'\\ Y_3'\\ \vdots\\ Y_n'\\ \end{bmatrix}= \begin{bmatrix} X_1'\\ X_2'\\ X_3'\\ \vdots\\ X_n' \end{bmatrix} \begin{bmatrix} X_1'&X_2'&X_3'&\cdots &X_n' \end{bmatrix} \begin{bmatrix} Y_1\\ Y_2\\ Y_3\\ \vdots\\ Y_n\\ \end{bmatrix}
⎣
⎡Y1′Y2′Y3′⋮Yn′⎦
⎤=⎣
⎡X1′X2′X3′⋮Xn′⎦
⎤[X1′X2′X3′⋯Xn′]⎣
⎡Y1Y2Y3⋮Yn⎦
⎤
注意到右侧三个矩阵大小分别为
(
n
,
k
)
(n,k)
(n,k),
(
k
,
n
)
(k,n)
(k,n),
(
n
,
d
)
(n,d)
(n,d),因而先计算后面两个矩阵的乘积再计算和第一个的乘积,复杂度仅为
O
(
2
k
n
d
)
\mathcal O(2knd)
O(2knd)。
#include <bits/stdc++.h>
using namespace std;
vector<vector<double>> operator*(vector<vector<double>> a, vector<vector<double>> b)
{
int n = a.size(), m = a[0].size(), k = b[0].size();
vector<vector<double>> ans(n, vector<double>(k, 0));
for (int i = 0; i < n;i++)
for (int j = 0; j < k;j++)
for (int l = 0; l < m;l++)
ans[i][j] += a[i][l] * b[l][j];
return ans;
}
int main()
{
int n, k, d;
scanf("%d%d%d", &n, &k, &d);
vector<vector<double>> a(n, vector<double>(k)), b(k, vector<double>(n)), c(n, vector<double>(d));
for (int i = 0; i < n;i++)
{
double all = 0;
for (int j = 0; j < k;j++)
{
scanf("%lf", &a[i][j]);
all += a[i][j] * a[i][j];
}
all = sqrt(all);
for (int j = 0; j < k; j++)
{
a[i][j] /= all;
b[j][i] = a[i][j];
}
}
for (int i = 0; i < n;i++)
for (int j = 0; j < d;j++)
scanf("%lf", &c[i][j]);
auto ans = a * (b * c);
for(auto i:ans)
{
for (auto j : i)
printf("%.10lf ", j);
printf("\n");
}
return 0;
}
J Link with Arithmetic Progression
题意:给定 n n n 个点 ( i , a i ) (i,a_i) (i,ai),求作一直线 l : y = k x + b l:y=kx+b l:y=kx+b 使得 ∑ ( a i − y i ) 2 \sum (a_i-y_i)^2 ∑(ai−yi)2 最小。
解法:最小二乘法:
k
^
=
∑
i
=
1
n
x
i
y
i
−
n
x
‾
y
‾
∑
i
=
1
n
x
i
2
−
n
x
‾
2
b
^
=
y
‾
−
k
^
\widehat{k}=\dfrac{\sum_{i=1}^nx_iy_i-n\overline{x}\overline{y}}{\sum_{i=1}^n x_i^2-n\overline{x}^2}\\ \widehat{b}=\overline{y}-\widehat{k}
k
=∑i=1nxi2−nx2∑i=1nxiyi−nxyb
=y−k
带入公式即可。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t, n;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
vector<long double> num(n + 1);
long double sum = 0, avay = 0, avax = 0, squx = 0;
for (long long i = 1; i <= n;i++)
{
scanf("%Lf", &num[i]);
avay += num[i];
avax += i;
squx += i * i;
sum += i * num[i];
}
long double k = (sum - avay * avax / n) / (squx - avax * avax / n);
long double b = avay / n - k * (avax / n);
long double ans = 0;
for (int i = 1; i <= n;i++)
{
long double dif = num[i] - k * i - b;
ans += dif * dif;
}
printf("%.15Lf\n", ans);
}
return 0;
}
K Link with Bracket Sequence I
题意:给定一个长度为 n n n 的括号序列 S S S,求有多少个合法长度为 m m m 的括号序列是以 S S S 作为其子序列(可以不连续)。多测, T , n , m ≤ 200 T,n,m \leq 200 T,n,m≤200。
解法:考虑三维 dp: f i , j , k f_{i,j,k} fi,j,k 表示原序列长度为 i i i,和 S S S 的最长公共子序列长度为 j j j(尽量优先和 S S S 匹配),左侧有 k k k 个游离左括号的方案数。
则转移有:
- 和当前 S j S_j Sj 相同。 f i , j , k ← f i − 1 , j − 1 , k ′ f_{i,j,k} \leftarrow f_{i-1,j-1,k'} fi,j,k←fi−1,j−1,k′,其中当 S j S_j Sj 为左括号时 k ′ ← k + 1 k' \leftarrow k+1 k′←k+1,反之为 k ′ ← k − 1 k' \leftarrow k-1 k′←k−1。
- 和当前 S j S_j Sj 不同。 f i , j , k ← f i − 1 , j , k ′ f_{i,j,k} \leftarrow f_{i-1,j,k'} fi,j,k←fi−1,j,k′,和上文同理。
注意一定要优先和
S
S
S 匹配:否则对于括号串 ()
,就会有两种 ()()
,一种为新序列是原序列的前两个位置,另一种为后两个位置,但是最终呈现的答案只有一种。
#include <bits/stdc++.h>
using namespace std;
const int N = 200;
char s[N + 5];
const long long mod = 1000000007;
int main()
{
int t, n, m;
scanf("%d", &t);
while(t--)
{
scanf("%d%d%s", &n, &m, s + 1);
vector<vector<vector<int>>> f(m + 1, vector<vector<int>>(n + 1, vector<int>(m + 1, 0)));
f[0][0][0] = 1;
for (int i = 0; i < m; i++)
for (int j = 0; j <= n; j++)
for (int k = 0; k <= i; k++)
{
f[i + 1][j + (s[j + 1] == '(')][k + 1] = (f[i][j][k] + f[i + 1][j + (s[j + 1] == '(')][k + 1]) % mod;
if (k)
f[i + 1][j + (s[j + 1] == ')')][k - 1] = (f[i][j][k] + f[i + 1][j + (s[j + 1] == ')')][k - 1]) % mod;
}
printf("%d\n", f[m][n][0]);
}
return 0;
}
L Link with Level Editor I
题意:给定 n n n 层图,每层图都有 m m m 个节点,和不同的边连接。选择一个子段 [ l , r ] , 1 ≤ l ≤ r ≤ n [l,r],1 \leq l \leq r \leq n [l,r],1≤l≤r≤n,从 1 1 1 号节点出发,每一步可以停留不动或者沿当前层的边走一条边,然后传送到下一层的同一编号节点,到达 m m m 号节点成功。问最小的子段长度。空间非常小, n ≤ 1 × 1 0 4 , m ≤ 2 × 1 0 3 n \leq 1\times 10^4,m \leq 2\times 10^3 n≤1×104,m≤2×103。
解法: f i , j f_{i,j} fi,j 表示在第 i i i 层的节点 j j j,最晚需要从哪一层出发。显然它的转移是只需要本层的图和上层的 dp 数组——要么从上层停留不动,要么从本层的转移边转移,因而使用滚动数组优化即可。时间复杂度 O ( n m ) \mathcal O(nm) O(nm),空间复杂度 O ( m ) \mathcal O(m) O(m)。
#include <bits/stdc++.h>
using namespace std;
const int M = 2000;
int f[2][M + 5];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
int ans = n + 1;
for (int i = 1, l, u, v; i <= n;i++)
{
for (int j = 1; j <= m;j++)
f[i % 2][j] = f[(i ^ 1) % 2][j];
f[(i ^ 1) % 2][1] = i;
scanf("%d", &l);
for (int j = 1; j <= l; j++)
{
scanf("%d%d", &u, &v);
f[i % 2][v] = max(f[i % 2][v], f[(i ^ 1) % 2][u]);
}
if (f[i % 2][m])
ans = min(ans, i - f[i % 2][m] + 1);
}
if(ans > n)
printf("-1");
else
printf("%d", ans);
return 0;
}