「WyOJ Round 1」归 · 星穗垂野

· · 题解

Solution

算法标签:动态规划、可持久化线段树。

O(n^3)

f_{i,j} 表示前 i 个数中,选择的最后一段(注意该段的末尾可以不为 i)的长度为 j 的最小代价,转移如下:

f_{i,j}=\min \begin{cases} & f_{i-j,k}+\gcd(a_{i-j+1},\dots,a_i)\times \sum_{i=l}^r b_i+C)\quad k\le \gcd\le j\\ & f_{i-1,j} \end{cases}

其中 \gcd\sum 可分别通过 ST 表和前缀和维护,这样查询时复杂度为 O(1)

O(n\log n\log V)

性质:\gcd 只有 \log V 种不同的值(因为 \gcd 相当于质因数集合取交,而至多只有 \log V 个质因数)。

注意到,若 \gcd 相同,则 j 越小越好。原因如下:

  1. 转移中有 f_{i,j}=f_{i-1,j},所以 k 相同的情况下,i-j 值越大必然 f_{i-j,k} 越小。而且 \gcd 相同的情况 k 的范围是一样的。

综上,j 越小越好,也就是只需要转移 \log Vj 的值。不过,这 \log V 种不一定全都是在 \gcd 第一次变成某个值的时候,因为有 \gcd\le j 的条件,这里找到第一个 \ge \gcdj 即可。

接下来,考虑到 k 的可取范围是一个区间,j 相当于单点修。于是,不难想到将第二维放到线段树上。不过,由于第一维每次取的是 f_{i-j},所以需要可持久化线段树。

时空复杂度均为 $O(n\log n\log V)$。 ## Code 1 ```cpp // std #include <bits/stdc++.h> using namespace std; using i64 = long long; template <class T> void ckmn(T &a, T b) { a = min(a, b); } template <class T> T gcd(T a, T b) { return !b ? a : gcd(b, a % b); } const int maxn = 1e5 + 5; int n; i64 C; int a[maxn]; i64 b[maxn]; i64 preb[maxn]; i64 sum(int l, int r) { return preb[r] - preb[l - 1]; } struct ST { int _gcd[18][maxn]; void init() { for (int i = 1; i <= n; i++) { _gcd[0][i] = a[i]; } for (int i = 1; i <= 17; i++) { for (int j = 1; j <= n - (1 << i) + 1; j++) { _gcd[i][j] = gcd(_gcd[i - 1][j], _gcd[i - 1][j + (1 << i - 1)]); } } } int query(int l, int r) { int _ = __lg(r - l + 1); return gcd(_gcd[_][l], _gcd[_][r - (1 << _) + 1]); } } st; struct HJT { struct Node { int ls, rs; i64 mn; } nd[32400005]; int ndcnt; void pushup(Node &x) { x.mn = min(nd[x.ls].mn, nd[x.rs].mn); } void build(int &rt, int l, int r) { if (!rt) rt = ++ndcnt; nd[rt].mn = 1e18; if (l == r) { return; } int mid = (l + r >> 1); build(nd[rt].ls, l, mid); build(nd[rt].rs, mid + 1, r); } void change(int &rtold, int &rtnew, int l, int r, int x, i64 _v) { if (!rtnew) { rtnew = ++ndcnt; nd[rtnew].mn = nd[rtold].mn; } if (l == r) { ckmn(nd[rtnew].mn, _v); return; } int mid = (l + r >> 1); if (mid >= x) { if (nd[rtnew].ls == nd[rtold].ls) nd[rtnew].ls = 0; change(nd[rtold].ls, nd[rtnew].ls, l, mid, x, _v); } else { if (nd[rtnew].rs == nd[rtold].rs) nd[rtnew].rs = 0; change(nd[rtold].rs, nd[rtnew].rs, mid + 1, r, x, _v); } if (!nd[rtnew].ls) nd[rtnew].ls = nd[rtold].ls; if (!nd[rtnew].rs) nd[rtnew].rs = nd[rtold].rs; pushup(nd[rtnew]); } i64 query(int rt, int curl, int curr, int l, int r) { if (!rt) return 1e18; if (curl >= l && curr <= r) { return nd[rt].mn; } int mid = (curl + curr >> 1); i64 ans = 1e18; if (mid >= l) ckmn(ans, query(nd[rt].ls, curl, mid, l, r)); if (mid < r) ckmn(ans, query(nd[rt].rs, mid + 1, curr, l, r)); return ans; } } ds; int rt[maxn]; void solve(int id_of_test) { cin >> n >> C; for (int i = 1; i <= n; i++) { cin >> a[i]; } st.init(); for (int i = 1; i <= n; i++) { cin >> b[i]; preb[i] = preb[i - 1] + b[i]; } ds.build(rt[0], 1, 100000); for (int r = 1; r <= n; r++) { rt[r] = ++ds.ndcnt; ds.nd[rt[r]] = ds.nd[rt[r - 1]]; int ok = 0; int efl = 1, efr = r; while (efl <= efr) { int mid = (efl + efr >> 1); if (r - mid + 1 >= st.query(mid, r)) { ok = mid; efl = mid + 1; } else { efr = mid - 1; } } if (!ok) continue; int l = ok; int _gcd = st.query(l, r); while (l >= 1) { if (r - l + 1 >= _gcd) { i64 tot = 1ll * _gcd * sum(l, r); i64 ans = tot; ckmn(ans, tot + C + ds.query(rt[l - 1], 1, 100000, 1, _gcd)); ds.change(rt[r - 1], rt[r], 1, 100000, r - l + 1, ans); } int efl = 1, efr = l; int ok = l; while (efl <= efr) { int mid = (efl + efr >> 1); int k = __lg(r - mid + 1); if (st._gcd[k][mid] % _gcd != 0 || st._gcd[k][r - (1 << k) + 1] % _gcd != 0) { efl = mid + 1; } else { ok = mid; efr = mid - 1; } } l = ok - 1; if (l) _gcd = st.query(l, r); } } cout << ds.query(rt[n], 1, 100000, 1, 100000) + C << '\n'; } int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int T; cin >> T; for (int _ = 1; _ <= T; _++) { solve(_); } return 0; } ``` ## Code 2 ```cpp // 验题人(KSCD_)的代码 #include<iostream> #define ll long long using namespace std; const int N=1e5+10; const int K=18; const ll inf=2e18; int n,a[N],rt[N]; ll C,s[N]; int gcd(int a,int b) {return (b?gcd(b,a%b):a);} struct ST { int w[N][K],lg[N],po[K]; void build() { lg[0]=-1,po[0]=1; for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1,w[i][0]=a[i]; for(int i=1;i<18;i++) po[i]=po[i-1]*2; for(int k=1;po[k]<=n;k++) for(int i=1;i+po[k]-1<=n;i++) w[i][k]=gcd(w[i][k-1],w[i+po[k-1]][k-1]); } int query(int l,int r) { int k=lg[r-l+1]; return gcd(w[l][k],w[r-po[k]+1][k]); } }Ta; struct Exsgmtt { int t,lc[N*K*K],rc[N*K*K]; ll w[N*K*K]; int build(int l,int r) { int u=++t; w[t]=inf; if(l<r) { int mid=((l+r)>>1); lc[u]=build(l,mid),rc[u]=build(mid+1,r); } return u; } int update(int u,int l,int r,int p,ll x) { int v=++t; w[v]=min(w[u],x),lc[v]=lc[u],rc[v]=rc[u]; if(l<r) { int mid=((l+r)>>1); if(p<=mid) lc[v]=update(lc[v],l,mid,p,x); else rc[v]=update(rc[v],mid+1,r,p,x); } return v; } ll query(int u,int l,int r,int L,int R) { if(l>=L&&r<=R) return w[u]; ll res=inf; int mid=((l+r)>>1); if(L<=mid) res=min(res,query(lc[u],l,mid,L,R)); if(R>mid) res=min(res,query(rc[u],mid+1,r,L,R)); return res; } }T; void sol() { cin>>n>>C,T.t=0,T.build(0,n),rt[0]=T.update(1,0,n,0,0); for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1,x;i<=n;i++) cin>>x,s[i]=s[i-1]+x; Ta.build(); for(int i=1;i<=n;i++) { int L=1,R; rt[i]=rt[i-1]; while(L<=i) { int k=Ta.query(i-L+1,i),l=L,r=i; R=L; while(l<=r) { int mid=((l+r)>>1); if(Ta.query(i-mid+1,i)==k) R=mid,l=mid+1; else r=mid-1; } if(k<=R) { int j=max(k,L); ll f=T.query(rt[i-j],0,n,0,k)+1ll*k*(s[i]-s[i-j])+C; rt[i]=T.update(rt[i],0,n,j,f); } L=R+1; } } cout<<T.query(rt[n],0,n,1,n)<<'\n'; } int main() { ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); int TT; cin>>TT; while(TT--) sol(); return 0; } ```