CSP提高组模拟1
CSP提高组模拟1
\(T1\) T5420. 最短路 \(80pts\)
-
\(Floyd\) 的本质是动态规划,最外层枚举的 \(k\) 是阶段,表示中转点的编号不超过 \(k\) 。同样地,本题中可以对点权排序,转移过程中钦定 \(k\) 是最大的,而 \(i,j\) 同理。
-
数据有点水,将边权和点权混在一起转移的错解因写得有点好看拿到了 \(80pts\) 。跑两遍 \(Floyd\) 就 \(AC\) 了。
点击查看代码
pair<ll,ll>a[310]; ll dis[310][310],ans[310][310]; int main() { freopen("path.in","r",stdin); freopen("path.out","w",stdout); ll n,m,q,u,v,w,i,j,k; cin>>n>>m>>q; memset(dis,0x3f,sizeof(dis)); memset(ans,0x3f,sizeof(ans)); for(i=1;i<=n;i++) { cin>>a[i].first; a[i].second=i; } sort(a+1,a+1+n); for(i=1;i<=m;i++) { cin>>u>>v>>w; dis[u][v]=dis[v][u]=min(dis[u][v],w); } for(k=1;k<=n;k++) { for(i=1;i<=n;i++) { if(a[i].second!=a[k].second) { for(j=1;j<=n;j++) { if(a[i].second!=a[j].second&&a[k].second!=a[j].second) { dis[a[i].second][a[j].second]=min(dis[a[i].second][a[j].second],dis[a[i].second][a[k].second]+dis[a[k].second][a[j].second]); ans[a[i].second][a[j].second]=min(ans[a[i].second][a[j].second],dis[a[i].second][a[j].second]+max(a[i].first,max(a[j].first,a[k].first))); } } } } } for(i=1;i<=q;i++) { cin>>u>>v; cout<<((ans[u][v]==0x3f3f3f3f3f3f3f3f)?-1:ans[u][v])<<endl; } fclose(stdin); fclose(stdout); return 0; }
\(T2\) T5421. 方格取数 \(35pts\)
-
部分分
- \(35pts\) :枚举每个子矩形,二维前缀和维护,时间复杂度为 \(O(n^{4})\) 。
- 在枚举的过程中如果 \(S<k\) 或 \(S>2k\) 了则及时停止了也需要及时停止。但这两种写法不能同时写,涉及到枚举顺序问题。
- 据此做法,还有一些玄学优化,但仅能减小常数。
- 最优性剪枝。
- \(10pts\) :\(S \in [2,4]\) 说明最多有 \(4\) 个格子,从每个位置进行扩展即可。
- \(5pts\) :找到一个位置的数 \(\le 2k\) 即可。
- \(50pts\) :发现 \(S\) 具有单调性,可以二分查找其中一个坐标,将时间复杂度降至 \(O(n^{3} \log_{2}n)\) 。
-
此做法是假的,在 luogu 上判无解放不过去。
点击查看代码
ll a[2010][2010],sum[2010][2010]; ll query(ll x1,ll y1,ll x2,ll y2) { return sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]; } int main() { freopen("matrix.in","r",stdin); freopen("matrix.out","w",stdout); ll n,m,i,j,k,h,l,r,mid; scanf("%lld%lld",&n,&m); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { scanf("%lld",&a[i][j]); } } for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]; } } for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { for(k=1;k<=i;k++) { l=1; r=j; while(l<=r) { mid=(l+r)/2; if(m<=query(k,mid,i,j)&&query(k,mid,i,j)<=2*m) { printf("%lld %lld %lld %lld\n",k,mid,i,j); goto label; } else { l=mid+1; } } } } } printf("-1\n"); label: fclose(stdin); fclose(stdout); return 0; }
-
- \(100pts\) :继续二分查找另一个坐标,时间复杂度为 \(O(n^{2} \log_{2}^{2}n)\) ,加个超级快读卡卡常就 \(AC\) 了。
-
此做法是假的,在 luogu 上判无解放不过去。
点击查看代码
#define LOCAL namespace IO{ #ifdef LOCAL FILE*Fin(fopen("matrix.in","r")),*Fout(fopen("matrix.out","w")); #else FILE*Fin(stdin),*Fout(stdout); #endif class qistream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qistream(FILE*_fp=stdin):fp(_fp),p(0){fread(buf+p,1,SIZE-p,fp);}void flush(){memmove(buf,buf+p,SIZE-p),fread(buf+SIZE-p,1,p,fp),p=0;}qistream&operator>>(char&x){x=getch();while(isspace(x))x=getch();return*this;}template<class T>qistream&operator>>(T&x){x=0;p+BLOCK>=SIZE?flush():void();bool flag=false;for(;!isdigit(buf[p]);++p)flag=buf[p]=='-';for(;isdigit(buf[p]);++p)x=x*10+buf[p]-'0';x=flag?-x:x;return*this;}char getch(){p+BLOCK>=SIZE?flush():void();return buf[p++];}qistream&operator>>(char*str){char ch=getch();while(ch<=' ')ch=getch();int i=0;for(;ch>' ';++i,ch=getch())str[i]=ch;str[i]='\0';return*this;}}qcin(Fin); class qostream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qostream(FILE*_fp=stdout):fp(_fp),p(0){}~qostream(){fwrite(buf,1,p,fp);}void flush(){fwrite(buf,1,p,fp),p=0;}template<class T>qostream&operator<<(T x){int len=0;p+BLOCK>=SIZE?flush():void();x<0?(x=-x,buf[p++]='-'):0;do buf[p+len]=x%10+'0',x/=10,++len;while(x);for(int i=0,j=len-1;i<j;++i,--j)std::swap(buf[p+i],buf[p+j]);p+=len;return*this;}qostream&operator<<(char x){putch(x);return*this;}void putch(char ch){p+BLOCK>=SIZE?flush():void();buf[p++]=ch;}qostream&operator<<(char*str){for(int i=0;str[i];++i)putch(str[i]);return*this;}qostream&operator<<(const char*s){for(int i=0;s[i];++i)putch(s[i]);return*this;}}qcout(Fout); } #define cin IO::qcin #define cout IO::qcout ll sum[2010][2010]; inline ll query(ll x1,ll y1,ll x2,ll y2) { return sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]; } int main() { freopen("matrix.in","r",stdin); freopen("matrix.out","w",stdout); ll n,m,i,j,num,minn,lk,rk,midk,lh,rh,midh; cin>>n>>m; for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { cin>>sum[i][j]; sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+sum[i][j]; } } for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { lk=1; rk=i; while(lk<=rk) { midk=(lk+rk)>>1; lh=1; rh=j; minn=0x7f7f7f7f7f7f7f7f; while(lh<=rh) { midh=(lh+rh)>>1; num=query(midk,midh,i,j); minn=min(minn,num); if(m<=num&&num<=2*m) { cout<<midk<<" "<<midh<<" "<<i<<" "<<j<<endl; goto label; } else { if(num<m) { rh=midh-1; } else { lh=midh+1; } } } if(minn<m) { rk=midk-1; } else { lk=midk+1; } } } } cout<<"-1"<<endl; label: fclose(stdin); fclose(stdout); return 0; }
-
- \(35pts\) :枚举每个子矩形,二维前缀和维护,时间复杂度为 \(O(n^{4})\) 。
-
正解
- \(\exists i,j \in [1,n],k \le a_{i,j} \le 2k\) ,则 \((i,j)\) 即为所求。假设此时矩阵中不再含有 \(k \le a_{i,j} \le 2k\) 的元素。
- 将 \(a_{i,j}>2k\) 的视作 \(0\) ,将 \(a_{i,j}<k\) 的视作 \(1\) ,那么最终选出的矩阵一定全是 \(1\) 。
- 单调栈维护出最大子矩形,答案矩阵一定被包含在其中。
- 一行一行切,如果切下来的能通过一列一列切列直到符合题意能输出,否则舍弃这一行。
点击查看代码
ll a[2010][2010],sum[2010][2010],f[2010][2010],l[2010],r[2010]; stack<ll>s; ll query(ll x1,ll y1,ll x2,ll y2) { return sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]; } int main() { freopen("matrix.in","r",stdin); freopen("matrix.out","w",stdout); ll n,k,flag=0,ans=0,x1=0,y1=0,x2=0,y2=0,i,j; scanf("%lld%lld",&n,&k); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { scanf("%lld",&a[i][j]); if(k<=a[i][j]&&a[i][j]<=2*k) { printf("%lld %lld %lld %lld\n",i,j,i,j); flag=1; } } } for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]; } } if(flag==0) { for(i=1;i<=n;i++) { while(s.empty()==0) { s.pop(); } f[i][0]=-1; s.push(0); for(j=1;j<=n;j++) { f[i][j]=(a[i][j]<k)*(f[i-1][j]+1); while(s.empty()==0&&f[i][s.top()]>=f[i][j]) { s.pop(); } l[j]=(s.empty()==0)?s.top()+1:1; s.push(j); } while(s.empty()==0) { s.pop(); } f[i][n+1]=-1; s.push(n+1); for(j=n;j>=1;j--) { while(s.empty()==0&&f[i][s.top()]>=f[i][j]) { s.pop(); } r[j]=(s.empty()==0)?s.top()-1:-1; s.push(j); if(f[i][j]!=0) { if(k<=query(i-f[i][j]+1,l[j],i,r[j])&&query(i-f[i][j]+1,l[j],i,r[j])<=2*k) { printf("%lld %lld %lld %lld\n",i-f[i][j]+1,l[j],i,r[j]); flag=1; break; } else { if(query(i-f[i][j]+1,l[j],i,r[j])>ans) { ans=query(i-f[i][j]+1,l[j],i,r[j]); x1=i-f[i][j]+1; y1=l[j]; x2=i; y2=r[j]; } } } } if(flag==1) { break; } } if(flag==0) { if(ans<k) { printf("-1\n"); } else { for(i=x2;i>=x1;i--) { if(k<=query(i,y1,i,y2)&&query(i,y1,i,y2)<=2*k) { printf("%lld %lld %lld %lld\n",i,y1,i,y2); break; } else { if(query(i,y1,i,y2)<k) { ans-=query(i,y1,i,y2); if(k<=ans&&ans<=2*k) { printf("%lld %lld %lld %lld\n",x1,y1,i-1,y2); break; } } else { ans=query(i,y1,i,y2); for(j=y2;j>=y1;j--) { ans-=a[i][j]; if(k<=ans&&ans<=2*k) { printf("%lld %lld %lld %lld\n",i,y1,i,j-1); flag=1; break; } } if(flag==1) { break; } } } } } } } fclose(stdin); fclose(stdout); return 0; }
\(T3\) T5422. 数组 \(100pts\)
-
正解
- 如果没有修改操作的话就是 BZOJ4026 dC Loves Number Theory 了,但本题的做法无法硬套进那题,因为与值域有关。
- 有欧拉函数计算式为 \(\varphi(n)=n \prod\limits_{p|n,p \in \mathbb{P}} \frac{p-1}{p}\) 。
- 观察到 \(a_{i},x \le 300\) ,那么最后的乘积所包含的质因子很少(最多有 \(62\) 个)。线段树维护最后的乘积及所包含的质因子即可,前后者都可以通过打懒标记来做。
- 略带卡常。
点击查看代码
const ll p=1000000007; ll a[100010],prime[70]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293},c[70]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } struct SMT { struct SegmentTree { ll l,r,mul,lazy; bitset<70>vis_mul,vis_lazy; }tree[400010],ls; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void pushup(ll rt) { tree[rt].mul=tree[lson(rt)].mul*tree[rson(rt)].mul%p; tree[rt].vis_mul=tree[lson(rt)].vis_mul|tree[rson(rt)].vis_mul; } void build(ll rt,ll l,ll r) { tree[rt].l=l; tree[rt].r=r; tree[rt].lazy=1; if(l==r) { tree[rt].mul=a[l]; for(ll i=1;i<=62&&prime[i]<=a[l];i++) { tree[rt].vis_mul[i]=(a[l]%prime[i]==0); } return; } ll mid=(tree[rt].l+tree[rt].r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void pushdown(ll rt) { if(tree[rt].lazy!=1) { tree[lson(rt)].lazy=tree[lson(rt)].lazy*tree[rt].lazy%p; tree[lson(rt)].vis_lazy|=tree[rt].vis_lazy; tree[lson(rt)].vis_mul|=tree[rt].vis_lazy; tree[lson(rt)].mul=(tree[lson(rt)].mul*qpow(tree[rt].lazy,tree[lson(rt)].r-tree[lson(rt)].l+1,p))%p; tree[rson(rt)].lazy=tree[rson(rt)].lazy*tree[rt].lazy%p; tree[rson(rt)].vis_lazy|=tree[rt].vis_lazy; tree[rson(rt)].vis_mul|=tree[rt].vis_lazy; tree[rson(rt)].mul=(tree[rson(rt)].mul*qpow(tree[rt].lazy,tree[rson(rt)].r-tree[rson(rt)].l+1,p))%p; tree[rt].lazy=1; tree[rt].vis_lazy.reset(); } } void update(ll rt,ll x,ll y,ll val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].lazy=tree[rt].lazy*val%p; for(ll i=1;i<=62&&prime[i]<=val;i++) { tree[rt].vis_mul[i]=tree[rt].vis_mul[i]|(val%prime[i]==0); tree[rt].vis_lazy[i]=tree[rt].vis_lazy[i]|(val%prime[i]==0); } tree[rt].mul=(tree[rt].mul*qpow(val,tree[rt].r-tree[rt].l+1,p))%p; return; } pushdown(rt); ll mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,val); } if(y>mid) { update(rson(rt),x,y,val); } pushup(rt); } SegmentTree query(ll rt,ll x,ll y) { if(x<=tree[rt].l&&tree[rt].r<=y) { return tree[rt]; } pushdown(rt); ll mid=(tree[rt].l+tree[rt].r)/2; SegmentTree ans,num; ans.mul=1; ans.vis_mul.reset(); if(x<=mid) { num=query(lson(rt),x,y); ans.mul=ans.mul*num.mul%p; ans.vis_mul|=num.vis_mul; } if(y>mid) { num=query(rson(rt),x,y); ans.mul=ans.mul*num.mul%p; ans.vis_mul|=num.vis_mul; } return ans; } }T; int main() { freopen("array.in","r",stdin); freopen("array.out","w",stdout); ll n,q,pd,l,r,x,i,j; scanf("%lld%lld",&n,&q); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); } for(i=1;i<=62;i++) { c[i]=(prime[i]-1)*qpow(prime[i],p-2,p)%p; } T.build(1,1,n); for(i=1;i<=q;i++) { scanf("%lld%lld%lld",&pd,&l,&r); if(pd==1) { scanf("%lld",&x); T.update(1,l,r,x); } else { T.ls=T.query(1,l,r); for(j=1;j<=62;j++) { if(T.ls.vis_mul[j]==1) { T.ls.mul=(T.ls.mul*c[j])%p; } } printf("%lld\n",T.ls.mul); } } fclose(stdin); fclose(stdout); return 0; }
\(T4\) T5423. 树 \(0pts\)
-
考虑根号分治。
-
当步长大于 \(\sqrt{n}\) 时步数小于 \(\sqrt{n}\) 可以暴力跳;否则对于每个步长预处理到根节点的贡献,接着类似求树上两点距离差分维护即可。跳步长时需要求 \(k\) 级祖先,详见 2024初三年前集训测试2 T4 金牌 。
-
特判跳到 \(LCA\) 时多产生贡献。
-
略带卡常。
点击查看代码
#define LOCAL namespace IO{ #ifdef LOCAL FILE*Fin(fopen("tree.in","r")),*Fout(fopen("tree.out","w")); #else FILE*Fin(stdin),*Fout(stdout); #endif class qistream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qistream(FILE*_fp=stdin):fp(_fp),p(0){fread(buf+p,1,SIZE-p,fp);}void flush(){memmove(buf,buf+p,SIZE-p),fread(buf+SIZE-p,1,p,fp),p=0;}qistream&operator>>(char&x){x=getch();while(isspace(x))x=getch();return*this;}template<class T>qistream&operator>>(T&x){x=0;p+BLOCK>=SIZE?flush():void();bool flag=false;for(;!isdigit(buf[p]);++p)flag=buf[p]=='-';for(;isdigit(buf[p]);++p)x=x*10+buf[p]-'0';x=flag?-x:x;return*this;}char getch(){p+BLOCK>=SIZE?flush():void();return buf[p++];}qistream&operator>>(char*str){char ch=getch();while(ch<=' ')ch=getch();int i=0;for(;ch>' ';++i,ch=getch())str[i]=ch;str[i]='\0';return*this;}}qcin(Fin); class qostream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qostream(FILE*_fp=stdout):fp(_fp),p(0){}~qostream(){fwrite(buf,1,p,fp);}void flush(){fwrite(buf,1,p,fp),p=0;}template<class T>qostream&operator<<(T x){int len=0;p+BLOCK>=SIZE?flush():void();x<0?(x=-x,buf[p++]='-'):0;do buf[p+len]=x%10+'0',x/=10,++len;while(x);for(int i=0,j=len-1;i<j;++i,--j)std::swap(buf[p+i],buf[p+j]);p+=len;return*this;}qostream&operator<<(char x){putch(x);return*this;}void putch(char ch){p+BLOCK>=SIZE?flush():void();buf[p++]=ch;}qostream&operator<<(char*str){for(int i=0;str[i];++i)putch(str[i]);return*this;}qostream&operator<<(const char*s){for(int i=0;s[i];++i)putch(s[i]);return*this;}}qcout(Fout); } #define cin IO::qcin #define cout IO::qcout struct node { int nxt,to; }e[100010]; int head[100010],siz[100010],fa[100010][20],dep[100010],son[100010],top[100010],a[100010],b[100010],c[100010],dis[100010][330],cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(int x,int father) { siz[x]=1; fa[x][0]=father; dep[x]=dep[father]+1; for(int i=1;(1<<i)<=dep[x];i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int father,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],x,id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father&&e[i].to!=son[x]) { dfs2(e[i].to,x,e[i].to); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]][0]; } else { v=fa[top[v]][0]; } } return (dep[u]<dep[v])?u:v; } int kth_fa(int x,int k) { int rt=x; for(int i=(int)log2(k);i>=0;i--) { if(dep[x]-dep[fa[rt][i]]<=k) { rt=fa[rt][i]; } } return rt; } void dfs3(int x,int father,int c) { dis[x][c]=(dep[x]<=c)?a[x]:a[x]+dis[kth_fa(x,c)][c]; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs3(e[i].to,x,c); } } } int main() { int n,s,u,v,rt,ans,r1,r2,i; cin>>n; s=sqrt(n)+1; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs1(1,0); dfs2(1,0,1); for(i=1;i<=n;i++) { cin>>b[i]; } for(i=1;i<=n-1;i++) { cin>>c[i]; if(c[i]<=s) { if(dis[1][c[i]]==0) { dfs3(1,0,c[i]); } } } for(i=1;i<=n-1;i++) { u=b[i]; v=b[i+1]; rt=lca(u,v); ans=0; if(c[i]<=s) { r1=(dep[u]-dep[rt])%c[i]; r2=(dep[v]-dep[rt])%c[i]; ans=dis[u][c[i]]+dis[v][c[i]]+((r1==0)?-2*dis[rt][c[i]]+a[rt]:-dis[kth_fa(rt,c[i]-r1)][c[i]]-dis[kth_fa(rt,c[i]-r2)][c[i]]); } else { while(dep[u]>=dep[rt]) { ans+=a[u]; u=kth_fa(u,c[i]); } while(dep[v]>dep[rt]) { ans+=a[v]; v=kth_fa(v,c[i]); } } cout<<ans<<endl; } return 0; }
总结
- 题面是 \(PDF\) 的形式,用插件能在 \(vscode\) 里打开。
- \(T1\) 第一眼觉得是 \(Kruskal\) 重构树,然后发现还有点权的限制。
- \(T2\) 口胡了一个二分套二分的做法,和暴力拍了几百组手动检验正确遂直接交了,赛后发现写假了,挂了 \(15pts\) 。
- \(T3\) 写线段树
pushdown
的时候传区间长度传成父亲的区间长度了,幸好小样例有区间修改需要pushdown
的测试点,马上就改出来了。 - \(T4\) 读假题了,以为只能走一步,走不到就停下了。
后记
-
昨晚上就把题面和样例发下来了(下发文件在比赛开始前就可以下载)。
-
\(PDF\) 上比赛编号是 ZZ Round 1,原 \(4.5h\) 的比赛到我们这就成 \(4h\) 了。
-
赛时关网了,可能是因为还有新高二的跟着打?
-
组题人的提示比较典。
-
\(T2\) 赛时没有
Special Judge
, 赛后的第一份Special Judge
还出锅了,下午才正式重测。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18298689,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。