8月18日考试 题解(动态规划+并查集+分块+提交答案题)
考的比昨天好,至少做对了一道题。T3很多白给部分分没看,感觉巨亏。
T1 蓝蓝的棋盘
题目大意:给定一个长度为$n$的序列。两个人轮流移动棋子,棋子一开始在$0$。每次可以移动的范围为$[p+1,\min (p+m,n)]$。两个人都按最优策略走。最优策略指自己的分减去对方的分最大。求先手的人的分数减去后手的人的分数。
一开始没看懂样例。看明白以后大力DP,刚了两个小时也没做出来。发现正着DP有后效性,因为你现在的策略可能不是最优的,但可能影响到后面的策略。不如倒着DP。
设$f[i]$表示从$i$出发到$n$的最大差值。假设另一个人的位置在$j$。两个人的差值是相反的,即在$j$的差值对于$i$的是$-f[j]$;因为从$j$转移过来,所以还要加上$a[j]$。所以有转移方程:$f[i]=\max\limits_{i<j\leq \min (n,i+m)} (f[i],a[j]-f[j])$
发现是一个线性DP的式子,很容易想到用单调队列优化。然而我单调队列写挂了QAQ,于是用的线段树。常数大了一点。
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int inf=1e18; int f[500005],n,m,a[500005]; int maxx[500005*4]; 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 update(int index,int l,int r,int pos,int x) { if (l==r) {maxx[index]=x;return;} int mid=(l+r)>>1; if (pos<=mid) update(index*2,l,mid,pos,x); else update(index*2+1,mid+1,r,pos,x); maxx[index]=max(maxx[index*2],maxx[index*2+1]); } inline int query(int index,int l,int r,int ql,int qr) { if (ql<=l&&r<=qr) return maxx[index]; int mid=(l+r)>>1,res=-inf; if (ql<=mid) res=max(res,query(index*2,l,mid,ql,qr)); if (qr>mid) res=max(res,query(index*2+1,mid+1,r,ql,qr)); return res; } signed main() { n=read();m=read(); for (int i=1;i<=n;i++) a[i]=read(); f[n]=0;f[n-1]=a[n]; update(1,1,n,n,a[n]-f[n]); update(1,1,n,n-1,a[n-1]-f[n-1]); for (int i=n-2;i>=0;i--) { f[i]=query(1,1,n,i+1,min(i+m,n)); update(1,1,n,i,a[i]-f[i]); } printf("%lld",f[0]); return 0; }
T2 淘淘的集合
给定$n$个集合$a[1],a[2],\cdots ,a[n]$,初始值为$0$。有4种操作:
1.合并$x,y$两个集合。如果$x,y$同属一个集合则忽略。
2.将$x$所在集合内所有数加上$y$。
3.$a[l]$到$a[r]$变为$0$。
4.求$\sum\limits_{i=l}^r a[i]$。
保证事件4的$\sum (r-l+1)\leq 10^7$。
暴力的$n^2$做法不难想到。暴力合并然后直接查询即可。上面的保证意味着我们可以暴力查询答案。
正解则是并查集+分块。并查集维护的是集合的合并:每次合并集合时采用启发式合并,可以证明复杂度是$n\log n$的。对于2操作,我们对每个集合打一个tag,合并的时候就把tag加到原数中去,查询就是原数的值加上tag的值。对于3操作我们分块维护:给每个位置打上时间戳标记,这样就变成了区间加。进行4操作时现在的值减去打上时间戳标记时的值就是答案。
对于1操作和2操作我们离线操作。时间复杂度$O(10^7+\sqrt n)$。
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int maxn=200005; int val[maxn],tag[maxn],fa[maxn];//set vector<int> v[maxn]; int a[maxn],tim[maxn],block,tot;//block int n,m,op[maxn],opx[maxn],opy[maxn],ans[maxn]; struct node { int id,t,x; bool operator < (node w) const{return t<w.t;} }tmp[maxn*5];vector<node> g[maxn]; int cntq,lq; struct Node { int id,t,l,r; bool operator <(Node w) const{return t<w.t;} }q[maxn];//lq2=cntq 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 int getpos(int x){return (x-1)/block+1;} inline void build() { block=sqrt(n);tot=n/block; if (n%block) tot++; for (int i=1;i<=tot;i++) tim[i]=-1; } inline void rebuild(int id) { if (tim[id]==-1) return; for (int i=(id-1)*block+1;i<=min(id*block,n);i++) a[i]=tim[id]; tim[id]=-1; } inline void update(int l,int r,int v) { int id; if (getpos(l)==getpos(r)) { id=getpos(l);rebuild(id); for (int i=l;i<=r;i++) a[i]=v; return; } rebuild(id=getpos(l)); for (int i=l;i<=min(id*block,n);i++) a[i]=v; rebuild(id=getpos(r)); for (int i=(id-1)*block+1;i<=r;i++) a[i]=v; for (int i=getpos(l)+1;i<id;i++) tim[i]=v; } inline int ask(int x){return tim[getpos(x)]==-1?a[x]:tim[getpos(x)];} inline int find(int x) { if (x==fa[x]) return x; return fa[x]=find(fa[x]); } inline void merge(int x,int y) { x=find(x),y=find(y); if (x==y) return; if (v[x].size()<v[y].size()) swap(x,y);fa[y]=x; for (int i=0;i<v[y].size();i++) val[v[y][i]]+=tag[y];tag[y]=0; for (int i=0;i<v[y].size();i++) val[v[y][i]]-=tag[x],v[x].push_back(v[y][i]); v[y].clear(); } inline int query(int x){return val[x]+tag[find(x)];} inline void add(int x,int v){x=find(x);tag[x]+=v;} signed main() { n=read();m=read(); build(); for (int i=1;i<=n;i++) fa[i]=i,v[i].push_back(i); for (int i=1;i<=m;i++) { op[i]=read(),opx[i]=read(),opy[i]=read(); if (op[i]==3) update(opx[i],opy[i],i); if (op[i]==4) { cntq++; q[cntq].id=cntq;q[cntq].t=i; q[cntq].l=opx[i];q[cntq].r=opy[i]; for (int j=opx[i];j<=opy[i];j++) { if (!ask(j)) continue; lq++; tmp[lq].id=cntq,tmp[lq].t=ask(j),tmp[lq].x=j; g[tmp[lq].t].push_back(tmp[lq]); } } } sort(q+1,q+cntq+1); int now=1; for (int i=1;i<=m;i++) { if (op[i]==1) merge(opx[i],opy[i]); if (op[i]==2) add(opx[i],opy[i]); for (int j=0;j<g[i].size();j++) ans[g[i][j].id]-=query(g[i][j].x); while(now<=cntq&&q[now].t<=i) { for (int j=q[now].l;j<=q[now].r;j++) ans[q[now].id]+=query(j); now++; } } for (int i=1;i<=cntq;i++) printf("%lld\n",ans[i]); return 0; }
T3 蓝蓝的程序
提交答案的SB题。
Subtask1:给定一个长度为$n$的序列。求$(\sum\limits_{i=1}^n a[i])+\max \limits_{1\leq i\leq n}(a[i])+\min \limits_{1\leq i\leq n}(a[i])$
硬搞即可。
Subtask2:给定一个矩阵。求二维前缀和之和。
考虑每个元素的贡献。类比二维树状数组,每个元素能产生$(n-i+1)*(n-j+1)$次贡献。$n^2$硬搞即可。
Subtask3:给定一个矩阵,求二维前缀和的前缀和之和。
还是考虑每个元素的贡献。设$f(n)=\frac{n(n+1)}{2}$,则每个元素能产生$f(n-i+1)*f(n-j+1)$次贡献。
Subtask4:直接输出$n$。很显然。
Subtask5:求$\sum\limits_{i=1}^n i$
高中数学必修五
Subtask6:求$\sum\limits_{i=1}^n i^2$
之前背过通项公式:$\frac{n(n+1)(2n+1)}{6}$
Subtask7:求$\sum\limits_{i=1}^n i^3$
$\frac{n^2(n+1)^2}{4}$
Subtask8:求$\sum\limits_{i=1}^n i^4$
通项公式:$\frac{n^8-1}{15}$。当然也可以拉格朗日插值做(我不会QAQ
Subtask9:给定一个$01$矩阵。问不含$0$的子矩阵个数。
单调栈。
Subtask10:给定一个$01$矩阵,问不含$0$的子矩阵的面积之和。
单调栈。上述两个子任务我都不会QAQ
上述子任务有70分是可做的,考试的时候没看这个题太TM亏了。