暑假集训CSP提高模拟11
暑假集训CSP提高模拟11
组题人: @KafuuChinocpp
\(T1\) P152. Fate \(24pts\)
-
强化版: HDU1688 Sightseeing | luogu P2865 [USACO06NOV] Roadblocks G
-
设 \(dis_{i,0/1}\) 表示从 \(s\) 到 \(i\) 的最短路/次短路长度, \(f_{i,0/1}\) 表示从 \(s\) 到 \(i\) 的最短路/次短路条数。
-
\(dijkstra\) 过程中按照路径长度与最短路、次短路的大小关系四种情况进行反讨。
-
由于需要统计最短路及次短路,
vis
数组同样需要记录当前是最短路还是次短路去更新状态 -
最后判断一下次短路长度是否等于最短路长度加一。
点击查看代码
const ll p=1000000007; struct node { ll nxt,to,w; }e[400010]; struct quality { ll dis,x,id; bool operator < (const quality another) const { return dis>another.dis; } }; ll head[400010],vis[400010][2],dis[400010][2],f[400010][2],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dijkstra(ll s) { ll x,id,i; priority_queue<quality>q; memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[s][0]=0; f[s][0]=1; q.push((quality){dis[s][0],s,0}); while(q.empty()==0) { x=q.top().x; id=q.top().id; q.pop(); if(vis[x][id]==0) { vis[x][id]=1; for(i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to][0]>dis[x][id]+e[i].w) { dis[e[i].to][1]=dis[e[i].to][0]; f[e[i].to][1]=f[e[i].to][0]; dis[e[i].to][0]=dis[x][id]+e[i].w; f[e[i].to][0]=f[x][id]; q.push((quality){dis[e[i].to][0],e[i].to,0}); q.push((quality){dis[e[i].to][1],e[i].to,1}); } else { if(dis[e[i].to][0]==dis[x][id]+e[i].w) { f[e[i].to][0]=(f[e[i].to][0]+f[x][id])%p; } else { if(dis[e[i].to][1]>dis[x][id]+e[i].w) { dis[e[i].to][1]=dis[x][id]+e[i].w; f[e[i].to][1]=f[x][id]; q.push((quality){dis[e[i].to][1],e[i].to,1}); } else { if(dis[e[i].to][1]==dis[x][id]+e[i].w) { f[e[i].to][1]=(f[e[i].to][1]+f[x][id])%p; } } } } } } } } int main() { ll n,m,s,t,u,v,i; cin>>n>>m>>s>>t; for(i=1;i<=m;i++) { cin>>u>>v; add(u,v,1); add(v,u,1); } dijkstra(s); cout<<(dis[t][1]-1==dis[t][0])*f[t][1]<<endl; return 0; }
\(T2\) P153. EVA \(5pts\)
-
从贪心的角度分析,网的一端和至少一条鱼重合一定不劣。
-
考虑枚举与左端点重合的鱼,然后固定这条鱼,令其他鱼与其做相对运动,处理出进入网的左右 \(t\) 边界,差分后,由于左端点已经固定,所以直接继承即可。
-
特判速度相等的情况。
-
略卡精度。
点击查看代码
const double eps=1e-10; int w[2010],x[2010],v[2010]; map<double,int>d; map<double,int>::iterator it; int main() { int n,a,ans=0,sum,i,j; double l,r; cin>>n>>a; for(i=1;i<=n;i++) { cin>>w[i]>>x[i]>>v[i]; } for(i=1;i<=n;i++) { d.clear(); sum=w[i]; for(j=1;j<=n;j++) { if(i!=j) { if(v[i]==v[j]) { if(x[i]<=x[j]&&x[j]<=x[i]+a) { sum+=w[j]; } } else { l=1.0*(x[i]-x[j])/(v[j]-v[i]); r=1.0*(x[i]+a-x[j])/(v[j]-v[i]); if(l-r>eps)//左右端点颠倒了要颠倒回来 { swap(l,r); } if(r>=0)//保证存在区间 { l=max(l,0.0); d[l]+=w[j]; d[r+eps]-=w[j]; } } } } ans=max(ans,sum); for(it=d.begin();it!=d.end();it++) { sum+=it->second; ans=max(ans,sum); } } cout<<ans<<endl; return 0; }
\(T3\) P166. 嘉然登场 \(5pts\)
-
原题: [ARC148E] ≥ K
-
将 \(\{ a \}\) 排序后分成 \(< \frac{k}{2}\) 和 \(\ge \frac{k}{2}\) 两部分,然后进行分讨。
-
当 \(x,y<\frac{n}{2}\) 时, \(x,y\) 不能相邻。
-
当 \(x<\frac{k}{2} \le y\) 时,若 \(|x-\frac{k}{2}| \le |y-\frac{k}{2}|\) 则可以相邻,否则不能相邻。
-
当 \(x,y \ge \frac{k}{2}\) 时, \(x,y\) 可以相邻。
-
将 \(|a_{i}-\frac{k}{2}|\) 进行排序,然后降序插入。
-
将 \(< \frac{k}{2}\) 的数看作 \(0\) ,否则看作 \(1\) 。插入的过程中要求插入的数必须两边都和 \(1\) 相邻,即只能插到 \(1?1\) 中来(没有位置的话可以拆开硬插)。
-
设当前可以插入的位置共有 \(s\) 个(初始时 \(s=1\) )。当目前要插入 \(t\) 个 \(0\) 时,由于一个位置仅可以插一个数,对答案产生的贡献为 \(\dbinom{s}{t}\) ,同时可以插入的位置减少 \(t\) 个;当目前要插入 \(t\) 个 \(1\) 时,由于一个位置可以插多个数,对答案产生的贡献为 \(\dbinom{s+t-1}{t}\) ,同时可以插入的位置增加 \(t\) 个。
点击查看代码
const ll p=998244353; ll jc[200010],inv[200010],jc_inv[200010],a[200010],k; map<ll,ll>cnt; bool cmp(ll a,ll b) { return (abs(2*a-k)==abs(2*b-k))?(a>b):(abs(2*a-k)>abs(2*b-k)); } ll C(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p:0; } int main() { ll n,m,ans=1,sum=1,i; cin>>n>>k; inv[1]=1; jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1; for(i=2;i<=n;i++) { inv[i]=(p-p/i)*inv[p%i]%p; jc[i]=jc[i-1]*i%p; jc_inv[i]=jc_inv[i-1]*inv[i]%p; } for(i=1;i<=n;i++) { cin>>a[i]; cnt[a[i]]++; } sort(a+1,a+1+n); m=unique(a+1,a+1+n)-(a+1); sort(a+1,a+1+m,cmp); for(i=1;i<=m;i++) { if(2*a[i]<k) { ans=ans*C(sum,cnt[a[i]],p)%p; sum-=cnt[a[i]]; } else { ans=ans*C(sum+cnt[a[i]]-1,cnt[a[i]],p)%p; sum+=cnt[a[i]]; } } cout<<ans<<endl; return 0; }
\(T4\) P178. Clannad \(0pts\)
-
大力结论
- 将 \(a_{l \sim r}\) 按 \(DFS\) 序排序后,最小连通块(虚树)边数为 \(\dfrac{dis_{a_{l},a_{l+1}}+dis_{a_{l+1},a_{l+2}}+ \dots +dis_{a_{r-1},a_{r}}+dis_{a_{r},a_{l}}}{2}\) ,点数等于边数加一。
-
普通莫队套
set
维护 \(DFS\) 序的前驱后继的话时间复杂度为 \(O(n\sqrt{n} \log n)\) ,瓶颈在set
的动态插入和动态删除。- 做法同 luogu P10930 异象石 。
-
考虑优化插入和删除过程,我们采用只减不加回滚莫队,右端点设为 \(m\) ,这样的话链表就可以支持 \(O(1)\) 删除了。
-
求 \(LCA\) 用树剖的话时间复杂度为 \(O(n \sqrt{n} \log n)\) ,加个超级快读在 luogu 贴着线过,但在学校 \(OJ\) 上过不了;使用 \(DFS\) 序求 \(LCA\) ,略带卡常,需要交换 \(ST\) 表两维下标和
__lg
。点击查看代码
namespace IO{ #ifdef LOCAL FILE*Fin(fopen("test.in","r")),*Fout(fopen("test.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 quality { int pre,nxt,pos; }x; vector<quality>s; struct node { int nxt,to; }e[200010]; int head[200010],dep[200010],rk[200010],dfn[200010],a[200010],pos[200010],L[200010],R[200010],ans[200010],num[200010],pre[200010],nxt[2000010],tot=0,cnt=0,klen,ksum; struct ask { int l,r,id; }q[200010]; bool q_cmp(ask a,ask b) { return (pos[a.l]==pos[b.l])?(a.r>b.r):(a.l<b.l); } void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } int sx_min(int x,int y) { return dfn[x]<dfn[y]?x:y; } struct ST { int fminn[25][200010]; void init(int n) { for(int j=1;j<=__lg(n);j++) { for(int i=1;i+(1<<j)-1<=n;i++) { fminn[j][i]=sx_min(fminn[j-1][i],fminn[j-1][i+(1<<(j-1))]); } } } int query(int l,int r) { int t=__lg(r-l+1); return sx_min(fminn[t][l],fminn[t][r-(1<<t)+1]); } }T; void dfs(int x,int fa) { tot++; dfn[x]=tot; T.fminn[0][tot]=fa; rk[tot]=x; dep[x]=dep[fa]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); } } } int lca(int u,int v) { if(dfn[u]>dfn[v]) { swap(u,v); } return (dfn[u]==dfn[v])?u:T.query(dfn[u]+1,dfn[v]); } int dis(int x,int y) { x=rk[x]; y=rk[y]; return dep[x]+dep[y]-2*dep[lca(x,y)]; } void init(int n,int m) { klen=n/sqrt(m)+1; ksum=n/klen; for(int i=1;i<=ksum;i++) { L[i]=R[i-1]+1; R[i]=R[i-1]+klen; } if(R[ksum]<n) { ksum++; L[ksum]=R[ksum-1]+1; R[ksum]=n; } for(int i=1;i<=ksum;i++) { for(int j=L[i];j<=R[i];j++) { pos[j]=i; } } } void del1(int x,int &sum) { num[x]--; if(num[x]==0) { sum-=dis(pre[x],x); sum-=dis(x,nxt[x]); sum+=dis(pre[x],nxt[x]); pre[nxt[x]]=pre[x]; nxt[pre[x]]=nxt[x]; } } void del2(int x,int &sum) { num[x]--; s.push_back((quality){pre[x],nxt[x],x});//后面要撤销这里的影响 if(num[x]==0) { sum-=dis(pre[x],x); sum-=dis(x,nxt[x]); sum+=dis(pre[x],nxt[x]); pre[nxt[x]]=pre[x]; nxt[pre[x]]=nxt[x]; } } int main() { int n,m,k,u,v,sum=0,tmp,l=1,r,last,i,j; cin>>n>>m>>k; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); T.init(n); init(m,k); for(i=1;i<=m;i++) { cin>>a[i]; } for(i=1;i<=k;i++) { cin>>q[i].l>>q[i].r; q[i].id=i; } sort(q+1,q+1+k,q_cmp); for(i=1;i<=k;i++) { if(pos[q[i].l]!=pos[q[i-1].l]) { memset(num,0,sizeof(num)); for(j=L[pos[q[i].l]];j<=m;j++) { num[dfn[a[j]]]++; } last=0; for(j=1;j<=n;j++) { pre[j]=last; last=(num[j]==0)?last:j; } for(j=1;j<=n;j++) { pre[j]=(pre[j]==0)?last:pre[j];//因为只有首尾的 num 不等于 0 ,所以全赋值也没什么大碍 } last=0; for(j=n;j>=1;j--) { nxt[j]=last; last=(num[j]==0)?last:j; } for(j=n;j>=1;j--) { nxt[j]=(nxt[j]==0)?last:nxt[j]; } sum=0; for(j=1;j<=n;j++) { sum+=(num[j]!=0)*dis(pre[j],j); } r=m; } l=L[pos[q[i].l]]; while(r>q[i].r) { del1(dfn[a[r]],sum); r--; } tmp=sum; while(l<q[i].l) { del2(dfn[a[l]],sum); l++; } ans[q[i].id]=sum/2+1; sum=tmp; while(s.empty()==0)//撤销回滚的影响 { x=s.back(); s.pop_back(); if(num[x.pos]==0) { nxt[x.pre]=x.pos; pre[x.nxt]=x.pos; } num[x.pos]++; } } for(i=1;i<=k;i++) { cout<<ans[i]<<endl; } return 0; }
-
树剖加珂朵莉树的做法会好写点,同 luogu P8512 [Ynoi Easy Round 2021] TEST_152 扫描线维护时间戳对答案的贡献。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],fa[200010],siz[200010],dep[200010],son[200010],top[200010],dfn[200010],ans[200010],a[200010],tot=0,cnt=0; vector<pair<int,int> >q[200010]; 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]=father; dep[x]=dep[father]+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 id) { top[x]=id; tot++; dfn[x]=tot; if(son[x]!=0) { dfs2(son[x],id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } struct BIT { int c[200010]; int lowbit(int x) { return (x&(-x)); } void add(int n,int x,int val) { if(x==0) { return; } for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; struct ODT { struct node { int l,r; mutable int col; bool operator < (const node &another) const { return l<another.l; } }; set<node>s; void init(int n) { s.insert((node){1,n,0}); } set<node>::iterator split(int pos) { set<node>::iterator it=s.lower_bound((node){pos,0,0}); if(it!=s.end()&&it->l==pos) { return it; } it--; if(it->r<pos) { return s.end(); } int l=it->l,r=it->r,col=it->col; s.erase(it); s.insert((node){l,pos-1,col}); return s.insert((node){pos,r,col}).first; } void assign(int l,int r,int col,int n) { set<node>::iterator itr=split(r+1),itl=split(l); for(set<node>::iterator it=itl;it!=itr;it++) { T.add(n,it->col,-(it->r-it->l+1)); } T.add(n,col,r-l+1); s.erase(itl,itr); s.insert((node){l,r,col}); } }O; void update(int u,int v,int col,int n) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { O.assign(dfn[top[u]],dfn[u],col,n); u=fa[top[u]]; } else { O.assign(dfn[top[v]],dfn[v],col,n); v=fa[top[v]]; } } if(dep[u]<dep[v]) { O.assign(dfn[u],dfn[v],col,n); } else { O.assign(dfn[v],dfn[u],col,n); } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,k,u,v,i,j; cin>>n>>m>>k; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } for(i=1;i<=m;i++) { cin>>a[i]; } for(i=1;i<=k;i++) { cin>>u>>v; if(u==v) { ans[i]=1; } else { q[v].push_back(make_pair(u,i)); } } dfs1(1,0); dfs2(1,1); O.init(n); for(i=2;i<=m;i++) { update(a[i-1],a[i],i,m); for(j=0;j<q[i].size();j++) { ans[q[i][j].second]=T.getsum(i)-T.getsum(q[i][j].first); } } for(i=1;i<=k;i++) { cout<<ans[i]<<endl; } return 0; }
总结
- \(T1\)
- 没想到在 \(dijkstra\) 过程中同样也能更新次短路,说明对 \(dijkstra\) 节点标记的认识不清楚。
- 在最短路 \(DAG\) 上统计答案上仅考虑到了最短路径上的点,没考虑到非最短路径上的点也能转移过来。
- \(T2\)
- 被所选择的 \(x,t\) 可以不为整数给吓到了,没敢再想贪心。
后记
-
奖励的限制越来越苛刻了。
-
题目背景夹带私货。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18330814,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。