Logo Pigsyy 的博客

博客

「WyOJ Round 1」归 · 星穗垂野

...
Pigsyy
2025-12-01 12:58:03

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-03-31 13:39:05

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. $b_i\ge 0$,那么显然 $j$ 越小 $\sum$ 越小。
  2. 转移中有 $f_{i,j}=f_{i-1,j}$,所以 $k$ 相同的情况下,$i-j$ 值越大必然 $f_{i-j,k}$ 越小。而且 $\gcd$ 相同的情况 $k$ 的范围是一样的。

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

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

$f_{i,j}=f_{i-1,j}$ 的转移直接通过继承 $i-1$ 版本的线段树得到即可。

时空复杂度均为 $O(n\log n\log V)$。

Code 1

\/\/ 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

\/\/ 验题人(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;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。