暑假集训CSP提高模拟18
暑假集训CSP提高模拟18
组题人: @worldvanquisher | @joke3579
\(T1\) P227. Mortis \(0pts\)
-
原题: [ABC302G] Sort from 1 to 4 | luogu P1459 [USACO2.1] 三值的排序 Sorting a Three-Valued Sequence
-
部分分
- \(0pts\) :输出逆序对个数。
-
正解
- 设 \(\{ a \}\) 排序后的序列为 \(\{ b \}\) 。
- 然后大力分讨。
- 若 \(a_{i}=b_{i}\) 说明不需要进行交换。
- 否则,若存在 \(i \ne j\) 使得 \(a_{i}=b_{j},a_{j}=b_{i}\) 交换两者即可,对答案产生 \(1\) 的贡献。
- 再否则,若存在 \(i \ne j \ne k\) 使得 \(a_{i}=b_{j},a_{j}=b_{k},a_{k}=b_{i}\) ,顺序交换三者即可,对答案产生 \(2\) 的贡献。
- 再否则,剩下的数一定形如 \(a_{i}=b_{j},a_{j}=b_{k},a_{k}=b_{h},a_{h}=b_{i}(i \ne j \ne k \ne h)\) ,顺序交换四者即可,对答案产生 \(3\) 的贡献。
- 具体地,我们记录 \(c_{i,j}\) 表示 \(i\) 最终位置上 \(j\) 的个数。不对情况 \(1\) 进行统计,仅枚举情况 \(2,3\) ,剩下的就是情况 \(4\) 的贡献。
点击查看代码
int a[200010],b[200010],c[5][5]; int main() { int n,ans=0,sum=0,i,j,k; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; b[i]=a[i]; } sort(b+1,b+1+n); for(i=1;i<=n;i++) { if(b[i]!=a[i]) { sum++; c[b[i]][a[i]]++; } } for(i=1;i<=4;i++) { for(j=1;j<=4;j++) { if(i!=j) { while(c[i][j]>=1&&c[j][i]>=1) { ans++; sum-=2; c[i][j]--; c[j][i]--; } } } } for(i=1;i<=4;i++) { for(j=1;j<=4;j++) { for(k=1;k<=4;k++) { if(i!=j&&i!=k&&j!=k) { while(c[i][j]>=1&&c[j][k]>=1&&c[k][i]>=1) { ans+=2; sum-=3; c[i][j]--; c[j][k]--; c[k][i]--; } } } } } cout<<ans+sum/4*3<<endl; return 0; }
\(T2\) P228. 生活在hzoi上 \(0pts\)
- 原题: luogu P5206 [WC2019] 数树 问题 \(1\)
- 部分分
- \(5pts\) :每个节点仅能给 \(1\) ,生成树的方案数为 \(n^{n-2}\) 。
- 正解
- 逆天题面, \(miaomiao\) 说改不动就不用改了。上次遇到子集反演的时候 \(miaomiao\) 就直接毙了。
- 挂一下官方题解。
题面锅了,谢罪。
首先我们先考虑一个暴力。枚举第二棵树的形态求方案。
先给他转化一下,容易发现只在一棵树里出现的边是没用的,那么考虑两棵树内都存在的边。而根据定义,如果只保留两棵树内都存在的边,那同一联通块内的节点给的数是一样的。
那么设 \(E_1\) 为第一棵树的边集,\(E_2\) 为第二棵树的边集,总方案数就是 \(y^{n-|E_1\cap E_2|}\)。
然后考虑正解。现在答案是这个东西:
\[\sum_{E_2}y^{n-|E_1\cap E_2|} \]有个憨批式子叫子集反演:
\[f(S)=\sum_{T\subseteq S}\sum_{P\subseteq T}(-1)^{|T|-|P|}f(P) \]那设个 \(f(S)=y^{n-|S|}\) 推式子:
\[\begin{aligned} &\sum_{E_2}y^{n-|E_1\cap E_2|}\\ =&\sum_{E_2}f(E_1\cap E_2)\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}\sum_{P\subseteq T}(-1)^{|T|-|P|}f(P)\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}\sum_{P\subseteq T}(-1)^{|T|-|P|}y^{n-|P|}\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}y^{n-|T|}\sum_{P\subseteq T}(-y)^{|T|-|P|}\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}y^{n-|T|}\sum_{i=0}^{|T|}\binom{|T|}i(-y)^{|T|-i}\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}y^{n-|T|}(1-y)^{|T|}\\ =&\sum_{T\subseteq E_1}y^{n-|T|}(1-y)^{|T|}g(T) \end{aligned} \]其中 \(g(T)\) 为包含边集 \(T\) 的树个数。由 prufer 序列容易得到
\[g(T)=n^{k-2}\prod_{i=1}^ka_i \]其中 \(k\) 是连通块个数(也就是 \(n-|T|\)), \(a_i\) 是第 \(i\) 个连通块大小。
那么代回原式:
\[\begin{aligned} &\sum_{T\subseteq E_1}y^{n-|T|}(1-y)^{|T|}g(T)\\ =&\sum_{T\subseteq E_1}y^k(1-y)^{n-k}n^{k-2}\prod_{i=1}^ka_i\\ =&\frac{(1-y)^n}{n^2}\sum_{T\subseteq E_1}\prod_{i=1}^k\frac{ny}> {1-y}a_i \end{aligned} \]先特判掉 \(y=1\)。
考虑一下怎么算后边一堆东西。设个 \(dp_{x,i}\) 为 \(x\) 的子树内 \(x\) 所在连通块大小为 \(i\) 的答案,转移考虑在 \(x\) 处枚举子树 \(v\),\((x,v)\) 这条边选不选。这样树上背包就是 \(O(n^2)\) 的。
考虑优化。我们 \(dp\) 的瓶颈为第二维枚举联通块的大小,其实有个 trick 可以给他压掉:考虑到扩展Cayley定理里的式子 \(\prod_{i=1}^ka_i\) 的意义,是每个连通块里面选一个标记点连上,把联通块的贡献变成在联通块内选一个标记点的贡献,即每个联通块有且仅有一个标记点。如果选了标记点那么产生了一个有贡献的联通块,贡献要乘上 \(\dfrac ny{1-y}\)。
那么设 \(dp_{x,0/1}\) 为 \(x\) 子树内 \(x\) 所在联通块是否选了点的贡献,转移有:
初值:\(dp_{x,0}=1,dp_{x,1}=\dfrac ny{1-y}\)。
转移:
-
\(dp_{x,1}\):两种情况,一种是和下边的儿子成为一个联通块,另一个是断开儿子的边(儿子的联通块必须选了标记点)。那么就是 \(dp_{x,1}\times dp_{v,0}+dp_{x,0}\times dp_{v,1}+dp_{x,1}\times dp_{v,1}\)。
-
\(dp_{x,0}\):也是要么断开要么不断。断开必须儿子选了,不断必须儿子没选。那么就是 \(dp_{x,0}\times dp_{v,0}+dp_{x,0}\times dp_{v,1}\)。
复杂度 \(O(n)\)。
\(T3\) P229. 嘉然今天吃什么 \(0pts\)
-
部分分
- \(10 \%\)
-
枚举 \(a_{i} \bigoplus a_{j}\) 可能会对哪些点产生影响,时间复杂度为 \(O(n^{3})\) 。
-
将 \(\{ a \}\) 插到 \(01Trie\) 上,每次遍历到一个新的节点就重构 \(01Trie\) ,时间复杂度为 \(O(n^{2} \log V)\) 。
点击查看代码
struct Trie { ll trie[3000010][2],tot=0; void init() { tot=0; memset(trie,0,sizeof(trie)); } void insert(ll s) { ll x=0,i; for(i=60;i>=0;i--) { if(trie[x][(s>>i)&1]==0) { tot++; trie[x][(s>>i)&1]=tot; } x=trie[x][(s>>i)&1]; } } ll query(ll s) { ll x=0,ans=0,i; for(i=60;i>=0;i--) { if(trie[x][((s>>i)&1)^1]==0) { x=trie[x][(s>>i)&1]; } else { x=trie[x][((s>>i)&1)^1]; ans|=(1ll<<i); } } return ans; } }T; struct node { ll nxt,to; }e[1000010]; ll head[1000010],a[1000010],pos[1000010],dfn[1000010],out[1000010],ans[1000010],fa[1000010],tot=0,cnt=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(ll x,ll fa) { tot++; dfn[x]=tot; pos[tot]=x; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs1(e[i].to,x); } } out[x]=tot; } ll ask(ll x,ll n) { T.init(); for(ll i=1;i<=dfn[x]-1;i++) { T.insert(a[pos[i]]); } for(ll i=out[x]+1;i<=n;i++) { T.insert(a[pos[i]]); } ll ans=0; for(ll i=1;i<=dfn[x]-1;i++) { ans=max(ans,T.query(a[pos[i]])); } for(ll i=out[x]+1;i<=n;i++) { ans=max(ans,T.query(a[pos[i]])); } return ans; } int main() { ll n,i; cin>>n; for(i=2;i<=n;i++) { cin>>fa[i]; add(fa[i],i); add(i,fa[i]); } for(i=1;i<=n;i++) { cin>>a[i]; } dfs1(1,0); for(i=1;i<=n;i++) { cout<<ask(i,n)<<endl; } return 0; }
-
- \(20 \%\) :将 \(\{ a \}\) 插到 \(01Trie\) 上,可持久化 \(01Trie\) 维护,常数小的话应该可以通过。
- 另外 \(20 \%\) :找到链顶后顺序插入,时间复杂度为 \(O(n \log n \log V)\) 。
- \(10 \%\)
-
正解
- 求出全局最大异或点对 \((x,y)\) ,只有 \(1 \to x,1 \to y\) 的两条链上的节点的答案不是 \(a_{x} \bigoplus a_{y}\) 。
- 单独处理 \(1 \to x,1 \to y\) 的两条链上的节点即可,和链的部分分一样。
点击查看代码
struct Trie { int trie[30000010][2],end[30000010],tot=0; void init() { for(ll i=0;i<=tot;i++) { trie[i][0]=trie[i][1]=end[i]=0; } tot=0; } void insert(ll s,ll id) { ll x=0,i; for(i=60;i>=0;i--) { if(trie[x][(s>>i)&1]==0) { tot++; trie[x][(s>>i)&1]=tot; } x=trie[x][(s>>i)&1]; } end[x]=id; } pair<ll,ll> query(ll s) { ll x=0,ans=0,i; for(i=60;i>=0;i--) { if(trie[x][((s>>i)&1)^1]==0) { x=trie[x][(s>>i)&1]; } else { x=trie[x][((s>>i)&1)^1]; ans|=(1ll<<i); } } return make_pair(ans,end[x]); } }T; struct node { ll nxt,to; }e[1000010]; ll head[1000010],a[1000010],vis[1000010],ans[1000010],fa[1000010],tot=0,cnt=0,sum=0; vector<ll>s; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(ll x) { T.insert(a[x],x); sum=max(sum,T.query(a[x]).first); for(ll i=head[x];i!=0;i=e[i].nxt) { dfs(e[i].to); } } void dfs_init(ll x,ll y) { T.insert(a[x],x); sum=max(sum,T.query(a[x]).first); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=y) { dfs(e[i].to); } } } void ask(ll x) { sum=0; T.init(); s.clear(); for(ll i=x;i!=1;i=fa[i]) { vis[i]=1; s.push_back(i); } for(ll i=s.size()-1;i>=0;i--) { dfs_init(fa[s[i]],s[i]); ans[s[i]]=sum; } } int main() { ll n,maxx=0,i; pair<ll,ll>tmp,pos; cin>>n; for(i=2;i<=n;i++) { cin>>fa[i]; add(fa[i],i); } for(i=1;i<=n;i++) { cin>>a[i]; T.insert(a[i],i); tmp=T.query(a[i]); if(maxx<tmp.first) { maxx=tmp.first; pos=make_pair(tmp.second,i); } } vis[1]=1; ask(pos.first); ask(pos.second); for(i=1;i<=n;i++) { cout<<((vis[i]==1)?ans[i]:maxx)<<endl; } return 0; }
\(T4\) P230. APJifengc \(10pts\)
- 原题: [AGC036D] Negative Cycle
- 部分分
-
\(10pts\) :枚举哪条边删或不删,最后跑 \(SPFA\) 判断有没有负环。
点击查看代码
struct node { ll nxt,to,w,id; }e[300010]; ll head[510],a[510][510],dis[510],vis[510],num[510],cnt=0,ans=0x7f7f7f7f; vector<ll>sh; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; e[cnt].id=cnt; head[u]=cnt; } pair<ll,ll>divide(ll sum,ll n) { ll u=ceil(1.0*sum/n),v=sum-(u-1)*n; if(v==u) { v++; } return make_pair(u,v); } bool spfa(ll s,ll n) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); memset(num,0,sizeof(num)); queue<ll>q; q.push(s); dis[s]=0; vis[s]=1; while(q.empty()==0) { ll x=q.front(); vis[x]=0; q.pop(); for(ll i=head[x];i!=0;i=e[i].nxt) { ll flag=0; for(ll j=0;j<sh.size();j++) { if(e[i].id==sh[j]) { flag=1; break; } } if(flag==0) { if(dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; num[e[i].to]=num[x]+1; if(num[e[i].to]>=n+1) { return false; } if(vis[e[i].to]==0) { q.push(e[i].to); vis[e[i].to]=1; } } } } } return true; } bool check(ll sum,ll n) { sh.clear(); for(ll i=1;i<=n*n;i++) { if((sum>>i)&1) { sh.push_back(i); } } return spfa(0,n+1); } void dfs(ll pos,ll sum,ll n) { if(pos==n*n+1) { if(check(sum,n)==1) { ll num=0; pair<ll,ll>tmp; for(ll i=0;i<sh.size();i++) { tmp=divide(sh[i],n); num+=a[tmp.first][tmp.second]; } ans=min(ans,num); } } else { dfs(pos+1,sum|(1<<pos),n); dfs(pos+1,sum,n); } } int main() { ll n,i,j; cin>>n; for(i=1;i<=n;i++) { for(j=1;j<=i-1;j++) { cin>>a[i][j]; add(i,j,1); } add(i,i+1,0); for(j=i+1;j<=n;j++) { cin>>a[i][j]; add(i,j,-1); } } for(i=1;i<=n;i++) { add(0,i,0); } dfs(1,0,n); cout<<ans<<endl; return 0; }
-
- 正解
- 逆天题面,讲题时讲都没讲,虽然讲了也听不懂。
拜神。
负环想到差分约束。设第 \(i\) 个点对应 \(x_i\)。
设 \(q_i=x_i-x_{i+1}\),首先根据初始的边可以知道 \(q_i\ge 0\)。
对于边权为 \(-1\) 的边 \(i\rightarrow j\),有 \(x_i-x_j\ge 1\),也就是 \(q_i+q_{i+1}+\dots+q_{j-1}\ge 1\)。
边权为 \(1\) 同理,有 \(x_j-x_i\le 1\),也就是 \(q_j+q_{j+1}+\dots+q_{i-1}\le 1\)。
假如我们知道 \(q_i\),那么边权为 \(-1\) 的边在区间和 \(\ge 1\) 时留下,边权为 \(1\) 的边在区间和 \(\le 1\) 时留下。也就是如果一段 \(q_i=0\),那么这段的边权为 \(-1\) 的边就要删掉,如果区间和 \(\ge 2\) 那么边权为 \(1\) 的边就要删掉。
如果 \(q_i\ge 2\),那么显然不如 \(1\) 好。于是 \(q_i=0/1\)。
设 \(dp_{i,j}\) 为扫到 \(i\),最后一个 \(1\) 是 \(q_i\),倒数第二个是 \(q_j\) 的最小代价,转移可以从 \(dp_{j,k}\) 转移,系数可以二维前缀和。复杂度 \(O(n^3)\)。
总结
- \(T2\) 忘了生成树的个数。
- \(T3\) 的 \(01Trie\) 树清空时没清空彻底且不会算空间,挂了 \(10pts\) 。
后记
-
题目背景夹带私活。
-
成分复杂。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18353666,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。