省队集训Round2 DAY1

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/clover_hxy/article/details/73729153

T1

这里写图片描述
这里写图片描述

题解

对于每个位置都可以暴力的找到最右边的一个点使之后的点再异或异或和下降。
可以维护一颗主席树,外层表示的是起点,内层表示的是以该点为起点的所以终点的合法区间。
每次利用前缀和作差即可。因为是区间操作所以我们标记永久化一下。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define N 100003
using namespace std;
struct data{
    int ls,rs,x,sum;
}tr[N*60];
int a[N],n,m,sz,root[N];
void update(int i,int lx,int rx)
{
    int l=tr[i].ls; int r=tr[i].rs;
    tr[i].sum=tr[i].x*(rx-lx+1);
    if (l) tr[i].sum+=tr[l].sum;
    if (r) tr[i].sum+=tr[r].sum;
}
void insert(int &i,int j,int l,int r,int ll,int rr)
{
    i=++sz;
    tr[i]=tr[j];
    if (ll<=l&&r<=rr) {
        tr[i].x+=1;
        update(i,l,r);
        return;
    }
    if (l==r) return;
    int mid=(l+r)/2;
    if (ll<=mid) insert(tr[i].ls,tr[j].ls,l,mid,ll,rr);
    if (rr>mid) insert(tr[i].rs,tr[j].rs,mid+1,r,ll,rr);
    update(i,l,r);
}
int qjchange(int i,int j,int l,int r,int ll,int rr,int v)
{
    if (ll<=l&&r<=rr) return tr[i].sum-tr[j].sum+(r-l+1)*v;
    int mid=(l+r)/2; int ans=0;
    if (ll<=mid) ans+=qjchange(tr[i].ls,tr[j].ls,l,mid,ll,rr,v+tr[i].x-tr[j].x);
    if (rr>mid) ans+=qjchange(tr[i].rs,tr[j].rs,mid+1,r,ll,rr,v+tr[i].x-tr[j].x);
    return ans;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=n;i++) {
        int x=0; int pos=i;
        for (int j=i;j<=n;j++)
         if ((x^a[j])>=x) x^=a[j],pos=j;
         else break;
    //  cout<<i<<" "<<pos<<endl;
        insert(root[i],root[i-1],1,n,i,pos);
    }
    scanf("%d",&m);
    int ans=0;
    for (int i=1;i<=m;i++) {
        int l,r; scanf("%d%d",&l,&r);
        l=(l+ans)%n+1; r=(r+ans)%n+1;
    //  cout<<l<<" "<<r<<endl;
        if (l>r) swap(l,r);
        ans=qjchange(root[r],root[l-1],1,n,l,r,0);
        printf("%d\n",ans);
    }
} 


T2

这里写图片描述
这里写图片描述

题解

从高的开始消,消到左右两边较高的位置,然后把他们合并成一大列一起向下消。
关键就是怎么划分,对于每一大列维护del[i]表示的是如果当前阶段继续向下消,上面还有多少空可以补。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector>
#define N 200003
#define LL long long 
using namespace std; 
set<pair<long long,int> > p;
LL k,f[N],h[N],del[N];  int n,l[N],r[N],fa[N];
int find(int x)
{
    if (fa[x]==x) return x;
    fa[x]=find(fa[x]);
    return fa[x];
}
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d%lld",&n,&k);
    for (int i=1;i<=n;i++) scanf("%lld",&h[i]);
    for (int i=1;i<=n;i++) {
        p.insert(make_pair(h[i],i));
        l[i]=r[i]=fa[i]=i;
    }
    LL ans=0;
    while (p.size()) {
        int x=(*--p.end()).second;
        p.erase(--p.end());
        LL L=l[x]-1==0?0:h[find(l[x]-1)];
        LL R=r[x]+1==n+1?0:h[find(r[x]+1)];
        LL hi=max(L,R);
        LL sum=(LL)(r[x]-l[x]+1)*(h[x]-hi);
        if (del[x]>=sum) del[x]-=sum;
        else sum-=del[x],ans+=(sum-1)/k+1,del[x]=(k-sum%k)%k;
        h[x]=hi;
        if (hi!=0) {
            if (L==hi) {
                int t=find(l[x]-1);
                del[x]+=del[t];
                p.erase(make_pair(L,t));
                fa[t]=x; l[x]=l[t];
            }
            if (R==hi) {
                int t=find(r[x]+1);
                del[x]+=del[t];
                p.erase(make_pair(R,t));
                fa[t]=x; r[x]=r[t];
            }
            p.insert(make_pair(h[x],x));
        }
    }
    printf("%lld\n",ans);
}


T3

这里写图片描述
这里写图片描述

题解

首先需要知道斐波那契数列的一个性质

gcd(f[a],f[b])=f[gcd(a,b)]

这个怎么证明?先证明 gcd(f[a],f[a+1])=1
f[0]=1,f[1]=1,gcd(f[0],f[1])=1
利用更相减损, gcd(f[a],f[a+1])=gcd(f[a],f[a]+f[a1])=gcd(f[a],f[a1]) ,最终得到 gcd(f[a],f[a+1])=gcd(f[0],f[1])
还需要用到一个式子
f[n+m]=f[n+1]f[m]+f[n]f[m1]

这道题有一种有趣的组合的证明方法(摘自小火车的论文)
这里写图片描述
这里写图片描述
gcd(f[a],f[b])=gcd(f[a],f[a]f[ba+1]+f[a1]f[ba])=gcd(f[a],f[a1]f[ba])
因为 gcd(f[a],f[a1])=1 ,那么 gcd(f[a],f[b])=gcd(f[a],f[ba]) ,可以递归,就是辗转相除嘛。那么上面的问题得证。
lcm(f[a],f[b])=f[a]f[b]f[gcd(a,b)]

设读入的下标构成的集合为 S ,那么
lcm=TS,Tf[gcd(T)](1)|T|1,gcd(T)T

然后考虑枚举 gcd(T)=i
lcm=i=1maxf[i]TS,T,gcd(T)=i(1)|T|1

式子的瓶颈在于 gcd(T)=i ,我们可以利用莫比乌斯反演进行转化。
g(i)=TS,T,gcd(T)=i(1)|T|1,h(i)=TS,T,i|gcd(T)(1)|T|1
T 比较讨厌,我们发现空集比较讨厌,考虑直接提出来。然后再把指数中的 1 提出来,式子可以化简成
h(i)=1TS,i|gcd(T)(1)|T|

TS,i|gcd(T)(1)|T|=|S|i=0(1)iC(|S|,i),C
其实就是杨辉三角中某一行的偶数列的和-奇数列的和,在 |S|>0 是式子的值恒等于0
所以
h(i)=[i]

根据莫比乌斯反演
g(n)=d|nh(d)μ(dn)

那么 lcm=i=1maxf[i]g[i] 可以用 O(nlogn) 的时间预处理 g[i] 数组,然后再用快速幂求解。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 1000003
#define p 1000000007
#define LL long long
using namespace std;
int mu[N],a[N],cnt[N],h[N],g[N],n,pd[N],prime[N],f[N];
LL quickpow(LL num,int x)
{
    LL ans=1,base=num%p;
    x=(x%(p-1)+p-1)%(p-1);
    while (x) {
        if (x&1) ans=ans*base%p;
        x>>=1;
        base=base*base%p;
    }
    return ans;
}
void init(int n)
{
    mu[1]=1;
    for (int i=2;i<=n;i++) {
        if (!pd[i]) {
            prime[++prime[0]]=i;
            mu[i]=-1;
        }
        for (int j=1;j<=prime[0];j++) {
            if (prime[j]*i>n) break;
            pd[i*prime[j]]=1;
            if (i%prime[j]==0) break;
            mu[i*prime[j]]=-mu[i];
        }
    }
    f[0]=0; f[1]=1;
    for (int i=2;i<=n;i++) f[i]=(f[i-1]+f[i-2])%p;
    for (int i=1;i<=n;i++) {
        if (!h[i]) continue;
        for (int j=i;j<=n;j+=i) g[i]+=h[j]*mu[j/i];
    }
}
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d",&n);
    int mx=0;
    for (int i=1;i<=n;i++) scanf("%d",&a[i]),mx=max(a[i],mx);
    for (int i=1;i<=n;i++) {
        for (int x=1;x*x<=a[i];x++)
         if (a[i]%x==0) h[x]=1,h[a[i]/x]=1;
    }
    init(mx);
    LL ans=1;
    for (int i=1;i<=mx;i++)
     ans=ans*quickpow(f[i],g[i])%p;
    printf("%lld\n",ans);
}

猜你喜欢

转载自blog.csdn.net/clover_hxy/article/details/73729153