GJOI 9.11/9.13 题解
因为这两次测试的后两题都不太可补,于是放在一起。不过前面的题目质量都挺高的。
1.排序
题意
思路
我做这种计数 dp 依然垃圾。因为最后那一步 swap
我直接迷茫了。
先看到这个 partition
函数,对于 al∼ra_{l\sim r}al∼r,以 ara_rar 为基准,设对这个区间排序之后 ara_rar 所在位置为 xxx:
- 将小于 ara_rar 的数顺序不变地放到 l∼x−1l\sim x-1l∼x−1;
- 将大于 ara_rar 的数顺序不变地放到 x∼r−1x\sim r-1x∼r−1;
- 将处理完后,在 xxx 上的数和 ara_rar 交换,使 ara_rar 在正确的位置上;
- 在
Qsort
中分治,处理 [l,x)[l,x)[l,x) 和 (x,r](x,r](x,r]。
虽然说 partition
函数最后一步 swap
会打乱原来的顺序,但是对于所有排列而言,大于 ara_rar 的数所有形态的排列顺序都会等概率出现,因而影响不大。
设 fi,kf_{i,k}fi,k 表示对 iii 个数的排列递归至少 kkk 层(注意和询问所给的 kkk 含义不同,因为 Qsort
中只有 k>1k>1k>1 时才能继续递归,实际递归层数为 k−1k-1k−1),能够变为有序的排列数。
我们发现决定排序后形态的是最后一个数,考虑枚举 1∼i1\sim i1∼i 中最后一个数(排名)为 lll,因此小于 lll 的 l−1l-1l−1 个数还可以递归 k−1k-1k−1 层,大于 lll 的 i−li-li−l 个数还可以递归 k−1k-1k−1 层,即 fi−1,k−1×fi−l,k−1f_{i-1,k-1}\times f_{i-l,k-1}fi−1,k−1×fi−l,k−1。
我们想要再递归 111 次 1∼i1\sim i1∼i 就被排序好了,因此当前小于 lll 的 l−1l-1l−1 个数、大于 lll 的 i−li-li−l 的数要有序排好。但是根据 partition
的功能,不一定要小于的、或大于的全部挨在一起,它们之间可以相互插入,只要相对有序即可。这样打乱插入的方案数就是在前面 i−1i-1i−1 个数选择 i−li-li−l 个位置写上大于 lll 的数,即 (i−1i−l)\binom{i-1}{i-l}(i−li−1) 或 (i−1l−1)\binom{i-1}{l-1}(l−1i−1)。
fi,k←fl−1,k−1×fi−l,k−1×(i−1i−l)f_{i,k}\leftarrow f_{l-1,k-1}\times f_{i-l,k-1}\times \binom{i-1}{i-l}fi,k←fl−1,k−1×fi−l,k−1×(i−li−1)
预处理 O(n2k)=O(n3)O(n^2k)=O(n^3)O(n2k)=O(n3),根据上文 O(1)O(1)O(1) 查询答案为 fn,k−1f_{n,k-1}fn,k−1。
记得勤取模。然后模拟赛时一定要勤思考,不要想一会没想到就问别人,不会就是不会,赛后补就好了。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=302;
ll Q,mod;
ll f[N][N];
ll C[N][N];
void init()
{for(int i=1;i<N;i++)C[i][0]=C[i][i]=1;for(int i=1;i<N;i++){for(int j=1;j<i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;}
}
int main()
{
// freopen("sort.in","r",stdin);
// freopen("sort.out","w",stdout);scanf("%lld%lld",&Q,&mod);init();for(int i=0;i<N;i++){f[0][i]=f[1][i]=1;}for(int i=2;i<N;i++){for(int k=1;k<N;k++){f[i][0]=1;for(int l=1;l<=i;l++){f[i][k]=(f[i][k]+f[l-1][k-1]*f[i-l][k-1]%mod*C[i-1][i-l]%mod)%mod;}}}while(Q--){ll n,k;scanf("%lld%lld",&n,&k);printf("%lld\n",f[n][k-1]);}return 0;
}
2.CF1975D Paint the tree
题意
ttt 组测试数据,1≤t≤51\le t\le 51≤t≤5,1≤n≤2×1051\le n\le 2\times 10^51≤n≤2×105。
思路
gzw 时期的题目,当时可做不出来,现在感觉不难。
一个显然的贪心:AAA 和 BBB 相遇之后,才能产生贡献,即 BBB 和 AAA 一起走(a,ba,ba,b 能在一个点相遇)或者 BBB 跟在 AAA 的尾巴(a,ba,ba,b 不能在一个点相遇)。在新的 b′b'b′ 重构整棵树,遍历 n−1n-1n−1 边 ,走下去再走回来 b′b'b′(这样会遍历每条边 222 次),最后遍历 b′b'b′ 为根的最长链时,就不用再回到 b′b'b′ 了,于是减去 DpstDpstDpst。
发现如果 Disa,bDis_{a,b}Disa,b 为偶数,两点可以在一个点相遇,这很好搞,直接在 a→ba\to ba→b 路径上,两点都跳 Disa,b2\frac{Dis_{a,b}}{2}2Disa,b 步即可。答案为 Disa,b2+2(n−1)−Dpst\frac{Dis_{a,b}}{2}+2(n-1)-Dpst2Disa,b+2(n−1)−Dpst。
如果为奇数,两点跳 ⌊Disa,b2⌋\left\lfloor \frac{Dis_{a,b}}{2} \right\rfloor⌊2Disa,b⌋ 步后还差一条边相遇。如果从现在的 b′b'b′ 开始重构,BBB 就跑到 AAA 前面了,于是我们让 BBB 多跳一步到 a′a'a′,此时 AAA 也会随便跳到 a′a'a′ 周围随便一点 a′′a''a′′,在 a′a'a′ 重构整棵树然后 BBB 沿着 AAA 的轨迹跑即可,只要 a′′a''a′′ 不在 a′a'a′ 为根的最长链,就是最优方案。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=2e5+9;
ll Q,n,a,b;
struct edge
{ll to,next;
}e[N<<1];
ll idx,head[N];
void addedge(ll u,ll v)
{idx++;e[idx].to=v;e[idx].next=head[u];head[u]=idx;
}
ll Ceil(ll fz,ll fm)
{return (fz+fm-1)/fm;
}
ll Dep[N],dis[N],Fat[N];
void dfs2(ll u,ll fa)
{Dep[u]=Dep[fa]+1;Fat[u]=fa;for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa)continue;dfs2(v,u);}
}
void clean()
{idx=0;memset(e,0,sizeof(e));memset(head,0,sizeof(head));memset(f,0,sizeof(0));memset(fat,0,sizeof(fat));
}
int main()
{freopen("col.in","r",stdin);freopen("col.out","w",stdout);scanf("%lld",&Q);while(Q--){scanf("%lld%lld%lld",&n,&a,&b);clean();for(int i=1;i<n;i++){ll u,v;scanf("%lld%lld",&u,&v);addedge(u,v);addedge(v,u);}dfs2(a,0);ll cur=b,tot=0;for(int i=1;i<=n;i++)dis[i]=Dep[i]-1;while(dis[cur]>Ceil(dis[b],2)){cur=Fat[cur];tot++;}if(dis[b]&1){tot++,cur=Fat[cur];}dfs2(cur,0);ll Dpst=0;for(int i=1;i<=n;i++)Dpst=max(Dpst,Dep[i]-1);printf("%lld\n",tot+2*(n-1)-Dpst);}return 0;
}
upd:为什么跑“中点”相遇就是正确的?假如存在一个最优重构点 ccc,依然想 A,BA,BA,B 都走到 ccc 且 AAA 需要先走到,实则这个点不管在树上什么位置,a→ca\to ca→c 和 b→cb\to cb→c 的过程两个人轨迹相并必然覆盖了整个 a→ba\to ba→b 的路径。
可以预见的,可能在 ccc 处重构的最长链更长,但是 AAA 先到 ccc 之际,和 BBB 拉开了一段距离要弥补回来,这就用 DpstcDpst_cDpstc 比 DpstmidDpst_{mid}Dpstmid 之差不回来。因此在中点 midmidmid 或者中间边相遇必然不劣。
后面 222 题感觉实在不是我能补的啊,但是同机房大佬 Garbage_fish 和 Fireworks_Rise 。欢迎右转前往他的超详细博客和他的超详细博客。
接下来是 GJOI 9.13,这后面同样有很难补的两题。
3.洛谷 P11662 JOI 2025 Final 方格染色 / Grid Coloring
题意
2≤N≤2×1052\le N\le 2\times 10^52≤N≤2×105,Ai,Bi∈[1,109]A_i,B_i\in[1,10^9]Ai,Bi∈[1,109],保证 A1=B1A_1=B_1A1=B1。
思路
显然 (1,1)(1,1)(1,1) 不会参与任何格子的最大值贡献。记 ai=maxi=2nAi,bi=maxi=2nBi\displaystyle a_i=\max_{i=2}^n A_i,b_i=\max_{i=2}^n B_iai=i=2maxnAi,bi=i=2maxnBi,那么 (i,j)(i,j)(i,j) 格子上的数为:
max(ai,bj)\max(a_i,b_j)max(ai,bj)
分别考虑有多少格子的 max(ai,bj)=ai\max(a_i,b_j)=a_imax(ai,bj)=ai 以及 bjb_jbj。显然 a,ba,ba,b 数组都是单调不降的,因此可以二分有多少 bj≤aib_j\le a_ibj≤ai,那么 (i,2∼j)(i,2\sim j)(i,2∼j) 都要填 aia_iai。同样二分多少 ai<bja_i<b_jai<bj,那么 (2∼i,j)(2\sim i,j)(2∼i,j) 都要填 bjb_jbj。(这里不用 ≤\le≤ 是因为在算 bj≤aib_j\le a_ibj≤ai 时已经算过等于的情况了)
二分 lower_bound
平替即可,记数懒得离散化就 map
。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2e5+9;
ll n,x;
ll a[N],b[N];
map<ll,ll>cnt;
ll tot,ans;
void merge(ll x)
{if(cnt[x]>tot)tot=cnt[x],ans=x;else if(cnt[x]==tot&&x>ans)ans=x;
}
int main()
{freopen("grid.in","r",stdin);freopen("grid.out","w",stdout);scanf("%lld",&n);scanf("%lld",&x);cnt[x]++;merge(x);for(int i=2;i<=n;i++){scanf("%lld",&x);cnt[x]++;merge(x);a[i]=max(a[i-1],x);}scanf("%lld",&x);for(int i=2;i<=n;i++){scanf("%lld",&x);cnt[x]++;merge(x);b[i]=max(b[i-1],x);}
// for(int i=1;i<=5;i++)
// cout<<cnt[i]<<" ";
// cout<<endl;
// cout<<ans1<<endl;for(int i=2;i<=n;i++){ll pos=upper_bound(b+2,b+n+1,a[i])-b-2;cnt[a[i]]+=pos;merge(a[i]);}for(int i=2;i<=n;i++){ll pos=lower_bound(a+2,a+n+1,b[i])-a-2;cnt[b[i]]+=pos;merge(b[i]);}printf("%lld %lld",ans,tot);return 0;
}
4.洛谷 P12576 UOI 2021 数字图
题意
1≤2.5×105,1≤m≤5×1051\le 2.5\times 10^5,1\le m\le 5\times 10^51≤2.5×105,1≤m≤5×105,ai∈[1,109]a_i\in[1,10^9]ai∈[1,109]。
思路
本篇博客实现参考这篇博客。
看到博弈论感觉来到盲区了。
我们发现 sub4 只有 1,21,21,2。那么先手会尽全力走到 222,后手会尽全力走到 111;一旦先手走到 111,后手立即结束游戏;一旦后手走到 222,先手立即结束游戏。
那么 a1=2a_1=2a1=2 先手必赢。我们考虑 a1=1a_1=1a1=1 的情况。在 111,先手为了不输,肯定不会走到相邻的 111,所以会走到 ax=2a_{x}=2ax=2(1,x1,x1,x 有边相连);在 xxx,后手为了不输,肯定不会走到相邻的 222,所以会走到 ay=1a_y=1ay=1(x,yx,yx,y 有边相连);……如此循环往复。
那么聪明的两人必然不会走 au=ava_u=a_vau=av 的边 u→vu\to vu→v。建出一个没有两点点权相同的边的图,从 111 开始走,如果使得后手走到某个点没有出度先手必胜,否则后手必胜。
依据这个博弈模拟这个过程。