11月2日考试 题解(前缀和+哈希+树状数组+树链剖分)

T1 计算异或和

题目大意:给定一个长度为$n$的序列$a_i$,设$b_i=a_i \oplus \ i\mod 1 \oplus\ i\mod 2\oplus \cdots \oplus\ i\mod n$,求出$q_1\oplus q_2\oplus \cdots \oplus q_n$。

可以单独把$i \mod k$这样一类式子提出来,发现有循环节,前缀异或和维护一下即可。

代码:

#include<cstdio>
#include<iostream>
#define int long long
using namespace std;
const int N=1000005;
int p[N],sum[N],n,ans;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
signed main()
{
    n=read();
    for (int i=1;i<=n;i++) 
    {
        p[i]=read();
        sum[i]=i^sum[i-1];ans^=p[i];
    }    
    for (int i=2;i<=n;i++)
    {
        int num=n/i,rest=n%i;
        if (num&1) ans^=sum[i-1];
        ans^=sum[rest];
    }
    printf("%lld",ans);
    return 0;
}

T2 配置香水

给定长度为$n$的序列$a_i$和整数$k$,问有多少$[l,r]$满足$\sum\limits_{i=l}^r a_i=k^j(j\geq 0)$。

发现题目要求形如这样$sum_r-sum_l=k^i$,我们变换一下形式:$sum_r-k_i=sum_l$。于是可以枚举$i$,然后用哈希表维护一下看有多少个合法的$sum_l$即可。

用了map成功被卡掉50分常数

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define int long long
using namespace std;
const int N=100005,M=500005;
const int up=1e14;
const int mod=499999;
int T,n,k,sum[N],ans;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct hash{
    int nxt[500005],hd[500005],cnt,a[500005],b[500005];
    void clear(){
        memset(hd,0,sizeof(hd));
        cnt=0;
    }
    void insert(int x){
        int t=((x%mod)+mod)%mod;
        nxt[++cnt]=hd[t];
        a[cnt]=x;
        b[cnt]=1;
        hd[t]=cnt;
    }
    bool find(int x){
        int t=((x%mod)+mod)%mod;
        for(int i=hd[t];i;i=nxt[i]){
            if(a[i]==x) return 1;
        }
        return 0;
    }
    void add(int x){
        int t=((x%mod)+mod)%mod;
        for(int i=hd[t];i;i=nxt[i]){
            if(a[i]==x) b[i]++;
        }
    }
    int ask(int x){
        int t=((x%mod)+mod)%mod;
        for(int i=hd[t];i;i=nxt[i]){
            if(a[i]==x) return b[i];
        }
        return 0;
    }
}h;
inline void solve(int x)
{
    h.clear();
    for (int i=0;i<=n;i++)
    {
        if (h.find(sum[i])) h.add(sum[i]);
        else h.insert(sum[i]);
        ans+=h.ask(sum[i]-x);
    }
}
signed main()
{
    T=read();
    while(T--)
    {
        n=read();k=read();ans=0;
        for (int i=1;i<=n;i++)
            sum[i]=sum[i-1]+read();
         if(k==1){
            solve(1);
            printf("%lld\n",ans);
            continue;
        }
        if(k==-1){
            solve(1);solve(-1);
            printf("%lld\n",ans);
            continue;
        }
        int kk=1;
        while(kk<=(long long)1000000000*n){
            solve(kk);
            kk*=k;        
        }
        printf("%lld\n",ans);
    }
    return 0;
}

T3 奥法之劫

题目大意:给定长度为$n$的序列$a_i,p_i$和长度为$m$的序列$b_i$。$p_i$为删掉$a_i$的代价,$b_i$单调递增。现要求删掉一些数,使得能从中依次选出$m$个数组成$b_i$,且对于任意$i\in[1,m]$,满足$b_{i-1}$和$b_i$之间所有数都小于$b_{i-1}$。求最小代价。

考场上写出来了$n^2$DP,想到了$n\log n$做法然而没调出来,自闭了。

设$f_{i,j}$表示$a$考虑到$i$,$b$考虑到$j$时的最小代价。显然对于$a_i$和$b_j$的大小关系有三种情况,分别转移就好。然后发现$j$这一维可以省去,因为对于$a_i<b_j$和$a_i>b_j$的情况它们都由$f_{i-1,j}$转移过来且后面加的都是个常数,且$j$显然是一段区间,所以可以数据结构维护。对于$a_i=b_j$的情况可以单点修改,时间复杂度$O(n\log n)$。然而它写挂了QAQ。

提供另一种$n\log n$的做法,好写好调。大致思路是维护一个权值树状数组,每次对于$a_i=b_j$的情况进行转移。每次找出大于$b_{k-1}$小于$b_k$的$a_i$,然后计算它们对答案的贡献。

正解复杂度是$O(n)$的,然而我并不太会。

代码:

#include<cstdio>
#include<iostream>
#define lowbit x&-x
#define ll long long
using namespace std;
const int N=5000005;
const ll inf=1e18;
ll tree[N],f[N];
int cnt[N],a[N],p[N],b[N],pos[N],n,m;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void add(int x,int k){while(x){tree[x]+=k;x-=lowbit;}}
inline ll query(int x){ll sum=0;while(x<=n+1){sum+=tree[x];x+=lowbit;}return sum;}
inline void solve()
{
    cnt[0]=1;
    for (int i=1;i<=n;i++) f[i]=inf;
    for (int i=1;i<=n;i++)
    {
        int k=pos[a[i]]; ll ff=0;
        if (k&&cnt[k-1]>0) ff=query(b[k-1]+1)+f[k-1];
        add(p[i]>=0?a[i]:n+1,p[i]);
        if (k&&cnt[k-1]>0)
        {
            ff-=query(b[k]+1);
            f[k]=min(f[k],ff);
            ++cnt[k];
        }
    }
    if (!cnt[m]){
        puts("Impossible");
        return;
    }
    ll ans=f[m]+query(b[m]+1);
    printf("%lld",ans);
}
signed main()
{
    n=read();
    for (int i=1;i<=n;i++) a[i]=read();
    for (int i=1;i<=n;i++) p[i]=read();
    m=read();
    for (int i=1;i<=m;i++) b[i]=read(),pos[b[i]]=i;
    solve();
    return 0;
}

T4 多彩树

题目大意:给定一棵含有$n$个节点的树,每个节点有颜色$c_i$。每次只能走向$(c_i+1)\mod C$的节点。$q$次操作,带修,询问从$x$出发的极大联通块的大小。

题解在有了。

posted @ 2020-11-03 19:30  我亦如此向往  阅读(131)  评论(0编辑  收藏  举报