暑假集训CSP提高模拟18
组题人: @H_Kaguya | @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\)
-
原题: luogu P8511 [Ynoi Easy Round 2021] TEST_68
-
部分分
- \(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\) 。
后记
-
题目背景夹带私活。
-
成分复杂。