Logo S08577 的博客

博客

标签
暂无

CF3D

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-08-25 10:31:24

题意

题目传送门

思路

不妨把 ? 全部换成),用优先队列(小根堆)记录每一个问号将)变成(的代价,如果不合法,就弹出堆首,并将)改成(即可

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<set>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define ENdl Endl
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)
#define fi first
#define se second
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

const int N=2e5+10;\/\/注意修改
const int mod=1e9+7;
const int Max=0x3f3f3f3f3f;

string s;
int t=0;
priority_queue<pii,vector<pii>,greater<pii> > q;
\/*
 *\/
signed main(){
    
\/\/   freopen("a.in","r",stdin);
\/\/   freopen("a.out","w",stdout);

    cin>>s;
    int res=0;
    for(int i=0;i<s.size();i++){
        if(s[i]=='(') t++;
        else if(s[i]==')'){
            t--;
        }else{
            int x,y;
            cin>>x>>y;
            q.push(make_pair(x-y,i));
            t--;
            res+=y;
            s[i]=')';
        }
        if(t<0){
            if(q.empty()){
                cout<<-1;
                return 0;
            }
            pii p=q.top();
            q.pop();
            s[p.se]='(';
            t+=2;
            res+=p.fi;
        }
    }
    if(t) {
        cout<<-1;
        return 0;
    }
    cout<<res<<endl;
    cout<<s;
    return 0;
}



\/*
 1
 5 3
 2 4 5

 *\/

solution

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-17 18:46:52

那咤 2 系列赛 题解

题目并非按照难度升序排序

谢罪

NOI 风格的 PDF 太难做了,有些公式放进去爆炸了(比如 T2 的 K),以及题面数据范围有误,谢罪

疏忽考虑,奇怪原因导致 T1 SPJ 无法正确返回结果,谢罪

T3 搬大样例搬串了,谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪……,错了哥。

没想到会换机房,科研做法忘记改存储地址了,谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪谢罪

第一次办模拟赛,经验不足,望原谅(想骂就骂吧,错了哥)。

网球君

P5959 [POI 2018] Plan metra - 洛谷

首先考虑树的形态,只会有两种情况:

  • $1$ 与 $n$ 直接连边,其余点连向 $1$ 或 $n$。
  • $1$ 与 $n$ 的路径上有一些点,其余的点都挂在这条链上。

对于第一种情况,要求所有的点的 $\left| d(1,i) - d(i,n) \right|$ 均相等。

考虑第二种情况,在 $1$ 号点与 $N$ 号点之间,必然有一条路径,在这条路径上的点 $i$ 都满足 $d(1,i) + d(i,n) = d(1,n)$。那么 $d(1,n)$ 就应该为

$$\min_{i = 1}^{n} {d(1,i) + d(i,n)}$$

如果我们选择了一个更大的作为 $d(1,n)$,那么小于它的点就无法放置了。

接下来我们需要做的操作就是在这条链上挂上一些节点,我们考虑在点 $i$ 上挂上点 $j$,那么需要满足 $d(1,j) - d(j,n) = d(1,i) - d(i,n)$。

我们设 $i,j$ 之间的边权为 $w$,那么 $d(1,j) = d(1,i) + w$,$d(j,n)$ 同理,两个减一下就有了这个式子。用 map 存一下即可。

大战

P12738 [POI 2016 R2] 口吃 Stutter - 洛谷

在这里我们称题目中要求的 $K_{2\times i} = K_{2\times i - 1}$ 的两个数称为一个配对。

时空均为 $O(n ^ 2)$ 的做法应该是好想的,看原题题解是设 $f_{i,j}$ 表示 $A$ 序列前 $i$ 个,$B$ 序列前 $j$ 个元素,满足条件的最大长度,转移的时候像其他公共子序列的题目一样转移就好。

这里我最开始想到的状态是 $f_{i,j,0/1}$ 表示 $A$ 序列前 $i$ 个,$B$ 序列前 $j$ 个,当前已经配对完成 / 还没有完成配对的最大长度。

转移时,对于 $A_i = B_j$ 的情况,我们记录 $A_i$ 和 $B_j$ 上一次出现的位置,记作 $pre_i$ 和 $pr_j$,那么转移就有:

$$f_{i,j,0} = \max(f_{i,j,0},f_{pre_i,pr_j,1} + 2)$$

发现当我们从最近的一个转移过来是不劣的,而由于转移时我们已经保证了 $A_i = B_j$,所以我们不需要再考虑 $i$ 的限制,只需要从 $B$ 序列中 $j$ 前面第一个与 $j$ 相同的位置 $k$,从 $f_{x,k}$ 转移过来即可,我们维护前缀最大值,直接转移即可。

题解:P12738 [POI 2016 R2] 口吃 Stutter - 洛谷专栏

首先考虑一个时空复杂度都是 $O(n^2)$ 的做法:

首先求出 $a_i,b_i$ 中的每一个数下一次的出现位置 $nxta_i,nxtb_i$。类似于正常的 LCS,设 $dp_{i,j}$ 为考虑 $a$ 的前 $i$ 项,$b$ 的前 $j$ 项的最长公共口吃序列长度,考虑向后转移,显然有以下转移:

  • $dp_{i+1,j}\leftarrow dp_{i,j}$,$dp_{i,j+1}\leftarrow dp_{i,j}$;
  • 特别地,若 $a_{i+1}=b_{j+1}$,则 $dp_{nxta_{i+1},nxtb_{j+1}}\leftarrow dp_{i,j}+2$。

由于空间限制只有 32MB,而当前的状态转移又无法进行滚动数组优化,考虑重新设计状态。

仔细分析转移过程,考虑如何将第二步写成能够滚动数组优化的形式。现在的转移过程是 $i\to nxta_{i+1}$,$j\to nxtb_{j+1}$,不如我们先将 $j$ 一步移动到 $nxtb_{j+1}$,并对当前状态进行标记,代表 $i$ 目前只匹配了一次,在转移的过程中,不断将 $i$ 往右移,不改变 $j$ 的值,直到发现 $a_i=b_j$(此时的 $j$ 就相当于原来的 $nxtb_{j+1}$),完成一次匹配。

这样我们只会从 $dp_i$ 转移到 $dp_{i+1}$,可以使用滚动数组优化。具体的转移可以参考代码。其中的一个细节是,若发现 $a_{i+1}=b_{j+1}$,我们只能将 $dp_{i+1,nxtb_{j+1},1}$ 更新,但是此时 $a_{i+1}=b_{nxtb_{j+1}}$,因此我们完成匹配时判断的是 $a_{i+1}$ 和 $b_j$ 是否相等。

魔丸

P7215 [JOISC 2020] 首都 - 洛谷

做法 $1$

可以考虑一种颜色 $c$,将它作为目标颜色,将它的所有结点从树中取出来。

这样可以确定最小的一棵连通子树使得所有颜色为 $c$ 的结点都在这棵连通子树上。

但是连通子树上会存在许多其他颜色的结点。这样可以确定很多形如“必须将颜色 $d$ 归为颜色 $c$ 才能达成目标”的颜色之间的关系。然而只将所有 $d$ 变成 $c$ 是不够的。

将这样的颜色关系进行连边。$c$ 连向 $d$ 表示 “必须将颜色 $d$ 归为颜色 $c$ 才能达成所有颜色为 $c$ 的结点组成一棵连通子树”。

根据这样的依赖关系,对所有颜色都去尝试连边,然后将图建立出来。

可以发现,想要满足“所有颜色为 $c$ 的结点组成一棵连通子树”,必须要将点 $c$ 可以到达的所有颜色都变成 $c$。答案等价于点数最少的缩点之后出度为 $0$ 的强连通分量的点数。

而连边可以使用重链剖分+线段树优化建图,建立同色结点形成的连通子树可以使用类似虚树的建立方法。

这种方法的时间复杂度是 $O(n\log ^2n)$,瓶颈在于线段树优化建图和确定连通子树上,这些的常数是低于点分治的,效率良好。

更优的理论复杂度

首先考虑一个 $n^2$ 的暴力,我们考虑对于每种颜色的点,维护一个队列,最初将所有与它颜色相同的点加入队列,随后将他的所有父亲节点加入,以及所有与它父亲颜色相同的点,这样相当于将所有必要的点都加入了,时间复杂度 $O(n^2)$。

发现有这样一条贪心性质:如果以 $y$ 为根时选到了颜色 $x$,则以 $x$ 为根的答案一定不会更劣。

换句话说:如果我们在统计点 $x$ 时发现要选颜色 $y$,但我们已经知道了以 $y$ 为根的答案,则无需继续统计这个 $x$,因为选择 $x$ 一定要选择 $y$,所以选择 $x$ 一定会包含选择 $y$ 的所有贡献,因此选择 $y$ 更优。

有了这个结论,我们考虑点分治,考虑选择分治中心作为这个点的最小次数,依旧维护队列,如果发现队列中元素位于当前子连通块外,则直接返回即可,否则假如有关的点,计算贡献取 $\min$ 即可,由于对于每个子连通块计算是 $O(size)$ 的,因此总时间复杂度 $O(n\log n)$。

掌牧松

CF1393E2 Twilight and Ancient Scroll (harder version) - 洛谷

考虑暴力,直接设 $f_{i, j}$ 表示前 $i$ 个字符串,第 $i$ 个删了第 $j$ 个字符的合法方案数。

直接暴力枚举前一个,然后暴力比较 $O(L^3)$。

然后可以将比较换成 hash 加二分比较时间复杂度 $O(L^2 \log L)$

然后考虑将 $s_{i, j}$ 排序,$s_{i, j}$ 表示字符串 $i$ 删除第 $j$ 个字符,这可以贪心排序,就是考虑字符串 $i$ 第一个不同的位置,如果 $a_i<a_{i+1}$ 那么删掉 $1\sim i$ 中的某个字符一定比删除 $i+1 \sim len$ 的某个字符字典序小,所以将 $1 \sim i$ 设成字典序前 $i$ 小;如果 $a_i > a_{i + 1}$ 是类似的。然后将前 $i$ 个字符删掉,继续重复即可。

然后现在设 $f_i,j$ 表示前 $i$ 个字符串,第 $i$ 个是 $s_i,k$ 中的吃第 $j$ 小时方案数。

然后 $f_{i,j}$ 一定由 $f_{i-1}$ 的某个前缀转移过来,而且如果 $j$ 增加,那么它能从 $f_{i-1}$ 转移过来的区间长度也增加。

所以那个指针扫一下,然后二分判断即可做到 $O(L\log L)$。

然后发现只会比较相邻的字符,维护三个数组,分别是偏移量为 $0,1,-1$ 时的 lcp,然后就可以 $O(1)$ 比较了。

7.25 mx题单(DP)题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-25 12:02:36

A

题意

给定一棵 $n$ 个节点的,带有点权的树(编号从 $1$ 开始),点权可能有负。

你需要求出这棵树的一个连通块,其点权和尽可能大。

你只需要输出那个最大的点权和,保证答案在 $int$ 范围内。

思路

树形DP板子题。类似最大子段和。

不难推出状态转移方程:

$$ f_x= \max(f_x,f_x+f_v)$$

其中,$x$ 为当前节点,$v$ 为 $x$ 的儿子。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define ll long long


const int N=1e5+5;\/\/注意修改
const int mod=1e9+7;

vector <int> g[N];
int a[N],f[N];

void dfs(int x,int fa){
    f[x]=a[x];
    for(int i=0;i<g[x].size();i++){
        int v=g[x][i];
        if(v==fa) continue ;
        dfs(v,x);
        f[x]=max(f[x],f[x]+f[v]);
    }
}

int main(){
\/\/    freopen(".in","r",stdin);
\/\/    freopen(".out","w",stdout);

    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1,0);
    int maxx=0;
    for(int i=1;i<=n;i++){
        maxx=max(maxx,f[i]);
    }
    cout<<maxx;
    return 0;
\/\/    fclose(stdin);
\/\/    fclose(stdout);
}

\/*


 *\/


B

题意

给定一个长度为 $n$ 的序列 $A$ ,你可以进行任意次如下的操作:

选择两个相邻且相等的数字,把它们合并成一个数,合并结果为原数值加一。

求你能得到的最大的数字。

$a_i \le 40 $

$ n \le 262144$

思路

一开始想用区间DP解,可惜数据范围 $n^3$ 搞不了。(听说mx数据贼几把水,$n^3$ 可过)

这道题状态十分的不好想。

设 $f_{i,j}$ 为合并出 $i$ ,左端点为 $j$,右端点的位置。

我们可以利用倍增的思想,请看下图:

又出现了一个问题:我们枚举 $i$ 要枚举到哪个数。首先,$a_i \le 40$,并且 $n \le 2^{18}$,不难发现,$i$ 最大就是 $40+18=58$ 。

所以状态转移方程为:

$$ f_{i,j}=f_{i-1,f_{i-1,j}}$$

时间复杂度:$O(58n)$。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define ll long long


const int N=5e3+5;\/\/注意修改
const int mod=1e9+7;

vector <int> g[N];
int a[N],f[60][(1<<20)+10],b[N];



int main(){
\/\/    freopen(".in","r",stdin);
\/\/    freopen(".out","w",stdout);

    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        int t;
        cin>>t;
        f[t][i]=i+1;
    }
    int maxx=0;
    for(int i=2;i<=58;i++){
        for(int j=1;j<=n;j++){
             if(f[i][j]==0) f[i][j]=f[i-1][f[i-1][j]];
             if(f[i][j]) maxx=i;
        }
    }
    cout<<maxx;
    return 0;
\/\/    fclose(stdin);
\/\/    fclose(stdout);
}

\/*


 *\/

C

题意

给定一个长度为 $n$ 的序列 $A$ 和一个长度为 $m$ 的序列 $B$ ,求它们的最长公共上升子序列。

你只需要输出该子序列的长度。

思路

板子题。

这道题是最长公共子序列和最长上升子序列的混合体。

设 $f_{i,j}$ 为 $A$ 序列取前 $i$ 个数,$B$ 序列取前 $j$ 个数的最长公共上升子序列的长度。

当 $a_i \ne b_j$ ,$f_{i,j} = f_{i-1,j}$ 。

当 $a_i = b_j$ ,$f_{i,j}= \max(f_{i,j},f_{i-1,k}+1) [1\le k \le j ,b_k<b_j ]$

只有 $b_k<b_j $ 时,序列才是严格上升子序列。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long


const int N=5e3+5;\/\/注意修改
const int mod=1e9+7;

vector <int> g[N];
int a[N],f[N][N],b[N];



signed main(){
\/\/    freopen(".in","r",stdin);
\/\/    freopen(".out","w",stdout);

    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    int m;
    cin>>m;
    for(int i=1;i<=m;i++) cin>>b[i];
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i]!=b[j]) f[i][j]=f[i-1][j];
            else{
                for(int k=1;k<=j;k++){
                    if(b[k]<b[j]){
                        f[i][j]=max(f[i][j],f[i-1][k]+1);
                    }
                }
            }
        }
    }
    int maxx=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            maxx=max(maxx,f[i][j]);
        }
    }
    cout<<maxx;
    return 0;
\/\/    fclose(stdin);
\/\/    fclose(stdout);
}

\/*


 *\/

e

题意

给定一个长度为 $n$ 的序列 $A$,下标从 $1$ 开始。

你需要删掉一些元素,剩余元素相对位置不动,得到一个新序列 $B$,下标同样从 $1$ 开始。

你的目标是最大化新序列中 $B_i=i$ 的数量。

输出这个最大的数量。

思路

设 $f_{i,j}$ 表示以 $i$ 结尾,偏移量为 $j$ 的数量。

难列出状态转移方程: $$f_{i,j}= \max(f_{i,j},f_{i-1,j}+(a_i==i-j))$$

我们发现,当 $a_i=i-j$ 时,这个数字刚好能对上,所以可能数要加一。

如果有偏移量,如果我们删去一个数,那么总个数减一,偏移量也减一,这里也要判断一下。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long


const int N=5e3+5;\/\/注意修改
const int mod=1e9+7;

vector <int> g[N];
int a[N],f[N][N],b[N];\/\/fij表示以i结尾,偏移量为j



signed main(){
\/\/    freopen(".in","r",stdin);
\/\/    freopen(".out","w",stdout);

    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    int maxx=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<n;j++){
            int d=0;
            if(a[i]==i-j) d=1;
            f[i][j]=max(f[i][j],f[i-1][j]+d);
            if(j){
                f[i][j]=max(f[i][j],f[i-1][j-1]);
            }
            maxx=max(maxx,f[i][j]);
        }
    }
    cout<<maxx;
    return 0;
\/\/    fclose(stdin);
\/\/    fclose(stdout);
}

\/*


 *\/

f

题意

给出两个长度为 $n$ 的正整数序列 $a$ 和 $b$

你需要回答 $q$ 次询问,每次给出 $[l,r]$,问进行以下操作的最少次数使得 $\forall i\in [l,r] \,,a_i=b_i$,或报告无解

询问之间互相独立

一次操作定义为选出一个长度为 $k$($k$ 是偶数)的正整数序列 $pos$,满足 $l\le pos_1<pos_2<\ldots<pos_{k-1}<pos_k\le r$,将 $a_{pos_1},a_{pos_3},\ldots,a_{pos_{k-1}}$ 加一,将 $b_{pos_2},b_{pos_4},\ldots,b_{pos_k}$ 加一。

思路

不难发现,我们这个操作只与 $b_i-a_i$ 有关,与 $a_i$ $b_i$ 本身无关。所以,我们设 $c_i=b_i-a_i$ ,最终要求 $?$ 序列某段区间全为 0 的最小操作次数。

那么,每次操作就是 $l \le p_1 < p_2 < ... < p_{k}$,$c_{p_{2i}}$ 加一, $c_{p_{2i+1}}$ 减一。

不难发现,$ {\textstyle \sum_{i=l}^{r}} c_i$ 为定值,对于 $\forall i \in \left [ l,r \right ] $,无论如何操作,${\textstyle \sum_{j=l}^{i}} c_i$ 都 不会减(若 $r-l+1$ 为奇数,则 ${\textstyle \sum_{j=l}^{i}} c_i$ 会加)。

我们可以把+1看成左括号,-1看做右括号,括号的最大层数就是答案。

换句话说,如果 $ \left [ l,r \right ] $ 是一个合法的括号序列,那么其答案就是其括号层数。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long
#define ll long long



const int N=1e5+5;\/\/注意修改
const int mod=1e9+7;
inline int read(){
    int re=0;char ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch>='0'&&ch<='9') re=10*re+ch-'0',ch=getchar();
    return re;
}
int a[N],b[N];
int mx[22][N],mn[22][N];
int s[N];
int n,q;
void init(){
    for(int i=1;i<=n;i++)
        mx[0][i]=mn[0][i]=s[i];
    for(int i=1;i<=19;i++)
        for(int j=1;j+(1<<i)-1<=n;j++){
            mx[i][j]=max(mx[i-1][j],mx[i-1][j+(1<<i-1)]),
            mn[i][j]=min(mn[i-1][j],mn[i-1][j+(1<<i-1)]);
        }
    return;
}
ll Max(int l,int r){
    int len=log2(r-l+1);
    return max(mx[len][l],mx[len][r-(1<<len)+1]);
}
ll Min(int l,int r){
    int len=log2(r-l+1);
    return min(mn[len][l],mn[len][r-(1<<len)+1]);
}

signed main(){
\/\/    freopen(".in","r",stdin);
\/\/    freopen(".out","w",stdout);
    
    cin>>n>>q;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++){
        int t;
        cin>>t;
        a[i]=t-a[i];
    }
    for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
    init();
    while(q--){
        int l,r;
        cin>>l>>r;
        if(s[r]-s[l-1]!=0||Min(l,r)<s[l-1]) cout<<-1<<endl;
        else{
            cout<<Max(l,r)-s[l-1]<<endl;
        }
    }
    
    return 0;
\/\/    fclose(stdin);
\/\/    fclose(stdout);
}

\/*


 *\/

GCD 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-30 16:30:21

B

题意

给定正整数 $n$,求 $1 \le x,y \le n$ 且 $\gcd(x,y)$ 是素数的数对 (x,y) 的数量。

思路

脑抽数论题。

形象化题意: $$\sum_{p\in prime}^{} \sum_{x=1}^{n} \sum_{y=1}^{n} \left [ \gcd(x,y)==p \right ] $$

不难发现,$\gcd(x,y)==p$ 和 $\gcd(\frac{x}{p} ,\frac{y}{p} )==1$ 是等价的。

于是可以将式子转化成

$$\sum_{p\in prime}^{} \sum_{x=1}^{\frac{n}{p}} \sum_{y=1}^{\frac{n}{p}} \left [ \gcd(x ,y )==1 \right ] $$

不难发现,这个式子具有对称性,即很多计算重复了两次,我们可以简化一下式子:

$$\sum_{p\in prime}^{} \sum_{x=1}^{\frac{n}{p}} (2\sum_{y=1}^{x} \left [ \gcd(x ,y )==1 \right ])-1 $$

-1 是因为 $x=y$ 这个情况,这个只应该被计算一次,而我们计算了两次,所以要减去一次。

而这个式子 $\sum_{y=1}^{x} \left [ \gcd(x ,y )==1 \right ]$ 就是欧拉函数。即 $\sum_{y=1}^{x} \left [ \gcd(x ,y )==1 \right ]=\ arphi (i)$

式子又可以转化为: $$\sum_{p\in prime}^{} 2(\sum_{x=1}^{\frac{n}{p}} \ arphi (i))-1 $$

最后要求的式子便为:

$$\sum_{p\in prime}^{} 2(\sum_{x=1}^{\frac{n}{p}} \ arphi (i))-1 $$

其中,欧拉函数可以利用前缀和优化思想 $\Theta(1)$ 直接求出。

而质数可以用欧拉筛线性求出。

(不会欧拉筛的请看这里,欧拉函数的请看这里这里)

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define ls(rt) tr[rt].ch[0]
#define rs(rt) tr[rt].ch[1]

const int N=1e7+10;\/\/注意修改
const int mod=1e9+7;
const int M=2e5+10;
int prime[N],st[N],n,cnt=0,phi[N],s[N],ans;
void Pr(){
    phi[1]=1;
    for(int i=2;i<=n;i++){
        if(st[i]==0){
            cnt++;
            prime[cnt]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=cnt&&prime[j]*i<=n;j++){
            st[prime[j]*i]=1;
            if(i%prime[j]==0) {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            else phi[i*prime[j]]=phi[i]*phi[prime[j]];
        }
    }
}

signed main(){
    cin>>n;
    Pr();
    for(int i=1;i<=n;i++) s[i]=s[i-1]+phi[i];
    for(int i=1;i<=cnt;i++) ans+=2*s[n\/prime[i]]-1;
    cout<<ans;
    return 0;
}


\/*

 *\/


7.30 mx 模拟赛题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-30 16:34:45

A

题意

给定一张 $n$ 个点,$m$ 条边的无向图。

对于每条边,你都要指定一个该边所属的点,且必须为其两个端点之一。

另外还需要满足,每个点最多拥有一个属于它的边。

求方案数,对 $10^9+7$ 取模。

思路

这道题看上去毫无头绪,但是能发现一些规律:

设 $edge$ 为联通块中边的数量,$node$ 为联通块中点的数量,$x_i$ 为第 $i$ 个联通块的方案数。

当 $edge = node - 1$ ,显然这是一棵树,不难发现 $x_i=n$。

当 $edge = node $ ,显然这是一棵基环树,不难发现 $x_i=2$ (自己手玩一下就知道了)。

当 $edge > node $ ,就是一个普通的图,显然无解,即 $x_i=0$。

我们所求的答案为 $\prod_{i=1}^{联通块的数量} x_i$。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define ls(rt) tr[rt].ch[0]
#define rs(rt) tr[rt].ch[1]

const int N=5e5+10;\/\/注意修改
const int mod=1e9+7;
const ll MAX=2e14+5;
const int M=2e7+10;

vector <int> ve[N];
    
int vis[N],node,edge;

void dfs(int x){
    if(x){
        node++;
        vis[x]=1;
        for(int i=0;i<ve[x].size();i++){
            edge++;
            if(!vis[ve[x][i]]) dfs(ve[x][i]);
        }
    }
}

signed main(){
    int n,m,u,v,res=1,x;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>u>>v;
        ve[u].push_back(v),ve[v].push_back(u);
    }
    for(int i=1;i<=n;i++){
        edge=0,node=0;
        if(!vis[i]){
            dfs(i);
            edge\/=2;
            if(edge<node) x=node;
            if(edge==node) x=2;
            if(edge>node) x=0;
            res*=x,res%=mod;
        }
    }
    cout<<res%mod<<endl;
    return 0;
}

B

题意

给定正整数 $n$,求 $1 \le x,y \le n$ 且 $\gcd(x,y)$ 是素数的数对 (x,y) 的数量。

思路

脑抽数论题。

形象化题意: $$\sum_{p\in prime}^{} \sum_{x=1}^{n} \sum_{y=1}^{n} \left [ \gcd(x,y) = p \right ] $$

不难发现,$\gcd(x,y)=p$ 和 $\gcd(\frac{x}{p} ,\frac{y}{p} )=1$ 是等价的。

于是可以将式子转化成

$$\sum_{p\in prime}^{} \sum_{x=1}^{\frac{n}{p}} \sum_{y=1}^{\frac{n}{p}} \left [ \gcd(x ,y )=1 \right ] $$

不难发现,这个式子具有对称性,即很多计算重复了两次,我们可以简化一下式子:

$$\sum_{p\in prime}^{} \sum_{x=1}^{\frac{n}{p}} (2\sum_{y=1}^{x} \left [ \gcd(x ,y )=1 \right ])-1 $$

-1 是因为 $x=y$ 这个情况,这个只应该被计算一次,而我们计算了两次,所以要减去一次。

而这个式子 $\sum_{y=1}^{x} \left [ \gcd(x ,y )=1 \right ]$ 就是欧拉函数。即 $\sum_{y=1}^{x} \left [ \gcd(x ,y )=1 \right ]=\ arphi (i)$

式子又可以转化为: $$\sum_{p\in prime}^{} 2(\sum_{x=1}^{\frac{n}{p}} \ arphi (i))-1 $$

最后要求的式子便为:

$$\sum_{p\in prime}^{} 2(\sum_{x=1}^{\frac{n}{p}} \ arphi (i))-1 $$

其中,欧拉函数可以利用前缀和优化思想 $\Theta(1)$ 直接求出。

而质数可以用欧拉筛线性求出。

(不会欧拉筛的请看这里,欧拉函数的请看这里这里)

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define ls(rt) tr[rt].ch[0]
#define rs(rt) tr[rt].ch[1]

const int N=1e7+10;\/\/注意修改
const int mod=1e9+7;
const int M=2e5+10;
int prime[N],st[N],n,cnt=0,phi[N],s[N],ans;
void Pr(){
    phi[1]=1;
    for(int i=2;i<=n;i++){
        if(st[i]==0){
            cnt++;
            prime[cnt]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=cnt&&prime[j]*i<=n;j++){
            st[prime[j]*i]=1;
            if(i%prime[j]==0) {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            else phi[i*prime[j]]=phi[i]*phi[prime[j]];
        }
    }
}

signed main(){
    cin>>n;
    Pr();
    for(int i=1;i<=n;i++) s[i]=s[i-1]+phi[i];
    for(int i=1;i<=cnt;i++) ans+=2*s[n\/prime[i]]-1;
    cout<<ans;
    return 0;
}


\/*

 *\/


C

题意

P8849

思路

若这道题只有操作3,4 ,相信大家都会用树状数组完成。

而这道题要求逆序对的奇偶性,似乎没有什么数据结构能够维护。

但是我们仔细观察发现:交换任意两个数 $x$,$y$,逆序对奇偶性刚好相反。(这个具体手玩一下就行,需分类讨论,这里不在赘述)。

最难处理的操作是操作1,即区间交换。

$$.......aaadddddbbb.......$$

设 $a$ 序列的长度为 $n1$,$d$ 序列的长度为 $N$,$b$ 序列的长度为 $n2$。

将 $a$ 序列和 $b$ 序列交换,可以先将 $a$ 序列和 $d$ 序列交换,交换的次数为 $n1 \times N$。

交换后的序列为: $$.......dddddaaabbb.......$$

继续将 $a$ 序列和 $b$ 序列交换,交换的次数为 $n1 \times n2$。

交换后的序列为: $$.......dddddbbbaaa.......$$

最后将 $d$ 序列和 $b$ 序列交换,所需的操作次数为 $n2 \times N$。

总操作次数为: $n1 \times N + n1 \times n2 + n2 \times N$。

即:$ N (n1+n2) + n1 \times n2 $。

因为交换任意两个数 $x$,$y$,逆序对奇偶性刚好相反,所以该序列逆序对的奇偶性是基于$ N (n1+n2) + n1 \times n2 $ 之上。

而操作二可以转化为序列 $[l,r]$ 与 $[r+1,k]$ 的交换,不再赘述。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define ls(rt) tr[rt].ch[0]
#define rs(rt) tr[rt].ch[1]
#define lowbit(x) x&(-x)

const int N=1e7+10;\/\/注意修改
const int mod=1e9+7;
const int Max=2e6+10;

int c[N],a[N];

void add(int x,int k){
    for(int i=x;i<=Max;i+=lowbit(i)){
        c[i]+=k;
    }
}
int search(int x){
    int s=0;
    for(int i=x;i;i-=lowbit(i)) s+=c[i];
    return s;
}

signed main(){
 
    
    int n,q;
    cin>>n>>q;
    int f=0;
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        f^=search(Max-2)-search(a[i]);
        add(a[i],1);
    }
    while(q--){
        int op,k,l1,r1,l2,r2,n1,n2,N;
        cin>>op;
        if(op==1){
            cin>>l1>>r1>>l2>>r2;
            n1=r1-l1+1;
            n2=r2-l2+1;
            N=l2-r1-1;
            f^=N*(n1+n2)+n1*n2;
        }
        if(op==2){
            cin>>l1>>r1>>k;
            n1=r1-l1+1;
            n2=k-r1;
            f^=n1*n2;
        }
        if(op==3){
            int x;
            cin>>x;
            f^=search(Max-2)-search(x);
            add(x,1);
        }
        if(op==4){
            int x;
            cin>>x;
            f^=search(x);
            add(x,1);
        }
        if(f&1) cout<<"odd"<<endl;
        else cout<<"even"<<endl;
    }
    
    return 0;
}


\/*

 *\/


mx 731 模拟赛记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-31 21:27:06

A

题意

Arc158C

思路

形象化题意:

$$\sum_{i=1}^{N} \sum_{j=1}^{N}f(A_i+A_j)$$

其中,$f(i)$ 为 $i$ 的数位和。

显然,这个式子十分的不美观,且 $f(A_i+A_j)$ 也比较难求,不妨将其拆成 $f(A_i)+f(A_j)$。但是,两个数的和的数位和 和 两个数的数位和的和显然不同。

举个例子:

$$5 \ 6 \ 7$$ $$7 \ 8 \ 9$$

相加得到:

$$1 \ 3 \ 5 \ 6$$

第一个数的数位和为18,第二个数的数位和为24,两数和的数位和为15。

但是,仔细思考不难得出,进位一次,数位和会减去9。

所以,我们不难推出这个式子:

$$f(A_i+A_j)=f(A_i)+f(A_j)-9\times g(A_i,A_j)$$

其中,$g(A_i,A_j)$ 是 $A_i$ 和 $A_j$ 相加进位的次数。

整个式子便可化为:

$$\sum_{i=1}^{N} \sum_{j=1}^{N}f(A_i)+f(A_j)-9\times g(A_i,A_j)$$

其中:$\sum_{i=1}^{N} \sum_{j=1}^{N}f(A_i)+f(A_j)$ 等价为 $2 \times n(\sum_{i=1}^{N} f(A_i))$。

分析 $g(a,b)$,发现在第 $d$ 位上有进位当且仅当 $a \ mod \ 10^d + b \ mod \ 10^d \ge 10^{d+1}$。我们可以二分求答案。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define ls(rt) tr[rt].ch[0]
#define rs(rt) tr[rt].ch[1]
#define lowbit(x) x&(-x)

const int N=1e6+5;\/\/注意修改
const int mod=1e9+7;
const int Max=2e6+10;

int n,m;
int a[20][N],b[N];



signed main(){
        
    int n;
    cin>>n;
    int sum=0;
    for(int i=1;i<=n;i++) {
        int t;
        cin>>t;
        int tmp=10;
        for(int j=1;j<=15;j++){
            a[j][i]=t%tmp;
            tmp*=10;
        }
        while(t){
            int tt=t%10;
            sum+=tt;
            t\/=10;
        }
    }
    sum*=2*n;
    \/\/cout<<sum<<endl;
    int Tt=1;
    for(int i=1;i<=15;i++){
        Tt*=10;
        sort(a[i]+1,a[i]+1+n);
        for(int j=1;j<=n;j++){
            sum-=9*(a[i]+n+1-lower_bound(a[i]+1,a[i]+1+n,Tt-a[i][j]));
           
        }

    }
    
    cout<<sum<<endl;
    
    return 0;
}


\/*

 *\/

B

时间到了,写不了了,以后有时间在写。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define ls(rt) tr[rt].ch[0]
#define rs(rt) tr[rt].ch[1]
#define lowbit(x) x&(-x)

const int N=1e6+5;\/\/注意修改
const int mod=1e9+7;
const int Max=2e6+10;

int n,m;
int a[20][N],b[N],p[N];
int x,k;
int sum=0,cnt=0;

int tot=0;

void dfs(int x,int y){ \/\/要拆分得数是x,拆到了第y层
    \/\/cout<<x<<' '<<y<<endl;
    if(tot>=100000) return ;
    if(y==0||x==1){
        tot++;
        cout<<x<<' ';
        sum+=x;
        return ;
    }
    if(tot>=100000) return ;
    for(int i=1;i<=cnt;i++){
        if(p[i]>x||tot>100000) return ;
       if(x%p[i]==0) dfs(p[i],y-1);
    }
}

signed main(){
    
\/\/    freopen("digit.in","r",stdin);
\/\/    freopen("digit,out","w",stdout);
    
    cin>>x>>k;
    
    for(int i=1;i*i<=x;i++){
        if(x%i==0) p[++cnt]=i;
    }
    int d=cnt;
    for(int i=d;i>=1;i--){
        if(p[i]*p[i]!=x){
            p[++cnt]=x\/p[i];
          \/\/  cout<<p[i]<<' ';
        }
    }
\/\/    for(int i=1;i<=cnt;i++) {
\/\/        cout<<p[i]<<' ';
\/\/    }
    dfs(x,k);
    
    return 0;
}


\/*

 *\/


8.1 mx训练赛记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-01 21:11:17

A

题意

P9588

思路

  • 对于操作一,观察数据范围,发现 $x \le 10^9$,所以不可能将 $1-x$ 全部放进序列。不妨只将 $x$ 放入序列,并记录前缀和。

  • 对于操作二,不妨记录一共删去了多少数,然后暴力删除即可。

  • 对于操作三,令 $z$ 加上删去的数的个数,然后二分查找即可。

  • 对于操作四,我们可以使用 multiset 直接查询最大值。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<set>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)

const int N=1e6+5;\/\/注意修改
const int mod=1e9+7;
const int Max=2e6+10;

int a[N],ss[N];

multiset<int> s;



signed main(){
    
\/\/    freopen("digit.in","r",stdin);
\/\/    freopen("digit,out","w",stdout);
    
  
    int q;
    cin>>q;
    int cnt=0;
    int num=0,idx=1;
    while(q--){
        int op,x;
        cin>>op;
        if(op!=4) cin>>x;
        if(op==1){
            a[++cnt]=x;
            ss[cnt]=ss[cnt-1]+x;
            s.insert(x);
        }
        if(op==2){
            num+=x;
            while(ss[idx]<=num&&idx<=cnt){
                s.erase(s.find(a[idx]));
                idx++;
            }
        }
        if(op==3){
            x+=num;
            int index=lower_bound(ss+1,ss+1+cnt,x)-ss-1;
            cout<<x-ss[index];
        }
        if(op==4){
            cout<<*s.rbegin();
        }
        Endl
    }
    
    return 0;
}
\/\/唐儿祭天,法力无边

B

题意

CF527C

思路

不难发现,横着切割和竖着切割是无关的,换句话说,无论选择哪一个长和宽,都能找到对应的碎片。所以,要找面积最大的碎片,就是求两边没被切割过的最大长度的积。

我们可以用 set 维护切割位置,用 multiset 维护切割出来的长度。

每次切割,我们先将切割位置放入set,找到其前一个切割位置 $a$,再找到其后一个切割位置 $b$,将 $a$ 和 $b$ 之间的长度删除,在增加两个长度(具体看代码,不好口述)。

最后,求出两条边的长度最大值求积即可。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<set>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)

const int N=1e6+5;\/\/注意修改
const int mod=1e9+7;
const int Max=2e6+10;


int n,m;
int id[N],tag[N],v[N];\/\/ id[i] 是当前点所在的块的编号

multiset <int> x,y;
set<int> lx,ly;
multiset<int>::iterator it;

signed main(){
    
\/\/    freopen("digit.in","r",stdin);
\/\/    freopen("digit,out","w",stdout);
    
    int w,h;
    cin>>w>>h>>n;
    x.insert(w);
    y.insert(h);
    lx.insert(0);
    lx.insert(w);
    ly.insert(0);
    ly.insert(h);
    
    for(int i=1;i<=n;i++){
        char op;
        int t;
        cin>>op>>t;
        if(op=='H'){
            ly.insert(t);
            it=ly.find(t);
            it--;
            int l=*it;
            it++;
            it++;
            int r=*it;
            it=y.find(r-l);
            y.erase(it);
            y.insert(t-l);
            y.insert(r-t);
        }
        if(op=='V'){
            lx.insert(t);
            it=lx.find(t);
            it--;
            int l=*it;
            it++;
            it++;
            int r=*it;
            it=x.find(r-l);
            x.erase(it);
            x.insert(t-l);
            x.insert(r-t);
        }
        it=x.end();
        it--;
        int l=*it;
        it=y.end();
        it--;
        int r;
        r=*it;
        cout<<l*r<<endl;
    }
    
    
    return 0;
}

D

题意

线段树,区间加,区间最值,区间和,区间除。

思路

这里只叙述区间除。

不难发现,$max-\left \lfloor \frac{max}{d} \right \rfloor=min-\left \lfloor \frac{min}{d} \right \rfloor $,区间除可以退化成区间减。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)

const int N=1e6+5;\/\/注意修改
const int mod=1e9+7;
const int Max=2e6+10;

int a[N];

struct tree{
    int l,r;
    int add,sum,maxx,minn;
}tr[N<<2];

void pushup(int rt){
    tr[rt].sum=tr[rt<<1].sum+tr[rt<<1|1].sum;
    tr[rt].maxx=max(tr[rt<<1].maxx,tr[rt<<1|1].maxx);
    tr[rt].minn=min(tr[rt<<1].minn,tr[rt<<1|1].minn);
}

void build(int rt,int l,int r){
    tr[rt].l=l;
    tr[rt].r=r;
    tr[rt].add=0;
    if(l==r){
        tr[rt].sum=a[l];
        tr[rt].maxx=a[l];
        tr[rt].minn=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    pushup(rt);
}

void pushdown(int rt){
    if(tr[rt].add==0) return ;
    tr[rt<<1].add+=tr[rt].add;
    tr[rt<<1|1].add+=tr[rt].add;
    tr[rt<<1].sum+=tr[rt].add*(tr[rt<<1].r-tr[rt<<1].l+1);
    tr[rt<<1|1].sum+=tr[rt].add*(tr[rt<<1|1].r-tr[rt<<1|1].l+1);
    tr[rt<<1].maxx+=tr[rt].add;
    tr[rt<<1|1].maxx+=tr[rt].add;
    tr[rt<<1].minn+=tr[rt].add;
    tr[rt<<1|1].minn+=tr[rt].add;
    tr[rt].add=0;
}

void Add(int rt,int l,int r,int x){
    if(tr[rt].l>=l&&tr[rt].r<=r){
        tr[rt].add+=x;
        tr[rt].sum+=x*(tr[rt].r-tr[rt].l+1);
        tr[rt].maxx+=x;
        tr[rt].minn+=x;
        return ;
    }
    pushdown(rt);
    int mid=(tr[rt].l+tr[rt].r)>>1;
    if(l<=mid) Add(rt<<1,l,r,x);
    if(r>mid) Add(rt<<1|1,l,r,x);
    pushup(rt);
}

void Div(int rt,int l,int r,int x){
    if(tr[rt].l>=l&&tr[rt].r<=r&&tr[rt].maxx-floor(1.0*tr[rt].maxx\/x)==tr[rt].minn-floor(1.0*tr[rt].minn\/x)){
        int d=floor(1.0*tr[rt].maxx\/x)-tr[rt].maxx;
        tr[rt].add+=d;
        tr[rt].sum+=d*(tr[rt].r-tr[rt].l+1);
        tr[rt].maxx+=d;
        tr[rt].minn+=d;
        return ;
    }
    pushdown(rt);
    int mid=(tr[rt].l+tr[rt].r)>>1;
    if(l<=mid) Div(rt<<1,l,r,x);
    if(r>mid) Div(rt<<1|1,l,r,x);
    pushup(rt);
}

int query(int rt,int l,int r){
    if(tr[rt].l>=l&&tr[rt].r<=r){
        return tr[rt].sum;
    }
    if(tr[rt].l==tr[rt].r) return 0;
    pushdown(rt);
    int mid=(tr[rt].l+tr[rt].r)>>1;
    int res=0;
    if(l<=mid) res+=query(rt<<1,l,r);
    if(r>mid) res+=query(rt<<1|1,l,r);
    pushup(rt);
    return res;
}

int Query(int rt,int l,int r){
    if(tr[rt].l>=l&&tr[rt].r<=r){
        return tr[rt].minn;
    }
    if(tr[rt].l==tr[rt].r) return 0;
    pushdown(rt);
    int mid=(tr[rt].l+tr[rt].r)>>1;
    int res=0x3f3f3f3f;
    if(l<=mid) res=min(res,Query(rt<<1,l,r));
    if(r>mid) res=min(res,Query(rt<<1|1,l,r));
    pushup(rt);
    return res;
}

signed main(){
    
\/\/    freopen("digit.in","r",stdin);
\/\/    freopen("digit,out","w",stdout);
    
  
    int n,q;
    cin>>n>>q;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    build(1,1,n);
    while(q--){
        int op;
        cin>>op;
        int l,r,c,d;
        if(op==1){
            cin>>l>>r>>c;
            l++;
            r++;
            Add(1,l,r,c);
        }
        if(op==2){
            cin>>l>>r>>d;
            l++;
            r++;
            Div(1,l,r,d);
        }
        if(op==3){
            cin>>l>>r;
            l++;
            r++;
            cout<<Query(1,l,r);
        }
        if(op==4){
            cin>>l>>r;
            l++;
            r++;
            cout<<query(1,l,r);
        }
        Endl
\/\/        Endl
\/\/        cout<<query(1,1,n);
\/\/        Endl
\/\/        Endl
    }
    return 0;
}


\/*

 *\/

8.2 mx 数论记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-02 20:22:34

二项式定理

mx没学这个,看了pry的题解,了解了一下。

$$(x+y)^n=C^0_n x^ny^0+C^1_n x^{n-1}y^1+C^2_n x^{n-2}y^2+\dots+C^{n-1}_n x^{1}y^{n-1}+C^n_n x^{0}y^n$$

进一步简化:

$$(x+y)^n= \sum^{n}_{k=0} C^k_n x^{n-k}y^k$$

Exgcd

用来求解线性同余方程($ax+by=\gcd(a,b)$)的一组可行解。

$$设 ax_1+by_1=\gcd(a,b) , bx_2+(a \ \bmod \ b)y_2=\gcd(b,a \bmod b)$$

$$\because \gcd(a,b)= \gcd(b,a \bmod b)$$

$$\therefore ax_1+by_1=bx_2+(a \ \bmod \ b)y_2$$

$$\because a \bmod b=a-b \times \left \lfloor \frac{a}{b} \right \rfloor $$

$$\therefore ax_1+by_1=bx_2+(a-b \times \left \lfloor \frac{a}{b} \right \rfloor)y_2$$

$$\therefore ax_1+by_1=bx_2+ay_2-b(y_2 \times \left \lfloor \frac{a}{b} \right \rfloor) $$

$$\therefore ax_1+by_1=ay_2+b(x_2-y_2 \times \left \lfloor \frac{a}{b} \right \rfloor) $$

$$\therefore x1=y2,y1=x_2-y_2 \times \left \lfloor \frac{a}{b} \right \rfloor$$

代码

int exgcd(int a,int b,int &x,int &y){
    if(b==0){
        x=1,y=0;
        return a;
    }
    d=exgcd(b,a%b,x,y);
    
    int xx=x;
    x=y;
    y=xx-(a\/b)*y;
    return d;
}

8.5 mx放学路

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-05 19:35:18

思路

Dij+二分。

一眼二分答案,二分最小能量值。

Dij是板子。注意两点之间的距离是最多能放传送锚点的个数,这里分讨,如果两点之间的距离不能和能量值整除,那么锚点个数为 $dis \div x$,否则为 $\max(0,dis \div x-1)$。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<set>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)

const int N=5e5+10;\/\/注意修改
const int mod=1e9+9;
const int Max=2e9+10;

struct node{
    int v,d;
    bool operator < (const node&b) const{
        return d>b.d;
    }
};

pii a[N];

int vis[N],ddis[N];



int n,m,x;

int dis(int u,int v,int k){
    int dis_d=abs(a[u].first-a[v].first)+abs(a[u].second-a[v].second);
    if(dis_d%k){
        return dis_d\/k;
    }
    else{
        return max(0ll,dis_d\/k-1);
    }
}

int Dij(int X){
    priority_queue<node> q;
    ddis[m+1]=0;
    q.push(node{m+1,0});
    while(!q.empty()){
        node t=q.top();
        q.pop();
        if(vis[t.v]) continue;
        vis[t.v]=1;
        for(int i=1;i<=m+2;i++){
            if(vis[i]) continue;
            int w=dis(t.v,i,X);
            if(ddis[i]>ddis[t.v]+w){
                ddis[i]=ddis[t.v]+w;
                if(!vis[i]){
                    q.push(node{i,ddis[i]});
                }
            }
        }
    }
    return ddis[m+2];
}

bool check(int X){
    for(int i=1;i<=m+2;i++) vis[i]=0;
    for(int i=1;i<=m+2;i++) ddis[i]=Max;
    return Dij(X)<=x;
}

signed main(){
    
\/\/    freopen("road.in","r",stdin);
\/\/    freopen("road.out","w",stdout);

    int sx,sy,ex,ey;
    cin>>n>>m>>x;
    cin>>sx>>sy>>ex>>ey;
    for(int i=1;i<=m;i++){
        cin>>a[i].first>>a[i].second;
    }
    a[m+1]={sx,sy};
    a[m+2]={ex,ey};
    int l=1,r=2e9,res=0;
    while(l<r){
        int mid=(l+r)>>1;
        if(check(mid)){
            r=mid;

        }
        else l=mid+1;
    }
    cout<<l;
    return 0;
}


\/\/唐儿祭天,法力无边。

\/*


 *\/

8.5 mx 组合专题

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-05 20:35:13

容斥原理

设 $U$ 中元素有 $n$ 种不同的属性,而第 $i$ 种属性称为 $P_i$,拥有属性 $P_i$ 的元素构成集合 $S_i$,那么

$$\left | \bigcup {i=1}^n s_i \right | =\sum{m=1}^{n} (-1)^{m-1} \sum_{a_i<a_{i+1}}^{} \left | \bigcap {i=1}^m s{a_i} \right |$$

二项式反演

二项式反演的式子具体归纳成两个式子:

  • $g_n$ 表示至多 $n$ 种的方案数量,$f_n$ 表示恰好 $n$ 种的方案数量。

$$f_n=\sum_{i=0}^{n} (-1)^{n-i} C^i_n g_i \Longleftrightarrow g_n=\sum^n_{i=0} C_n^if_i $$

  • $g_k$ 表示至少 $k$ 种的方案数量,$f_k$ 表示恰好 $k$ 种的方案数量。

$$f_k=\sum_{i=k}^{n} (-1)^{i-k} C^i_k g_i \Longleftrightarrow g_n=\sum^n_{i=k} C_k^if_i $$

共 53 篇博客