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$出发的极大联通块的大小。
题解在有了。