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 intN=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$出發的極大聯通塊的大小。
題解在有了。