10月26日考试 题解(gcd+思维题+树状数组+树套树)
由于前几天的题实在太屑了,不是很想写题解,于是就这么咕掉了……
T1 解方程
题目大意:询问三元一次不定方程$ax+by+cz=d$是否有解。
简单推一下式子:
$ax+by=g=d-cz$
$cz+gw=d$
于是看$\gcd(c,g)|d$的情况即可。
代码:
#include<cstdio> #include<iostream> #include<cmath> #define int long long using namespace std; int a,b,c,d,T; 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 gcd(int x,int y){ return y==0?x:gcd(y,x%y); } signed main() { T=read(); while(T--) { a=read(),b=read(),c=read(),d=read(); if (a==b&&b==c&&c==0){ if (d==0) cout<<"YES"<<endl; else cout<<"NO"<<endl; continue; } int g=gcd(a,gcd(b,c)); if (d%g==0) cout<<"YES"<<endl; else cout<<"NO"<<endl; } return 0; }
T2 猜球游戏
题目大意:给定$0-9$十个数,$n$个操作,每次交换两个数。每次询问经过$[l,r]$操作过后这$10$个数的位置。$n\leq 10^5$。
发现我们只是关心位置的相对变化,所以我们不妨把$i$次操作后这十个数的位置都存下来,每次询问的时候将$l-1$和$r$时的状态作比较即可。
我写的比较劣,是$O(100\times n)$的,事实上可以优化到$O(10\times n)$。
代码:
#include<cstdio> #include<iostream> using namespace std; const int N=100005; int sta[N][10],a[10],n,m,x,y,l,r; 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; } int main() { n=read();m=read(); for (int i=0;i<=9;i++) sta[0][i]=i; for (int i=1;i<=n;i++) { for (int j=0;j<=9;j++) sta[i][j]=sta[i-1][j]; x=read(),y=read(); swap(sta[i][x],sta[i][y]); } while(m--) { int l=read(),r=read(); for (int i=0;i<=9;i++) { int num=sta[l-1][i],pos=i; for (int j=0;j<=9;j++) if (num==sta[r][j]) { a[j]=pos;break; } } printf("%d",a[0]); for (int i=1;i<=9;i++) printf(" %d",a[i]); cout<<endl; } return 0; }
T3 凑数游戏
题目大意:给定一个长度为$n$的序列$a$,每次询问$[l,r]$内最小的不能表示的数是多少。
考试的时候发现了一条性质:将序列内的数从小到大排序,设$s_i=\sum\limits_{j=1}^i a_j$,若$s_i<a_i-1$,那么$s_i+1$就是第一个不能被表示的数字。关于这点我不太会证明,但是可以感性理解一下:$0$到$s_i$之间的所有数都可以被表示,下一个能表示的最小的数是$a_i$,若之间还有数那么这些数就不能被表示。于是我们就有了一个$O(nm\log n)$的做法。
现在我们做的就是想办法将$sort$的$n\log n$降到$\log n$。利用主席树迭代更新显然能解决这个问题(~~因为太显然所以我不会写~~);也可以将询问挂在序列上,每次尽可能尝试更新更大的值,如果更新过后的值与原来相同就退出循环。迭代的复杂度是$\log n$的,因为每一次更新都至少将答案扩大到原来的二倍。
总时间复杂度$O(n\log^2 n)$。
代码:
#include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define int long long using namespace std; const int N=100005; int a[N],b[N],idx[N],n,m,len; int tree[N],sum[N],pre[N]; vector< pair<int,int> > v[N]; 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 y){ for (int i=x;i<=len;i+=i&(-i)) tree[i]+=y; } inline int query(int x){ int res=0; for (int i=x;i;i-=i&(-i)) res+=tree[i]; return res; } inline int solve(int x) { int l=1,r=len,mid,ans; while(l<=r) { mid=(l+r)>>1; if (b[mid]<=x) l=mid+1,ans=mid; else r=mid-1; } return ans; } signed main() { n=read();m=read(); for (int i=1;i<=n;i++) b[i]=a[i]=read(); sort(b+1,b+n+1); len=unique(b+1,b+n+1)-b-1; for (int i=1;i<=n;i++) idx[i]=lower_bound(b+1,b+len+1,a[i])-b; for (int i=1;i<=m;i++) { int l=read(),r=read(); v[l-1].push_back(make_pair(i,-1)); v[r].push_back(make_pair(i,1)); } memset(pre,-1,sizeof(pre)); while(1) { bool flag=1; for (int i=1;i<=m;i++) if (sum[i]!=pre[i]){ flag=0;break; } if (flag) break; memcpy(pre,sum,sizeof(pre)); memset(tree,0,sizeof(tree)); for (int i=1;i<=n;i++) { add(idx[i],a[i]); for (int j=0;j<v[i].size();j++) { int qid=v[i][j].first,f=v[i][j].second; int sid=solve(pre[qid]+1); sum[qid]+=query(sid)*f; } } for (int i=1;i<=m;i++) sum[i]-=pre[i]; } for (int i=1;i<=m;i++) printf("%lld\n",sum[i]+1); return 0; }
T4 RPG游戏
题目大意:给定一个$n\times m$的网格图,每个格子有两种权值$val_i$和$buff_i$,表示这个格子的权值和选择这个格子后直到下一个选择的格子每走一步增加的权值;人从$(1,1)$出发到$(n,m)$。人可以经过格子但不选这个格子。问获得的权值最大值是多少。
cdq+李超线段树。放在noip确实有点难(对于我来说QAQ)。
std写的是cdq+cdq;听同机房大佬写的树状数组+李超线段树后觉得挺好,于是按照这种做法改了改题。
首先很容易得出转移方程:$f_{i,j}=\max\limits_{x\leq i,y\leq j}(f_{i,j},f_{x,y}+dis\times b_{x,y}+v_{i,j})$
我们把式子拆开,可以得到:$f_{i,j}=b_{x,y}\times (i+j)+f_{x,y}-b_{x,y}\times (x+y)+v_{i,j}$
很像斜率优化的式子,但它并不是斜率优化,因为它的斜率不满足单调性(我考试就是这么想的然后凉了……)所以要维护动态凸包,可以用cdq分治或者李超线段树来维护;对于$x,y$的限制可以用树状数组来解决二维偏序关系。
时间复杂度$O(n\log^2 n)$,空间复杂度$O(n\log n)$。
代码:
#include<cstdio> #include<iostream> #define all 400000 #define inf 1000000000000 #define lowbit(x) (x&(-x)) #define mid ((l+r)>>1) #define d(i,j) ((i-1)*m+(j)) using namespace std; typedef long long ll; const int N=200005; int rt[N*21],v[N*21],ls[N*21],rs[N*21],tot,n,m; ll k[N],b[N],f[N],val[N],buff[N]; 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 ll get(int id,ll x){ return k[id]*x+b[id]; } inline void update(int &cur,int l,int r,int id) { int t=v[cur]; if(!cur) { cur=++tot,v[cur]=id; return; } if(get(id,l)>=get(t,l)&&get(id,r)>=get(t,r)) { v[cur]=id; return; } if(get(id,l)<get(t,l)&&get(id,r)<get(t,r)) return; if(k[id]>k[t]) { if(get(id,mid)<get(t,mid)) update(rs[cur],mid+1,r,id); else update(ls[cur],l,mid,t),v[cur]=id; } else { if(get(id,mid)<get(t,mid)) update(ls[cur],l,mid,id); else update(rs[cur],mid+1,r,t),v[cur]=id; } } inline ll query(int p,int l,int r,int x) { if (!p) return -inf; if (l==r) return get(v[p],x); ll k=get(v[p],x); if (x<=mid) k=max(k,query(ls[p],l,mid,x)); else k=max(k,query(rs[p],mid+1,r,x)); return k; } inline void add(int x,int id){ for (int i=x;i<=m;i+=lowbit(i)) update(rt[i],1,all,id); } inline ll query(int x,int id){ ll res=0; for (int i=x;i;i-=lowbit(i)) res=max(res,query(rt[i],1,all,id)); return res; } signed main() { n=read();m=read(); for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) buff[d(i,j)]=read(); for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) val[d(i,j)]=read(); for(int i=1;i<=n;++i) { for(int j=1;j<=m;++j) { int id=d(i,j); if(i!=1||j!=1) f[id]=query(j,i+j)+val[id]; k[id]=buff[id],b[id]=f[id]-(i+j)*buff[id],add(j,id); } } printf("%lld",f[d(n,m)]); return 0; }