Loading

CF671C Ultimate Weirdness of an Array

链接:https://www.luogu.com.cn/problem/CF671C

题目描述:定义\(f(i,j)\)表示一个序列去掉\([i,j]\)这一段后任意两数\(gcd\)的最大值,求所有\(f(i,j)\)的和。

题解:这个东西不能莫比乌斯反演,因为有一个最大值,那么我们考虑暴力维护\(gcd=d\)\((i,j)\)对数。

考虑\(gcd\)可能为\(d\)的数对满足什么条件,它必须是\([sa,sb],[ta,n],[1,tb]\)的一个子序列(\(sa\)\(gcd\)可能为\(d\)的最小值,\(ta\)\(gcd\)可能为\(d\)的次小值,\(sb\)\(gcd\)可能为\(d\)的大值,\(tb\)\(gcd\)可能为\(d\)的次大值)

由于\(gcd(i,j)\)可能为\(d\)\(d|gcd(i,j)\),那么\(gcd(i,j)\)被最后一次覆盖的就是它的权值,那么我们可以倒序枚举\(d\),再对区间进行覆盖,这样只要将不被覆盖的覆盖,然后统计要覆盖的区间对数即可。

将区间拖到网格图上,令区间\([l,r]\)对应从下到上的第\(l\)行,从左到右的第\(r\)列的元素,这个网格图即为一个下三角。因为下面的覆盖,上面的就一定会被覆盖,所以我们可以令\(d_{i}\)表示网格第\(i\)列的高度,一开始\(d_{i}=i\)

那么区间覆盖就变成了对\(d\)区间取\(min\),区间求和。

由于也是用与网格图一样的下三角进行覆盖,那么\(d_{i}<=j(j<i)\)一定是\(i\)被以\(j\)左边为左端点的下三角将它覆盖了,此时\(d_{j}\)也被覆盖了,\(d_{j}<=d_{i}\)

那么由于\(d\)单调,我们可以二分出覆盖区间,然后区间\(min\)变为区间赋值,这样线段树就可以直接维护了。

#include<iostream>
#include<cstdio>
#include<set>
#define N 200000
using namespace std;
struct node
{
  long long l,r,data,lazy;
};
node tree[800001];
int read()
{
  char c=0;
  int sum=0;
  while (c<'0'||c>'9')
    c=getchar();
  while ('0'<=c&&c<='9')
    {
      sum=sum*10+c-'0';
      c=getchar();
    }
  return sum;
}
int used[200001];
long long n,a[200001],min2[200001],max2[200001],ans,min1[200001],max1[200001];
void spread(int k)
{
  if (tree[k].lazy==-1)
    return;
  tree[k*2].data=tree[k].lazy*(tree[k*2].r-tree[k*2].l+1);
  tree[k*2].lazy=tree[k].lazy;
  tree[k*2+1].data=tree[k].lazy*(tree[k*2+1].r-tree[k*2+1].l+1);
  tree[k*2+1].lazy=tree[k].lazy;
  tree[k].lazy=-1;
  return;
}
void push_up(int k)
{
  tree[k].data=tree[k*2].data+tree[k*2+1].data;
}
void build(int k,int l,int r)
{
  tree[k].l=l;
  tree[k].r=r;
  tree[k].lazy=-1;
  if (l==r)
    {
      tree[k].data=l;
      return;
    }
  int mid=(l+r)/2;
  build(k*2,l,mid);
  build(k*2+1,mid+1,r);
  push_up(k);
  return;
}
void change(int k,int l,int r,int d,int ad)
{
  if (tree[k].l==l&&tree[k].r==r)
    {
      ans-=(d*(tree[k].r-tree[k].l+1)-tree[k].data)*ad;
      tree[k].lazy=d;
      tree[k].data=d*(tree[k].r-tree[k].l+1);
      return;
    }
  spread(k);
  int mid=(tree[k].l+tree[k].r)/2;
  if (l<=mid&&r<=mid)
    {
      change(k*2,l,r,d,ad);
      push_up(k);
      return;
    }
  if (l>=mid+1&&r>=mid+1)
    {
      change(k*2+1,l,r,d,ad);
      push_up(k);
      return;
    }
  change(k*2,l,mid,d,ad);
  change(k*2+1,mid+1,r,d,ad);
  push_up(k);
  return;
}
int query(int k,int x)
{
  if (tree[k].l==tree[k].r)
      return tree[k].data;
  spread(k);
  int mid=(tree[k].l+tree[k].r)/2;
  if (x<=mid)
    return query(k*2,x);
  else
    return query(k*2+1,x);
}
void ADD(int l,int r,int ad)
{
  if (l>r)
    return;
  int first=1,last=n,res=n+1,mid;
  while (first<=last)
    {
      mid=(first+last)/2;
      if (query(1,mid)>=l)
	{
	  res=min(res,mid);
	  last=mid-1;
	}
      else
	first=mid+1;
    }
  if (res<=r)
      change(1,res,r,l-1,ad);
  return;
}
int main()
{
  n=read();
  for (int i=1;i<=n;++i)
    {
      a[i]=read();
      used[a[i]]=i;
    }
  for (int i=1;i<=N;++i)
    min1[i]=1e9,max1[i]=-1e9,min2[i]=1e9,max2[i]=-1e9;
  for (int i=1;i<=N;++i)
    for (int j=i;j<=N;j+=i)
      if (used[j])
	{
	  if (used[j]<min1[i])
	    {
	      min2[i]=min1[i];
	      min1[i]=used[j];
	    }
	  else if (used[j]<min2[i])
	    min2[i]=used[j];
	  if (used[j]>max1[i])
	    {
	      max2[i]=max1[i];
	      max1[i]=used[j];
	    }
	  else if (used[j]>max2[i])
	    max2[i]=used[j];
	}
  build(1,1,n);
  for (int i=N;i>=1;--i)
    {
      ADD(min2[i]+1,n,i);
      ADD(1,max2[i]-1,i);
      ADD(min1[i]+1,max1[i]-1,i);
    }
  printf("%lld\n",ans);
  return 0;
}
posted @ 2022-12-14 21:55  zhouhuanyi  阅读(39)  评论(0)    收藏  举报