三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

介绍国外的网站有什么不同中美俄最新军事新闻

介绍国外的网站有什么不同中美俄最新军事新闻 介绍国外的网站有什么不同,中美俄最新军事新闻,怎么做中英文网站,91wordpress[题解] 河南萌新联赛 2026 第一场 C. 最大公约数(ST 表 + 二分) TAG: ST 表,最大公约数,二分,贡献法,牛客 题目链接: 牛客 C - 最大公约数 #cnblogs_post_body pre table.hljs-ln { display: table !important; … [题解] 河南萌新联赛 2026 第一场 C. 最大公约数(ST 表 + 二分) TAG: ST 表,最大公约数,二分,贡献法,牛客 题目链接: 牛客 C - 最大公约数 #cnblogs_post_body pre table.hljs-ln { display: table !important; width: 100% !important; margin: 0 !important; border: 0 !important; background: rgba(0, 0, 0, 0) !important; box-shadow: none !important } #cnblogs_post_body pre table.hljs-ln tr, #cnblogs_post_body pre table.hljs-ln th, #cnblogs_post_body pre table.hljs-ln td, #cnblogs_post_body pre table.hljs-ln tr:nth-child(2n) td { border: 0 !important; background: rgba(0, 0, 0, 0) !important } #cnblogs_post_body pre table.hljs-ln td { padding: 0 !important } #cnblogs_post_body pre table.hljs-ln td.hljs-ln-numbers { padding: 0 14px 0 0 !important; color: rgba(115, 121, 132, 1) !important } #cnblogs_post_body pre code.hljs { color: rgba(171, 178, 191, 1) !important; background: rgba(40, 44, 52, 1) !important } 题目大意 给定一个正整数序列,定义一个连续子数组的 \( g \) 为区间所有元素的最大公约数,\( m \) 为区间最小值。求满足 \( g=m \) 的非空连续子数组数量。 拿到题目后如何思考 1. 先分析等号什么时候成立 任意一组正整数的最大公约数一定不超过其中的最小值,因此始终有 \( g\le m \)。 如果某个区间满足 \( g=m \),那么区间最小值一定在区间内某个位置 \( i \) 取得,并且有: \[g(l,r)=a_i \]反过来,如果一个包含位置 \( i \) 的区间满足 \( g(l,r)=a_i \),那么 \( a_i \) 能整除区间内所有元素。由于所有数都是正整数,区间内每个元素都不小于 \( a_i \),所以 \( a_i \) 就是区间最小值。 于是原条件可以等价改写为:统计有多少个子数组,其区间最大公约数等于区间内的某个元素。这个转化非常关键。它让我们可以枚举位置 \( i \),计算有多少个包含 \( i \) 的区间满足区间 gcd 等于 \( a_i \)。 2. 直接枚举位置会重复计数 例如区间 [2,4,2] 的 gcd 和最小值都是 2,但其中有两个位置的值等于 2。如果对两个位置都计算贡献,这个区间就会被统计两次。 因此需要给每个合法区间指定唯一代表。这里规定:位置 \( i \) 必须是该子数组中值为最小值的最右侧位置。这样每个合法区间都只会归属于一个位置。 3. 固定位置 \( i \),先求能够向左、向右扩展多远 固定位置 \( i \),希望寻找所有包含 \( i \) 且 gcd 等于 \( a_i \) 的区间。 先只向左扩展,定义 \( ql \) 为最小的左端点,使得: \[g(ql,i)=a_i \]那么任意 \( l\in[ql,i] \) 都满足 \( g(l,i)=a_i \)。 同理,定义 \( qr \) 为最大的右端点,使得: \[g(i,qr)=a_i \]那么任意 \( r\in[i,qr] \) 都满足 \( g(i,r)=a_i \)。 对于任意 \( l\in[ql,i] \) 和 \( r\in[i,qr] \),左右两部分中的所有元素都能被 \( a_i \) 整除,并且区间内包含 \( a_i \) 本身,所以必有: \[g(l,r)=a_i \]因此,在暂时不考虑重复计数时,合法左端点和右端点可以独立选择。 4. 为什么边界可以二分 考虑左边界。区间 [mid,i] 一定包含 \( a_i \),所以它的 gcd 一定是 \( a_i \) 的约数。 当 mid 向左移动、区间不断扩大时,gcd 只可能保持不变或者变小。一旦 gcd 已经小于 \( a_i \),继续向左扩大也不可能重新变回 \( a_i \)。 所以判断条件 gcd(mid,i)==a[i] 具有单调性,可以二分出最小的合法左端点 ql。 右侧完全同理,可以二分出最大的合法右端点 qr。 二分过程中需要快速查询任意区间 gcd。gcd 满足结合律和幂等性,因此可以使用 ST 表,在 \( O(1) \) 时间内完成一次查询。 如何消除相同最小值造成的重复 令 nxt[i] 表示位置 \( i \) 右侧第一个满足 a[j]==a[i] 的位置;如果不存在,则令 nxt[i]=n+1。 由于规定 \( i \) 是区间中最右侧的最小值位置,所以区间右端点必须满足: \[rnxt_i \]同时右端点还不能超过 gcd 条件允许的 qr,所以真正的最远右端点为: \[right=\min(qr,nxt_i-1) \]左端点可以任取 ql 到 \( i \),右端点可以任取 \( i \) 到 right,位置 \( i \) 的贡献就是: \[(i-ql+1)(right-i+1) \]把所有位置的贡献相加即可得到答案。 nxt 如何求 将所有二元组 (a[i],i) 排序。排序后,相同数值会聚在一起,并且下标按升序排列。 对于每个相同数值的分组,组内下一个二元组的下标就是当前位置右侧第一次出现相同值的位置;组内最后一个位置的 nxt 为 n+1。 正确性证明 1. 计算出的每个区间都合法 对于位置 \( i \) 统计的任意区间 [l,r],有 l=ql、r=qr,所以 [l,i] 和 [i,r] 的 gcd 都等于 \( a_i \)。因此整个 [l,r] 的 gcd 等于 \( a_i \)。 又因为区间内包含元素 \( a_i \),并且 \( a_i \) 能整除区间内所有正整数,所以 \( a_i \) 也是区间最小值,故该区间满足 gcd 等于最小值。 2. 每个合法区间都会被统计 任取一个合法区间 [l,r],选择其中最右侧的最小值位置 \( i \)。由于区间 gcd 等于最小值 \( a_i \),所以 \( l\ge ql \)、\( r\le qr \)。 同时 \( i \) 是最右侧的最小值位置,因此在 \( i \) 与 \( r \) 之间不会再次出现 \( a_i \),即 rnxt[i]。所以这个区间一定会被位置 \( i \) 的贡献统计到。 3. 每个合法区间只会被统计一次 一个区间中最右侧的最小值位置是唯一的。代码只把区间计入这个位置的贡献,因此不会重复统计。 综上,算法恰好统计所有合法区间。 复杂度建立 gcd ST 表需要 \( O(n\log n) \) 时间; 排序并计算 nxt 需要 \( O(n\log n) \) 时间; 每个位置进行两次二分,每次查询 gcd 为 \( O(1) \),总计 \( O(n\log n) \); ST 表占用 \( O(n\log n) \) 空间,其余数组占用 \( O(n) \) 空间。因此总时间复杂度为 \( O(n\log n) \),总空间复杂度为 \( O(n\log n) \)。 代码前说明st.f[j][i]:区间 [i,i+2^j-1] 的 gcd; st.query(l,r):使用两个长度为 \( 2^k \) 的区间求 [l,r] 的 gcd; p:保存 (a[i],i),排序后用于求相同元素的下一次出现位置; nxt[i]:位置 \( i \) 右侧第一个与 \( a_i \) 相等的位置; ql:满足 gcd(ql,i)==a[i] 的最小左端点; qr:满足 gcd(i,qr)==a[i] 的最大右端点; right=min(qr,nxt[i]-1):保证 \( i \) 是区间中最右侧的最小值位置; 单个测试的答案最多为 \( n(n+1)/2 \),需要使用 long long。参考代码 #includebits/stdc++.h using namespace std; #define int long long #define endl '\n' const int N=5e5+5; int a[N]; int nxt[N]; struct ST{vectorvectorint f;void init(int n){int k=__lg(n);f.assign(k+1,vectorint(n+1));for(int i=1;i=n;i++) f[0][i]=a[i];for(int j=1;j=k;j++){for(int i=1;i+(1j)-1=n;i++){f[j][i]=__gcd(f[j-1][i],f[j-1][i+(1(j-1))]);}}}int query(int l,int r){int k=__lg(r-l+1);return __gcd(f[k][l],f[k][r-(1k)+1]);} }st; void solve(){int n;cinn;vectorpairint,int p;for(int i=1;i=n;i++){cina[i];p.push_back({a[i],i});}st.init(n);sort(p.begin(),p.end());//求每个位置右边第一个相同元素for(int l=0,r;ln;l=r+1){r=l;while(r+1np[r+1].first==p[l].first) r++;//相同数值的分组为[l,r]for(int j=l;j=r;j++){int pos=p[j].second;if(j+1=r) nxt[pos]=p[j+1].second;else nxt[pos]=n+1;}}int ans=0;for(int i=1;i=n;i++){int ql=i;//二分最左边界qlint l=1,r=i;while(l=r){int mid=l+r1;if(st.query(mid,i)==a[i]){ql=mid;r=mid-1;}else l=mid+1;}int qr=i;l=i,r=n;while(l=r){int mid=l+r1;if(st.query(i,mid)==a[i]){qr=mid;l=mid+1;}else r=mid-1;}//规定i是子数组中最右边的最小值位置,防止重复计算int right=min(qr,nxt[i]-1);ans+=(i-ql+1)*(right-i+1);}coutansendl; } signed main(){ios::sync_with_stdio(false);cin.tie(nullptr);int t=1;cint;while(t--) solve();return 0; }
← 返回列表