19CSP-S十一集训三地联考—众神归位 题解总结
订正了三天的题目 自闭....
T1 幸福
m<=1e18
考虑这个数据范围不是 (1) 就是 (logn)
只有我这个菜鸡 考场上 直接来了通项 我直接带入了 斐波那契的 通项公式 当然 我考试的时候 制作出来了一半
但是 其实考场上 对于带根号 并且 存在取模的 式子 我真的 很怵 不敢写 但是 在考试结束后 我认真分析了一边
这个东西绝对是存在通项公式的 我们发现他是两个等比数列 乘上某个东西在进行求和 我们可以构造 一个a 序列 和一个 b 序列
那么
$F_n=(F_{n-1}+f{n-1}+F_{n-2}+f_{n-2}) $
然后我们 知道 $a_{n+1}=a_{n}+a_{n-1}$ $b_{n+1}=b_{n}+b_{n-1}$
$F_n=a_n*(F_2+f_2)+b_n*(f_1+F_1)$
这个东西 显然是多个 等比数列 乘 然后求和 不管了 我信仰是有通项公式的 而且 这确实可以算出来 但是 我无法码这个东西 所以只作为
拓展 以了却我考场上的思路 而且 T1 就是一个泛函分析的东西 这个东西 我好久没看了 鸽了
那么 我们现在来考虑 logn 的做法 显然
努力的寻找这个东西的 递推关系 $F_n=F_{n-1}+F_{n-2}+f_n$ 具体证明就是考虑将$f_i$ 用斐波那契的 递推带入 从而发现这个递推
我们既然已经知道了这个 递推关系 那么我们 有必要 使用矩阵快速幂 加速这个递推关系
我们是需要一个1✖5 的矩阵 和一个 5✖5 的 矩阵 因为chdy 跟我讲 转移矩阵 一般都是 方形的
那么我们考虑 为什么需要一个 1✖5 的状态矩阵 我们尝试 发现 一个 2✖3 的矩阵 是很难做的 但是 对于什么情况下 时候
这样的矩阵 一半是在转移的过程中 与之有关的那个函数的转移 和当前是一样的
所以我们对于这样一个矩阵 我们还需要 求F 的和 所以在 状态中 有一个Sn 那么 根据刚才的矩阵 存在 Fn-1 Fn 然我们还需要 斐波那契的一项
所以还有两个是 fn 和 fn-1 然后我们求出 转移矩阵
$$
\begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 1 & 0 & 0\\ 1 & 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 1 & 1\\ 0 & 1 & 0 & 1 & 0\end{bmatrix}
$$
T2 树链剖分
考虑到 对于一个 树 确定覆盖哪几条边 不会随着 树的根节点的改变而改变
而且 这种对于一个无根树的路径进行覆盖 我们显然可以想到 树上差分 对于边的差分
对于一条路径从s到t 我们类比序列上的差分 即 $sum[s]--,sum[t]--,sum[lca(s,t)]-=2$
所以进行一遍dfs 求出一个节点的子树权值和 就是从这个点的父节点 到这个点 被覆盖了多少次
所以我们 求出所有的和就是整棵树所有边被覆盖的次数 记作res 然后单独考虑 这个树链剖分集合的大小
首先$size[i]$ 表示 i的子树和 那么就是$fa[i]$指向$i$那条边被覆盖的次数
$maxx[i]$ 表示从i出发的边中所有的儿子y 对应的最大的$size[y]$
$maxp[i]$ 表示从i出发的边中所有的儿子y 对应的次大的$size[y]$ 换根的时候会用到
然后考虑 对于一个节点来说 我们可以贪心的去选取 下一个 树链剖分的边 是指向哪个儿子的 显然是 当前被覆盖的边数次数最多的那一条边
这个贪心的正确性应该是显然的 所以我们考虑 指定不同的根节点 一定会造成不同的情况 这其中一定存在一个换根dp
我们按照刚才贪心的思路 我们可以求出来 以当前为根的子树内最大树链剖分的大小 指定1为根节点 显然存在$g[1]=所有节点的maxx[i]$
假设当前存在一条从x指向y的边 而且 $g[x]$ 已经求出 我们现在考虑 怎么求出来$g[y]$
存在状态转移方程:
$$g[y]=g[x]-max(size[x],maxx[x])-maxx[y];$$
$$g[y]+=max(size[y],maxx[y])+max(size[x],size[y]==maxx[x]?maxp[x]:maxx[x]);$$
对于第二个方程 是因为 我们需要知道x有没有选择指向y的那条边 如果选择的是y 那么 我们就要加上x出发的次大值 否则
我们就要加上最大值 此时y成为了整颗子树的根 而对于他的儿子他只能选择一条边 我们按照贪心的思路就找都这条
#include<bits/stdc++.h> using namespace std; template<typename T>inline void read(T &x) { x=0;T f=1,ch=getchar(); while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x*=f; } typedef long long ll; const int N=100010; ll n,m,tot,x,y,size[N],sum[N],d[N],f[N][25],lin[N]; ll g[N];//f1[i]表示以i为根的子树内的最大树链剖分 //g[i] 表示以i为整颗子树的根的最大树链剖分 ll res,maxx[N],maxp[N],sx[N],sxp[N]; struct gg { int y,next; }a[N<<1]; inline void add(ll x,ll y) { a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; } inline void bfs() { queue<ll>q; q.push(1);d[1]=1; while(q.size()) { ll x=q.front(); q.pop(); for(ll i=lin[x];i;i=a[i].next) { ll y=a[i].y; if(d[y]) continue; d[y]=d[x]+1; f[y][0]=x; for(int j=1;j<=23;j++) f[y][j]=f[f[y][j-1]][j-1]; q.push(y); } } } inline ll lca(ll x,ll y) { if(d[x]>d[y]) swap(x,y); for(ll i=23;i>=0;--i) { if(d[f[y][i]]>=d[x]) y=f[y][i]; } if(x==y) return x; for(ll i=23;i>=0;--i) { if(f[y][i]!=f[x][i]) { y=f[y][i],x=f[x][i]; } } return f[x][0]; } inline void dfs(ll x,ll fa) { size[x]=sum[x]; for(ll i=lin[x];i;i=a[i].next) { ll y=a[i].y; if(y==fa) continue; dfs(y,x); size[x]+=size[y]; if(maxx[x]<size[y]) { maxp[x]=maxx[x]; maxx[x]=size[y]; } else if(size[y]>maxp[x]) { maxp[x]=size[y]; } } g[1]+=maxx[x];res+=size[x]; } inline void dp(ll x,ll fa) { for(ll i=lin[x];i;i=a[i].next) { ll y=a[i].y; if(y==fa) continue; g[y]=g[x]-max(size[x],maxx[x])-maxx[y]; g[y]+=max(size[y],maxx[y])+max(size[x],size[y]==maxx[x]?maxp[x]:maxx[x]); dp(y,x); } } int main() { //freopen("1.in.cpp","r",stdin); read(n); read(m); for(ll i=1;i<n;i++) { read(x); read(y); add(x,y); add(y,x); } bfs(); for(ll i=1;i<=m;i++) { read(x); read(y); sum[x]++,sum[y]++; ll c=lca(x,y); sum[c]-=2; } dfs(1,0); dp(1,0); ll ans=0,x; for(ll i=1;i<=n;i++) { if(ans<g[i]) { //x=i; ans=g[i]; } } cout<<res-ans<<endl; return 0; }
T3
比较自闭的是 我以为分给小Ex张 而将剩下y张都给了小y
算了 这不重要 我们存在一种暴力的做法 就是枚举出来当前小E 手中每种i花色牌的数量 然后考虑对于他拥有的第i个花色的牌的数量bi
但是这样的情况过多 所以我们不妨枚举胜率 还是对于第i种花色 考虑此时小E手中拥有bi 然后考虑 此时小E选择 i花色 能赢的概率
那么此时 设小F拥有的 i 花色的牌的数量是 j 那么存在 可以使小E 获胜的方案数
设m=$\sum_{i=1}^{n} a_i$;
$\sum_{j=0}^{min(y,b_i-a_i,b_i-z)}\binom{a_i-b_i}{j}\binom{m-x-a_i+b_i}{y-j}$
解释一下上面那个式子的意义 因为当前枚举的是j 所以前半部分的 表示小F拥有j张i花色牌的方案数
而后半部分表示 小F不拿到i花色牌的数量 观察 $m-x-(a_i-b_j)$ 其实保证我们此时在备选集合内 不再拥有i花色的牌
因为当前我们要保证他能选择i花色的牌的数量是j 所以利用乘法原理 用选到i花色 乘 选到非i 花色的 数量
这里我们考虑的都是胜率的分子 因为分母是永远不变的 所以 根据上面的胜率 我们可以考虑 由最开始 枚举的小E手中的牌
改为 枚举胜率 上面那个式子的分母 显然是 $\binom{m-x}{y}$ 根据分子必定小于分母 所以我们这个式子是 小于$\binom{100}{50}$ 的
根据数据范围可以知道 所以这样的答案我们可以使用__int128 存储下 而不用写高精度
考虑 我们已经求出来所有的胜率 我们还要做一个事情 我们要保证选择了bi 张i花色的牌 和x-bi张其他花色的牌
此时选择 i花色 其他花色的胜率都小于等于 i 花色这里我们考虑 dp
设$dp(i,j)$ 表示对于前i种花色 总共选出来了j张 并且每种花色的胜率都小于等于 i 花色的胜率得方案数
那么 目标状态就是 $dp(n,m-b_i)$ 考虑 此时呈上 $\binom{a_i}{b_i}$ 就是此时的贡献
显然这个题目最难想到的就是容斥 而且对于容斥的处理也比较巧妙
首先考虑为什么要容斥
根据上面 我们已经求出来了所有的胜率 假设所有的胜率两两不同
那么对于这种花色i满足限制 $b_i$ 的情况 不会存在一种j st j的胜率比 i 大 显然 这是我们在刚才dp的过程中就处理出来了
所以现在出现了一个问题 如果存在 胜率是两两相同的 那么我们按照上面的方式就会算重复
这里我们有一个解决方法 我们可以开一个数组 vector<pair<__int128,pii> >vec 将所有的胜率 丢进去
首先将所有的胜率 从小到大 排序
对于每一个限制 $lim[i]$ 也就是 $b_i$ 的限制 最多不超过多少张牌 我们首先初始化为$min(a_i,z-1)$ 这样保证此时i一定是最优策略
那么 我们可以保证此时枚举的胜率一定是满足 在限制下的 i 是最优花色 而且我们可以保证此时计算的答案一定不会在之前被计算过
我们i知道从小到大枚举 胜率 那么对于限制 lim[i] 一定是递增的 因为 拿的牌越多 我们的胜率 越大
所以我们一边 枚举 胜率 一边把 i 的 $lim[i]$ 赋值为$b[i]$ 此时一定递增 因为我们排序后胜率分子是递增的
分析复杂度
我们不断枚举i 然后枚举一个 bi 考虑不同的胜率是 $(\sum_{a_i})$ 所以总的复杂度应该是 (m3+m2n) 的
#include<bits/stdc++.h> using namespace std; template<typename T>inline void read(T &x) { x=0;T f=1,ch=getchar(); while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x*=f; } typedef long long ll; typedef pair<int,int> pii; typedef pair<ll,ll> Pll; const int MOD=998244353; __int128 c[105][105]; int n,x,y,z,m,a[110],fac[110],invf[110],lim[110]; inline int mod(int x) {return x<MOD?x:x-MOD;} inline int comb(int n,int k){if(n<k) return 0; return (ll)fac[n]*invf[k]%MOD*invf[n-k]%MOD;} inline int pow_mod(int x,int i){ int y=1; while(i) { if(i&1)y=(ll)y*x%MOD; x=(ll)x*x%MOD; i>>=1; } return y; } inline int calcF(int i,int j) { int t=0; for(int k=0;k+z<=j&&k<=y&&j+k<=a[i];++k){ t=mod(t+(ll)comb(a[i]-j,k)*comb(m-x-a[i]+j,y-k)%MOD); } return t; } inline void c_init() { fac[0]=1; for(int i=1;i<=m;++i) fac[i]=(ll)fac[i-1]*i%MOD; invf[m]=pow_mod(fac[m],MOD-2); for(int i=m-1;i>=0;--i) invf[i]=(ll)invf[i+1]*(i+1)%MOD; c[0][0]=1; for(int i=1;i<=m;++i) { c[i][0]=1; for(int j=1;j<=i;++j) { c[i][j]=c[i-1][j]+c[i-1][j-1]; } } } int dp[110][110]; inline int DP(int ex,int up){ memset(dp,0,sizeof(dp)); dp[0][0]=1; for(int i=1;i<=n;++i) { if(i==ex) { for(int j=0;j<=up;++j) dp[i][j]=dp[i-1][j]; continue; } for(int j=0;j<=lim[i];++j) { for(int k=j;k<=up;++k) { dp[i][k]=mod(dp[i][k]+(ll)dp[i-1][k-j]*comb(a[i],j)%MOD); } } } return dp[n][up]; } int main() { //freopen("1.in.cpp","r",stdin); read(n); read(x); read(y); read(z); for(int i=1;i<=n;i++) read(a[i]),m+=a[i]; c_init(); vector<pair<__int128,pii> >vec; for(int i=1;i<=n;++i) { for(int j=z;j<=a[i]&&j<=x;++j) { __int128 t=0; for(int k=0;k+z<=j&&k<=y&&j+k<=a[i];++k) { t+=c[a[i]-j][k]*c[m-x-a[i]+j][y-k]; } vec.push_back(make_pair(t,make_pair(i,j))); } } sort(vec.begin(),vec.end()); for(int i=1;i<=n;++i) lim[i]=min(a[i],z-1); int ans=0; for(int t=0;t<(int)vec.size();++t){ int i=vec[t].second.first,j=vec[t].second.second; ans=mod(ans+(ll)DP(i,x-j)*comb(a[i],j)%MOD*calcF(i,j)%MOD); lim[i]=j; } cout<<(ll)ans*pow_mod((ll)comb(m,x)*comb(m-x,y)%MOD,MOD-2)%MOD<<endl; return 0; }