2022 年牛客多校第二场补题记录

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 n2×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 n2×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(aiw) 个石子,才能使得异或和为 0 0 0。因而先手选择一个最大的 x = a i ⊕ ( a i − w ) x=a_i \oplus (a_i-w) x=ai(aiw) 作为第一次拿的个数,总轮数就是 ∑ a i − x + 1 \sum a_i -x+1 aix+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(aiw)=max(aj(ajw))

若先手必输,则必然会进行 ∑ 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 2v1。后手为了保证必胜,需要让异或值回到 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,分两种情况——

  1. 作为 l o w b i t \rm lowbit lowbit 出现。记录一下每一位作为 l o w b i t \rm lowbit lowbit 的次数,如果第一步选择该石头,则异或值变化 2 v − 1 2v-1 2v1
  2. 不作为 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 2vlowbit(ai)>1 个石头,同样可以做到异或值变化 2 v − 1 2v-1 2v1

之所以不考虑 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^+ kR+。原定方式会导致出现无穷多个物品,现在需要求出最大的参数 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,c1×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 logcloga,求最大的 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 kbit的字符串个数,需要对 k ∈ [ 0 , n ] k \in [0,n] k[0,n] 输出答案。 n ≤ 1 × 1 0 6 n\leq 1\times 10^6 n1×106

解法:对于恰好类问题,通常将其转化为至少或至多。

若至少出现 k k kbit,因为这个字符串不会重叠出现,因而可以将其绑定成一个特殊字符,等效于在 n − 2 k n-2k n2k 个位置中选择 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=(kn2k)26n3k。这部分可以 O ( n ) \mathcal O(n) O(n) 的求出。

接下来考虑容斥:恰有 k k k 个出现的需要从 k ′ , k ′ > k k',k'>k k,k>k 处转移。对于某一个 k ’ k’ k,其转移方案应当是从恰好 k ′ k' kbit的方案中选择出 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=Fkk=k+1n(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=in(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=in(1)ji(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=in(1)ji(ij)Fjj=in(1)ji(i!)(ji)!j!Fjj=in(ji)!(1)ji(j!Fj)
注意到 j − ( j − i ) = i j-(j-i)=i j(ji)=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,有以下四种操作:

  1. T x T_x Tx 的末尾增加一个字符;
  2. S S S 串末尾删除 p p p 个字符;
  3. S S S 串末尾插入 k k k 个字符;
  4. 查询 { 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,q5×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,k1,k2,,1),(2k,2k1,2k2,,k+1),,(n,n1,,knk+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,m2×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=1nXi∣∣XjXiXjYi n ≤ 1 × 1 0 4 n \leq 1\times 10^4 n1×104 k , d ≤ 50 k,d \leq 50 k,d50

解法:
[ 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} Y1Y2Y3Yn = X1∣∣X1X1X1X2∣∣X1X2X1X3∣∣X1X3X1Xn∣∣X1XnX1X1∣∣X2X1X2X2∣∣X2X2X2X3∣∣X2X3X2Xn∣∣X2XnX2X1∣∣X3X1X3X2∣∣X3X2X3X3∣∣X3X3X3Xn∣∣X3XnX3X1∣∣XnX1XnX2∣∣XnX2XnX3∣∣XnX3XnXn∣∣XnXnXn Y1Y2Y3Yn

考虑 X i ′ = X i ∣ X i ∣ X_i'=\dfrac{X_i}{|X_i|} Xi=XiXi,则有:
[ 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} Y1Y2Y3Yn = X1X2X3Xn [X1X2X3Xn] Y1Y2Y3Yn
注意到右侧三个矩阵大小分别为 ( 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 (aiyi)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=1nxi2nx2i=1nxiyinxyb =yk
带入公式即可。

#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,m200

解法:考虑三维 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 个游离左括号的方案数。

则转移有:

  1. 和当前 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,kfi1,j1,k,其中当 S j S_j Sj 为左括号时 k ′ ← k + 1 k' \leftarrow k+1 kk+1,反之为 k ′ ← k − 1 k' \leftarrow k-1 kk1
  2. 和当前 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,kfi1,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],1lrn,从 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 n1×104,m2×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;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值