暑假集训CSP提高模拟21
暑假集训CSP提高模拟21
组题人: @Muel_imj
\(T1\) P241. 黎明与萤火 \(10pts\)
-
部分分
- \(10pts\) :生成 \(1 \sim n\) 的全排列然后依次判断。
- \(20pts\) :输出
NO
。
-
正解
- 叶子节点的度数为 \(1\) ,不能直接删除,只能先删除父亲节点后再单点删除。若父亲节点度数是偶数可以直接删,否则接着向上找到一个度数为偶数的祖先节点删掉后一连串地删下来。
- 接着再从上到下扫一遍,看有没有被漏下的度数为奇数的点,若有说明无解,否则删掉这个点。
- 本质上是一个钦定最优解然后进行 \(check\) 的过程。
点击查看代码
int du[200010],vis[200010]; vector<int>e[200010],ans; void del(int x) { vis[x]=1; ans.push_back(x); for(int i=0;i<e[x].size();i++) { du[e[x][i]]--; } } void dfs1(int x,int fa) { for(int i=0;i<e[x].size();i++) { if(e[x][i]!=fa) { dfs1(e[x][i],x); } } if(du[x]%2==0) { del(x); } } void dfs2(int x,int fa) { if(vis[x]==0) { if(du[x]%2==0) { del(x); } else { cout<<"NO"<<endl; exit(0); } } for(int i=0;i<e[x].size();i++) { if(e[x][i]!=fa) { dfs2(e[x][i],x); } } } int main() { int n,u,v,rt=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>u; v=i; if(u==0) { rt=i; } else { du[u]++; du[v]++; e[u].push_back(v); e[v].push_back(u); } } dfs1(rt,0); dfs2(rt,0); cout<<"YES"<<endl; for(i=0;i<ans.size();i++) { cout<<ans[i]<<endl; } return 0; }
\(T2\) P242. Darling Dance \(100pts\)
-
建出最短路 \(DAG\) 后在 \(DAG\) 上的边就是候选集合的边。接着进行大力分讨。
- 若边的数量 \(\le k\) ,直接输出 \(DAG\) 上的边即可。
- 若边的数量 \(>k\) ,不断找度数最大的点删边,直至成为一棵树,若还 \(>k\) 遍历一遍整棵树取 \(k\) 条边即可。
点击查看代码
struct node { ll nxt,to,w,id; }e[600010]; ll head[600010],dis[600010],vis[600010],din[600010],cut[600010],e_cnt=0,E_cnt=0,cnt=0; vector<pair<ll,ll> >E[600010],fE[600010]; void add(ll u,ll v,ll w,ll id) { e_cnt++; e[e_cnt].nxt=head[u]; e[e_cnt].to=v; e[e_cnt].w=w; e[e_cnt].id=id; head[u]=e_cnt; } void dijstra(ll s) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pair<ll,ll> >q; dis[s]=0; q.push(make_pair(-dis[s],-s));; while(q.empty()==0) { ll x=-q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; q.push(make_pair(-dis[e[i].to],-e[i].to)); } } } } } void build(ll n) { for(ll x=1;x<=n;x++) { for(ll i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]==dis[x]+e[i].w) { E_cnt++; E[x].push_back(make_pair(e[i].to,e[i].id)); fE[e[i].to].push_back(make_pair(x,e[i].id)); din[e[i].to]++; } } } } void top_sort(ll n) { queue<ll>q; for(ll i=1;i<=n;i++) { if(din[i]==0) { q.push(i); } } while(q.empty()==0) { ll x=q.front(); q.pop(); for(ll i=0;i<E[x].size();i++) { if(cut[E[x][i].second]==0) { cout<<E[x][i].second<<" "; din[E[x][i].first]--; if(din[E[x][i].first]==0) { q.push(E[x][i].first); } } } } } void dfs(ll x,ll k) { for(ll i=0;i<E[x].size();i++) { if(cut[E[x][i].second]==0&&cnt<k) { cnt++; cout<<E[x][i].second<<" "; dfs(E[x][i].first,k); } } } struct quality { ll pos,val; }a[600010]; bool cmp(quality a,quality b) { return a.val>b.val; } int main() { ll n,m,k,u,v,w,flag=0,i,j; cin>>n>>m>>k; for(i=1;i<=m;i++) { cin>>u>>v>>w; add(u,v,w,i); add(v,u,w,i); } dijstra(1); build(n); for(i=1;i<=n;i++) { a[i].pos=i; a[i].val=din[i]; } sort(a+1,a+1+n,cmp); for(i=1;i<=n;i++) { for(j=1;j<fE[a[i].pos].size();j++) { if(E_cnt<=k||E_cnt==n-1) { flag=1; break; } else { E_cnt--; din[a[i].pos]--; cut[fE[a[i].pos][j].second]=1; } } if(flag==1) { break; } } if(E_cnt>k) { cout<<k<<endl; dfs(1,k); } else { cout<<E_cnt<<endl; top_sort(n); } return 0; }
-
或者只建最短路树,然后遍历一遍整棵树取 \(\min(k,n-1)\) 条边即可。
- 具体地,在 \(Dijkstra\) 的松弛过程中每个节点记录一个 \(pre\) ,表示是由其转移而来的,多次松弛取最后一次。顺序连起来后就得到了最短路树。
\(T3\) P243. Non-breath oblige \(0pts\)
-
部分分
- \(10pts\) :暴力枚举操作,时间复杂度为 \(O(mq \log n)\) 。
- \(20pts\) :没有操作 \(2\) ,则序列始终都是 \(0\) ,输出 \(0\) 即可。
-
正解
- 一个数只有最后一次的操作(交换或赋值)会对答案产生影响。考虑记录最后一次操作 \(2\) 的时间戳 \(\{ t \}\) ,操作 \(1\) 本质上就是 \(x,y\) 的时间戳交换了一下,操作 \(2\) 仍是区间赋值,操作 \(3\) 记录下时间戳。
- 操作 \(3\) 多一维表示下标,即 \((i,t_{x_{i}})\) 。此时等价于查询 \(i \in [l,r],t_{x_{i}} \in [l,r]\) 的点的贡献,二维数点维护。
- 因为加点是单调的,不会存在一个点 \((i,t_{x_{i}})\) 满足 \(i<l\) 但 \(t_{x_{i}}>l\) ,所以可以将询问离线下来,枚举右端点后扫描线加树状数组维护,从而省去原差分过程,同 luogu P10814 【模板】离线二维数点 。
- 略带卡常,加个超级快读就行了。学校 \(OJ\) 跑得慢,所以加了火车头,最大点跑了 \(1247ms/1250ms\) 。
点击查看代码
#pragma GCC optimize(3) #pragma GCC target("avx") #pragma GCC optimize("Ofast") #pragma GCC optimize("inline") #pragma GCC optimize("-fgcse") #pragma GCC optimize("-fgcse-lm") #pragma GCC optimize("-fipa-sra") #pragma GCC optimize("-ftree-pre") #pragma GCC optimize("-ftree-vrp") #pragma GCC optimize("-fpeephole2") #pragma GCC optimize("-ffast-math") #pragma GCC optimize("-fsched-spec") #pragma GCC optimize("unroll-loops") #pragma GCC optimize("-falign-jumps") #pragma GCC optimize("-falign-loops") #pragma GCC optimize("-falign-labels") #pragma GCC optimize("-fdevirtualize") #pragma GCC optimize("-fcaller-saves") #pragma GCC optimize("-fcrossjumping") #pragma GCC optimize("-fthread-jumps") #pragma GCC optimize("-funroll-loops") #pragma GCC optimize("-fwhole-program") #pragma GCC optimize("-freorder-blocks") #pragma GCC optimize("-fschedule-insns") #pragma GCC optimize("inline-functions") #pragma GCC optimize("-ftree-tail-merge") #pragma GCC optimize("-fschedule-insns2") #pragma GCC optimize("-fstrict-aliasing") #pragma GCC optimize("-fstrict-overflow") #pragma GCC optimize("-falign-functions") #pragma GCC optimize("-fcse-skip-blocks") #pragma GCC optimize("-fcse-follow-jumps") #pragma GCC optimize("-fsched-interblock") #pragma GCC optimize("-fpartial-inlining") #pragma GCC optimize("no-stack-protector") #pragma GCC optimize("-freorder-functions") #pragma GCC optimize("-findirect-inlining") #pragma GCC optimize("-fhoist-adjacent-loads") #pragma GCC optimize("-frerun-cse-after-loop") #pragma GCC optimize("inline-small-functions") #pragma GCC optimize("-finline-small-functions") #pragma GCC optimize("-ftree-switch-conversion") #pragma GCC optimize("-foptimize-sibling-calls") #pragma GCC optimize("-fexpensive-optimizations") #pragma GCC optimize("-funsafe-loop-optimizations") #pragma GCC optimize("inline-functions-called-once") #pragma GCC optimize("-fdelete-null-pointer-checks") 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 int pd[1000010],val[1000010],tx[1000010]; ll ans[1000010]; vector<pair<int,int> >e[1000010]; struct SMT { struct SegmentTree { int l,r,sum,lazy; }tree[4000010]; int lson(int x) { return x<<1; } int rson(int x) { return x<<1|1; } void build(int rt,int l,int r) { tree[rt].l=l; tree[rt].r=r; tree[rt].lazy=-1; tree[rt].sum=0; if(l==r) { return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void pushdown(int rt) { if(tree[rt].lazy!=-1) { tree[lson(rt)].lazy=tree[rson(rt)].lazy=tree[rt].lazy; tree[lson(rt)].sum=tree[rson(rt)].sum=tree[rt].lazy; tree[rt].lazy=-1; } } void update(int rt,int x,int y,int val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].lazy=tree[rt].sum=val; return; } pushdown(rt); int 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); } } int query(int rt,int pos) { if(tree[rt].l==tree[rt].r) { return tree[rt].sum; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; if(pos<=mid) { return query(lson(rt),pos); } else { return query(rson(rt),pos); } } }S; struct BIT { ll c[1000010]; int lowbit(int x) { return (x&(-x)); } void add(int n,int x,ll val) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll getsum(int x) { ll ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }B; int main() { int n,m,q,l,r,x,y,i,j; cin>>n>>m>>q; S.build(1,1,n); for(i=1;i<=m;i++) { cin>>pd[i]; if(pd[i]==1) { cin>>l>>r; x=S.query(1,l); y=S.query(1,r); S.update(1,l,l,y); S.update(1,r,r,x); } if(pd[i]==2) { cin>>l>>r>>val[i]; S.update(1,l,r,i); } if(pd[i]==3) { cin>>l; tx[i]=S.query(1,l); } } for(i=1;i<=q;i++) { cin>>l>>r; e[r].push_back(make_pair(l,i)); } for(i=1;i<=m;i++) { if(pd[i]==3) { if(tx[i]!=0) { B.add(m,tx[i],val[tx[i]]); } } for(j=0;j<e[i].size();j++) { ans[e[i][j].second]=B.getsum(i)-B.getsum(e[i][j].first-1); } } for(i=1;i<=q;i++) { cout<<ans[i]<<endl; } return 0; }
\(T4\) P244. 妄想感伤代偿联盟 \(0pts\)
-
部分分
-
\(10pts\) :输出 \(|s_{1}|\) 。
-
\(45pts\) :哈希优化字符串匹配或 \(KMP\) 维护,同 luogu P4824 [USACO15FEB] Censoring S ,加个记忆化。
点击查看代码
const ll mod=998244353,base=13331; ll jc[600010]; vector<ll>hs[600010]; string s[600010]; map<ll,ll>vis[600010]; void sx_hash(string s,vector<ll>&hs) { hs.push_back(0); for(ll i=0;i<s.size();i++) { hs.push_back((hs[i]*base%mod+s[i])%mod); } } ll ask_hash(ll l,ll r,vector<ll>hs) { return (hs[r]-hs[l-1]*jc[r-l+1]%mod+mod)%mod; } bool check(ll mid,ll x,ll y) { return ask_hash(s[x].size()-mid+1,s[x].size(),hs[x])==ask_hash(1,mid,hs[y]); } int main() { ll n,q,x,y,ans,i; scanf("%lld%lld",&n,&q); for(i=1;i<=n;i++) { cin>>s[i]; sx_hash(s[i],hs[i]); } for(i=0;i<=600000;i++) { jc[i]=(i==0)?1:jc[i-1]*base%mod; } for(i=1;i<=q;i++) { scanf("%lld%lld",&x,&y); if(x>n||y>n) { printf("0\n"); } else { if(vis[x].find(y)==vis[x].end()) { for(ans=min(s[x].size(),s[y].size());ans>=0;ans--) { if(check(ans,x,y)==true) { vis[x][y]=ans; break; } } } printf("%lld\n",vis[x][y]); } } return 0; }
-
-
正解
-
多模式串的前后缀匹配,考虑建 \(AC\) 自动机。
-
在 \(AC\) 自动机上找到 \(x\) 的位置后不断往上跳 \(fail\) 指针的过程中到达的地方就是它的后缀在模式串中可以匹配到的前缀。即在 \(fail\) 树上 \(x\) 到 \(1\) 的这条链和 \(trie\) 树上 \(y\) 到 \(1\) 的这条链的交集中的节点在 \(trie\) 树的深度(长度)的最大值即为最终答案。
-
将询问离线下来,并按 \(\{ x \}\) 排序,每次找到一个新的 \(x\) 就暴力往上跳 \(fail\) 指针,沿途经过的点对 \(trie\) 树上整颗子树内的节点打上标记,遍历再让 \(y\) 往上跳 \(trie\) 树。用 \(DFS\) 序拍成序列后线段树维护区间赋值取 \(\max\) 单点查询即可。
-
为减少取 \(\max\) 的常数,可以选择从上往下覆盖,使得后面赋的值覆盖先前位置的(后面赋值更优)。
点击查看代码
struct node { int x,y,id; }q[1000010]; int ans[1000010]; string s[600010]; bool cmp(node a,node b) { return a.x<b.x; } struct SMT { struct SegmentTree { int l,r,sum,lazy; }tree[2400010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void build(int rt,int l,int r) { tree[rt].l=l; tree[rt].r=r; tree[rt].lazy=-1; tree[rt].sum=0; if(l==r) { return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void pushdown(int rt) { if(tree[rt].lazy!=-1) { tree[lson(rt)].lazy=tree[rson(rt)].lazy=tree[rt].lazy; tree[lson(rt)].sum=tree[rson(rt)].sum=tree[rt].lazy; tree[rt].lazy=-1; } } void update(int rt,int x,int y,int val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].lazy=tree[rt].sum=val; return; } pushdown(rt); int 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); } } int query(int rt,int pos) { if(tree[rt].l==tree[rt].r) { return tree[rt].sum; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; if(pos<=mid) { return query(lson(rt),pos); } else { return query(rson(rt),pos); } } }T; struct ACM { int end[600010],fail[600010],dep[600010],dfn[600010],out[600010],rt_sum=0,tot=0; struct Trie { int ch[27]; }tree[600010]; int val(char x) { return x-'a'+1; } void insert(string s,int idx) { int x=0; for(int i=0;i<s.size();i++) { if(tree[x].ch[val(s[i])]==0) { rt_sum++; tree[x].ch[val(s[i])]=rt_sum; } x=tree[x].ch[val(s[i])]; } end[idx]=x; } void build() { queue<int>q; for(int i=1;i<=26;i++) { if(tree[0].ch[i]!=0) { fail[tree[0].ch[i]]=0; q.push(tree[0].ch[i]); } } while(q.empty()==0) { int x=q.front(); q.pop(); for(int i=1;i<=26;i++) { if(tree[x].ch[i]==0) { tree[x].ch[i]=tree[fail[x]].ch[i]; } else { fail[tree[x].ch[i]]=tree[fail[x]].ch[i]; q.push(tree[x].ch[i]); } } } } void dfs(int x,int depth) { tot++; dfn[x]=tot; dep[x]=depth; for(int i=1;i<=26;i++) { if(tree[x].ch[i]!=0) { dfs(tree[x].ch[i],depth+1); } } out[x]=tot; } void up(int x) { if(x==0) { return; } up(fail[x]);//先让上面的赋值 T.update(1,dfn[x],out[x],dep[x]); } void add(string s) { int x=0; for(int i=0;i<s.size();i++) { x=tree[x].ch[val(s[i])]; } up(x); } }A; int main() { ll n,m,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>s[i]; A.insert(s[i],i); } A.dfs(0,0); A.build(); T.build(1,1,A.tot); for(i=1;i<=m;i++) { cin>>q[i].x>>q[i].y; q[i].id=i; } sort(q+1,q+1+m,cmp); for(i=1;i<=m;i++) { if(q[i-1].x!=q[i].x) { T.update(1,1,A.tot,0); A.add(s[q[i].x]); } ans[q[i].id]=T.query(1,A.dfn[A.end[q[i].y]]); } for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } return 0; }
-
-
官方题解
- 有点纰漏,改一下。
- 因为答案没有单调性,所以算法一中的二分哈希没有正确性。
- 算法四中提到的暴力跳 \(fail\) 复杂度是正确的,且不超过 \(O(\sum\limits_{i=1}^{n}|s_{i}|)\) 。
- 算法六中的 并集 应改为 交集 。
- 有点纰漏,改一下。
总结
- 赛时历程:溜了一眼题后,发现 \(T3\) 可持久化数据结构能骗点分, \(T4\) 在没改题面之前二分哈希可过,先看了几眼样例发现样例和题面不符,接着 @Charlie_ljk 就去和学长说这个问题了。改完题面莫名决定反转后二分哈希也可过,过小样例后发现大样例没过,又写了个基于答案单调性的暴力和二分哈希拍,最后发现暴力也写假了,二分哈希自然就不对了,只把哈希的交了上去,只剩了 \(3h\) ; \(T3\) 拿完暴力分,在想是否有可差分性;写 \(T1,T2\) 时只剩下了 \(1h+40 \min\) , \(T1\) 仍没思路,被迫先写 \(T2\) ,想了想就把 \(T2\) 切了,想先把 \(T1\) 切了再用 \(checker\) 对拍,但到最后 \(T1\) 也没切。
- \(T3\) 因 Ctrl+A 全选时因一些奇怪错误把
粘成了if(tree[rt].lazy!=-1) { tree[lson(rt)].sum=tree[rson(rt)].sum=tree[rt].lazy; tree[lson(rt)].lazy=tree[rson(rt)].lazy=tree[rt].lazy; tree[rt].lazy=-1; }
但是没有爆 \(CE\) ,挂了 \(10pts\) 。if(tree[rt].lazy!=-1) tree[lson(rt)].sum=tree[rson(rt)].sum=tree[rt].lazy; { tree[lson(rt)].lazy=tree[rson(rt)].lazy=tree[rt].lazy; tree[rt].lazy=-1; }
- \(T4\) 以为 \(subtask 2,subtask 3\) 代码能过 \(subtask 1\) 就没写 \(subtask 1\) 的部分分,挂了 \(10pts\) ;忘写记忆化了,挂了 \(45pts\) 。
后记
-
\(T2\) 数据出锅了,有重边。
-
\(T4\) 数据出锅了,没写 \(x,y\) 的数据范围所以有 \(x,y>n\) 的数据;题面出锅了,有错别字。
-
出题人的评价。
-
夹带私货。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18360833,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。