GJOI 5.27 题解
1.洛谷 P8981 距离
参考了这篇题解,我的比较口胡。
题意
给以一颗含有 n n n 个节点的树,定义树上任意两点的 u , v u,v u,v 之间的距离 d u , v d_{u,v} du,v 为两点之间点的数量。
如果树上两点 u , v u,v u,v 满足,对于树上的任意节点 x x x,都满足 d ( u , x ) ≤ d ( u , v ) d(u,x)\le d(u,v) d(u,x)≤d(u,v),并且满足 d ( x , v ) ≤ d ( u , v ) d(x,v)\le d(u,v) d(x,v)≤d(u,v),那么我们称无序对 ( u , v ) (u,v) (u,v) 为好点对。
树上每个节点 i i i 都有一个点权 v i v_i vi, v i v_i vi 的值为两点之间的最短路径经过 i i i 的好点对的数量。求 ∑ i = 1 n v i k m o d 998244353 \displaystyle\sum_{i=1}^n {v_i}^k \bmod998244353 i=1∑nvikmod998244353。
1 ≤ n ≤ 5000000 1\le n\le 5000000 1≤n≤5000000, k ∈ { 1 , 2 } k\in\{1,2\} k∈{1,2}。
思路
容易想到,好点对只能是直径的两端点,因为在树上直径是能出现的最长长度了。题目转化为每个点被直径覆盖的次数。
有一个性质,就是若树上所有边边权均为正,则树的所有直径中点重合。那么我们考虑将直径中点 M i d Mid Mid 提到根去,因为所有直径都会过 M i d Mid Mid 这个点。
我们计算每个节点 i i i 最多可以向下延伸多少个节点 m d i s i mdis_i mdisi,以及这种长度的数量 n u m i num_i numi。中点可能有两个?分类讨论。先考虑计算,除 M i d Mid Mid 外所有节点的覆盖次数,我们统计最长路和次长路的总条数 c n t 1 , c n t 2 cnt1,cnt2 cnt1,cnt2,对于 M i d Mid Mid 的儿子们:
-
当直径为奇数个点时,中点只有一个。此时中点的孩子的 m d i s mdis mdis 应该都是一样的;
-
当直径为偶数个点时,此时中点的孩子中,会有且仅有一个孩子的 m d i s mdis mdis 比其他孩子大 1 1 1。这时经过中点的直径数量就应该是这个特殊孩子的 n u m num num 与其他孩子的 n u m num num 的乘积。
遍历下面的其他节点处理完 M i d Mid Mid 和儿子就能遍历其他的节点了。
注意勤取模。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=5e6+9,mod=998244353;
ll n,k;
ll idx,head[N];
struct edge
{ll to,next;
}e[N<<1];
void addedge(ll u,ll v)
{idx++;e[idx].to=v;e[idx].next=head[u];head[u]=idx;
}
ll dis[N],d1,d2,D;
void dfs1(ll u,ll fa)
{if(dis[u]>dis[d1])d1=u;for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa)continue;dis[v]=dis[u]+1;dfs1(v,u);}
}
ll fat[N];
void dfs2(ll u,ll fa)
{fat[u]=fa;if(dis[u]>dis[d2]){d2=u;D=dis[u];}for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa)continue;dis[v]=dis[u]+1;dfs2(v,u);}
}
ll mdis[N],num[N];
//以中点为根,节点i向下能走最长距离及其数量
ll V[N];
void dp(ll u,ll fa)
{mdis[u]=1,num[u]=1;for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa)continue;dp(v,u);if(mdis[u]<mdis[v]+1){mdis[u]=mdis[v]+1;num[u]=num[v]; }else if(mdis[u]==mdis[v]+1)num[u]+=num[v];}
}
ll ans;
void dfs3(ll u,ll fa,ll w)
{if(k==1)ans=(ans+w*num[u]%mod)%mod;else ans=(ans+w*num[u]%mod*w%mod*num[u]%mod)%mod;for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa||mdis[u]!=mdis[v]+1)continue;//并非长链 dfs3(v,u,w);}
}
int main()
{scanf("%lld%lld",&n,&k);if(n==1){puts("0");return 0;}if(n==2){puts("2");return 0;}for(int i=1;i<n;i++){ll u,v;scanf("%lld%lld",&u,&v);addedge(u,v);addedge(v,u);}dfs1(1,0);dis[d1]=0;dfs2(d1,0);ll Mid=d2;for(int i=1;i<=(D+1)/2;i++)Mid=fat[Mid];dp(Mid,0);ll cnt1=0,cnt2=0;for(int i=head[Mid];i;i=e[i].next){ll v=e[i].to;if(mdis[v]==D/2)cnt1+=num[v];else if(mdis[v]>D/2)cnt2+=num[v];}
// cout<<cnt1<<" "<<cnt2<<endl;
// 分类讨论Midll VMid=0;if(D&1){for(int i=head[Mid];i;i=e[i].next){ll v=e[i].to;if(mdis[v]>D/2)V[v]=cnt1;else if(mdis[v]==D/2)V[v]=cnt2;}VMid=cnt1*cnt2%mod;}else //中点有两个 {for(int i=head[Mid];i;i=e[i].next){ll v=e[i].to;if(mdis[v]==D/2)V[v]=cnt1-num[v];}ll tem=cnt1;for(int i=head[Mid];i;i=e[i].next){ll v=e[i].to;if(mdis[v]==D/2){tem-=num[v];VMid=(VMid+tem*num[v]%mod)%mod; }}}//计算Mid外其他节点for(int i=head[Mid];i;i=e[i].next){ll v=e[i].to;if(mdis[v]>=D/2)dfs3(v,Mid,V[v]);}if(k==1)ans=(ans+VMid)%mod;else ans=(ans+VMid*VMid%mod)%mod;printf("%lld",ans);return 0;
}
2.SMOJ 任务兼容
题意
你有一个由 n n n 个任务组成的列表,每个任务有一个对应的难度值。你需要将这些任务划分为若干个组,每组至少包含两个任务。划分需要满足以下条件:
-
每组任务必须是连续的。第一组任务必须从第一个任务开始,最后一组任务必须以最后一个任务结束。
-
相邻的组之间必须连续,即第 i 组的最后一个任务的下一个任务必须是第 i+1 组的第一个任务。
-
每组内的每个任务必须至少有一个其他任务与它“兼容”。两个任务兼容的条件是它们的难度值的最大公约数不为 1 1 1。
你的目标是找到满足条件的最大组数。如果无法满足条件,则输出 − 1 -1 −1。
思路
感觉对了,但只有 89pts \text{89pts} 89pts 捏。
考虑 Θ ( n 2 ) \Theta(n^2) Θ(n2) 做法,设一维状态: f i f_i fi 表示最后一组以 i i i 结尾的最大组数,我们要枚举一个 j j j 使 j ∼ i j\sim i j∼i 成为一组,然后 f i = max { f j − 1 } + 1 f_i=\max\{f_{j-1}\}+1 fi=max{fj−1}+1。
我们考虑怎么 Θ ( 1 ) \Theta(1) Θ(1) 判定 j ∼ i j\sim i j∼i 可以成为一组。
我们 Θ ( n 2 ) \Theta(n^2) Θ(n2) 算出每个数左右最近的非互质数的位置,记作 p r e i , n x t i pre_i,nxt_i prei,nxti。对于左端点 j j j,如果 n x t j < i nxt_j<i nxtj<i,那么需要有 p r e j pre_j prej 出现。其实这是改变了新组的最右左端点。考虑更新这个东西,记作 m i mi mi,更新后如果 j ≤ m i j\le mi j≤mi,那么 j ∼ i j\sim i j∼i 可以成为新的一组。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2003,inf=0x3f3f3f3f;
ll n,k,a[N];
ll pre[N],nxt[N];
ll f[N];
int main()
{scanf("%lld",&n);if(n==1){puts("-1");return 0;}for(int i=1;i<=n;i++)scanf("%lld",&a[i]),f[i]=-inf;for(int i=1;i<=n;i++){pre[i]=-inf;nxt[i]=inf;for(int j=i-1;j>=0;j--){if(__gcd(a[i],a[j])>1){pre[i]=j;break;}}for(int j=i+1;j<=n;j++){if(__gcd(a[i],a[j])>1){nxt[i]=j;break;}}}
// for(int i=1;i<=n;i++)
// cout<<i<<" "<<pre[i]<<" "<<nxt[i]<<endl; for(int i=1;i<=n;i++){ll mi=inf;for(int j=i;j>=1;j--){if(nxt[j]>i)mi=min(mi,pre[j]);if(i!=1&&mi>=j)f[i]=max(f[i],f[j-1]+1)/*,cout<<"f["<<i<<"] <= f["<<j-1<<"]+1="<<f[j-1]+1<<endl*/; }// cout<<f[i]<<" ";}
// for(int i=1;i<=n;i++)
// cout<<f[i]<<" ";if(f[n]==-inf)puts("-1");else printf("%lld",f[n]);return 0;
}
3.CF1515E Phoenix and Computers
题意
3 ≤ n ≤ 400 3\le n\le 400 3≤n≤400, M ∈ [ 10 8 , 10 9 ] M\in[10^8,10^9] M∈[108,109]。
思路
一道好玩的计数题。
我们观察左右电脑打开后,这些电脑的打开方式:
- 一段电脑 1 ∼ A 1 − 1 1\sim A_1-1 1∼A1−1 被手动打开, A 1 A_1 A1 被自动打开;
- A 1 + 1 ∼ A 2 − 1 A_1+1\sim A_2-1 A1+1∼A2−1 被手动打开, A 2 A_2 A2 被自动打开;
- ……;
- A m − 1 + 1 ∼ A m − 1 A_{m-1}+1\sim A_m-1 Am−1+1∼Am−1 被手动打开, A m A_m Am 被自动打开;
- A m + 1 ∼ n A_m+1\sim n Am+1∼n 被手动打开( A m + 1 ≤ n A_m+1\le n Am+1≤n)。
简要地说,就是一段被手动打开,接着一个被自动打开的,再接一段手动打开的。
我们设连续 k k k 个全都手动打开的方案数为 g k g_k gk, f i , j f_{i,j} fi,j 表示前 i i i 台电脑手动打开了 j j j 台的方案数,我们考虑枚举一段有 k k k 台手动打开的电脑,最前面有一台自动打开的,那么将从 f i − k − 1 , j − k f_{i-k-1,j-k} fi−k−1,j−k 转移过来。
在所有连续打开的 j j j 台中,选择将要手动打开的 k k k 台,即 ( j k ) \dbinom{j}{k} (kj);然后开 k k k 台方案数为 g k g_k gk,那么有转移方程:
f i , j ← f i − k − 1 , j − k × ( j k ) × g k f_{i,j}\leftarrow f_{i-k-1,j-k}\times\binom{j}{k}\times g_k fi,j←fi−k−1,j−k×(kj)×gk
答案就是 ∑ i = 1 n f n , i \displaystyle\sum_{i=1}^n f_{n,i} i=1∑nfn,i。
我们现在再来考虑 g k g_k gk 怎么算。我们枚举中间开了哪一台,然后向两边扩展;从 x x x 开始开,对于 x x x 右边的电脑, 它们的相对开机顺序必须是 x + 1 , x + 2 , . . . , n x+1,x+2,...,n x+1,x+2,...,n 对于 x x x 左边的电脑,它们的相对开机顺序必须是 x − 1 , x − 2 , . . . , 1 x−1,x−2,...,1 x−1,x−2,...,1。两边的开机顺序是可以穿插(合并)在一起的。即:
g k = ∑ i = 1 k ( k − 1 k − i ) g_k=\sum_{i=1}^k \binom{k-1}{k-i} gk=i=1∑k(k−ik−1)
记得勤取模。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=405,inf=0x3f3f3f3f;
ll n,mod;
ll f[N][N],g[N];
ll C[N][N];
void init()
{for(int i=0;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()
{scanf("%lld%lld",&n,&mod);init();for(int i=1;i<=n;i++)for(int j=1;j<=i;j++)g[i]=(g[i]+C[i-1][i-j])%mod;for(int i=1;i<=n;i++)//前i台电脑 {f[i][i]=g[i];for(int j=1;j<i;j++)//手动开j台 for(int k=1;k<j;k++)//一段k台被手动打开 f[i][j]=(f[i][j]+f[i-k-1][j-k]*g[k]%mod*C[j][k]%mod)%mod;}ll ans=0;for(int i=1;i<=n;i++)ans=(ans+f[n][i])%mod;printf("%lld",ans);return 0;
}
4.洛谷 P3976 TJOI2015 旅游
题意
P3976 [TJOI2015] 旅游
题目描述
为了提高智商,ZJY 准备去往一个新世界去旅游。这个世界的城市布局像一棵树,每两座城市之间只有一条路径可以互达。
每座城市都有一种宝石,有一定的价格。ZJY 为了赚取最高利益,她会选择从 A 城市买入再转手卖到 B 城市。
由于ZJY买宝石时经常卖萌,因而凡是 ZJY 路过的城市,这座城市的宝石价格会上涨。让我们来算算 ZJY 旅游完之后能够赚取的最大利润。(如 A 城市宝石价格为 v v v,则ZJY出售价格也为 v v v)
1 ≤ n , q ≤ 5 × 10 4 1\le n,q \le 5\times 10^4 1≤n,q≤5×104,在任何时刻任何城市的宝石价格都不超过 10 9 10^9 109。