1. 程式人生 > 實用技巧 >11月2日考試 題解(字首和+雜湊+樹狀陣列+樹鏈剖分)

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$出發的極大聯通塊的大小。

題解在有了。