长乐集训 Day4
A.sequence
题意
给定一个长度为 $n$ 的序列,序列中第 $i$ 个数为 $a_i$。对于该序列的一个连续子序列 $a_l,a_{l+1},\dots,a_r$,定义其价值为 $a_l \bmod a_{l+1} \bmod \dots \bmod a_r$。
求这个序列的所有连续子序列价值之和。
Solution
因为一次有效的运算至少会使当前值减半,因此 $a_l,a_{l+1},\dots,a_r$ 中有效的运算不超过 $\log n$ 次。当右端点固定时可以按照左端点答案是否相同进行划分,最多会划分 $\log n$ 个区间,每段答案相同。
可以发现本题中每次的查找具有可二分性,可以使用 ST 表维护。
或者直接用线段树维护区间取模,然后直接查询 $[1,i]$ 的 $sum$ 即可。
复杂度 $\mathcal{O}(n\log n\log V)$,$V$ 为值域,且常数极小。
Code
#include<bits/stdc++.h>
inline int read()
{
int res=0,flag=1;
char ch=getchar();
while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
return res*flag;
}
struct node
{
int max;
long long sum;
};
int n;
long long ans;
int val[1000010];
struct node nd[4000010];
void pushup(int id)
{
nd[id].max=std::max(nd[id<<1].max,nd[id<<1|1].max);
nd[id].sum=nd[id<<1].sum+nd[id<<1|1].sum;
return ;
}
void build(int left,int right,int id)
{
if(left==right)
{
nd[id].sum=val[left];
nd[id].max=val[left];
return ;
}
int mid=(left+right)>>1;
build(left,mid,id<<1);
build(mid+1,right,id<<1|1);
pushup(id);
return ;
}
void modify(int left,int right,int id,int start,int end,int data)
{
if(end<left||right<start)
return ;
if(nd[id].max<data)
return ;
if(left==right)
{
nd[id].max%=data;
nd[id].sum%=data;
return ;
}
int mid=(left+right)>>1;
modify(left,mid,id<<1,start,end,data);
modify(mid+1,right,id<<1|1,start,end,data);
pushup(id);
return ;
}
long long query(int left,int right,int id,int start,int end)
{
if(end<left||right<start)
return 0;
if(start<=left&&right<=end)
return nd[id].sum;
int mid=(left+right)>>1;
long long ansl=query(left,mid,id<<1,start,end);
long long ansr=query(mid+1,right,id<<1|1,start,end);
return ansl+ansr;
}
int main(int argc,const char *argv[])
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
n=read();
for(int i=1;i<=n;i++)
val[i]=read();
build(1,n,1);
for(int i=1;i<=n;i++)
{
if(i!=1)
modify(1,n,1,1,i-1,val[i]);
ans+=query(1,n,1,1,i);
//std::cout<<query(1,n,1,1,i)<<std::endl;
}
printf("%lld",ans);
return 0;
}
B.gloomy
题意
Solution
传递忧郁值的过程并不重要,只需要最后每个人的忧郁值都为 $0$ 即可。
将传递忧郁值作为图来考虑,我们发现一棵树是一定足够的,那么 $k$ 个人最多只需要 $k-1$ 次操作就好了。
如果 $k$ 个人可以只通过 $k-1$ 次操作就完成目标,那么意味着我们可以将 $k$ 个人分成两组,每组内的结余为 $0$。因此,如果我们可以将 $n$ 个人分成 $i$ 个结余为 $0$ 的组,我们就可以通过 $n-i$ 次操作完成目标。
发现 $n$ 很小,考虑状压 DP。设 $f_S$ 表示当前选人的状态为 $S$ 时最多能分成多少个结余为 $0$ 的组,转移方程很简单,最终答案为 $n-f_{2^n-1}$。
时间复杂度 $\mathcal{O}(n\times 2^n)$。
Code
C.tree
题意
给定一棵 $n$ 个结点的无根树,每条边的边权均为 $1$。
树上标记有 $m$ 个互不相同的关键点,在这 $m$ 个点中等概率随机地选择 $k$ 个不同的点。求经过所有选中的的 $k$ 个点的最短路径长度的期望是多少。注意,可以任意选取起点和终点,路径也可以经过重复的点或重复的边。
Solution
假设回到起点,那么路径长度就是用这 $k$ 个点建的虚树的边长度之和乘 $2$ ,那么减去虚树的最长链就是最短路径长度了。 所求的期望就可以转化为求虚树边长之和的期望和虚树直径的期望。
求边长之和的期望只要枚举每条边,它出现在虚树的概率就是这条边两边都取关键点的概率。
求虚树直径的期望可以暴力一点。钦点一棵树中的某个点对 $(u,v)$ 为直径,当且仅当 $dis(u,v)$ 最长且字典序最小,并且钦定 $u\lt v$,那么直径是唯一的。枚举所有满足 $u\lt v$ 的点对 $(u,v)$,考虑这条路径是直径的概率。
首先,考虑点对 $(u,w)\ (w\ne v)$ 和 $(v,w)\ (w\ne u)$ 的影响,即先保证不会出现其他以 $u$ 或 $v$ 的端点的路径为直径。那么我们发现,对于一个点 ,它不能出现当且仅当下面几种条件之一被满足:
- $dis(u,v)\lt dis(u,w)$
- $dis(u,v)=dis(v,w),x<u$
- $dis(u,v)<dis(v,w)$
- $dis(u,v)=dis(u,w),x<v$
不难发现,假设存在 $(x,y)$ 比 $(u,v)$ 更优,并且这两条路径端点不重合,那么 $x$ 或 $y$ 一定会满足上面几个条件之一。所以这样我们也考虑到了其他点对的情况。因此上面的条件是充分且必要的。
我们数出能出现的点数 $cnt$,算概率就是其他 $k-2$ 个被选的点都落在 $cnt$ 个里面的概率。
Code
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int res=0,flag=1;
char ch=getchar();
while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
return res*flag;
}
const long long mod=998244353;
struct edge
{
int to,nxt;
};
int n,m,k,tot;
long long ans;
struct edge ed[4010];
int pos[2010];
long long dis[2010][2010],num[2010][2010];
int head[2010],size[2010];
inline void add_edge(int fr,int to)
{
ed[++tot]=(edge){to,head[fr]};
head[fr]=tot;
return ;
}
long long quick(long long a,int b)
{
long long res=1;
while(b!=0)
{
if(b&1==true)
res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
void init(int fr,int fa)
{
for(int i=head[fr];i!=0;i=ed[i].nxt)
{
int to=ed[i].to;
if(fa==to)
continue;
init(to,fr);
size[fr]+=size[to];
}
if(fa!=0)
ans=(ans+num[m][k]-num[size[fr]][k]-num[m-size[fr]][k]+mod)%mod;
return ;
}
void dfs(int fr,int fa,int root)
{
for(int i=head[fr];i!=0;i=ed[i].nxt)
{
int to=ed[i].to;
if(fa==to)
continue;
dis[root][to]=dis[root][fr]+1;
dfs(to,fr,root);
}
return ;
}
inline void input()
{
n=read(),m=read(),k=read();
for(int i=0;i<=m;i++)
{
num[i][0]=1;
for(int j=1;j<=i;j++)
num[i][j]=(num[i-1][j-1]+num[i-1][j])%mod;
}
for(int i=1;i<=m;i++)
{
pos[i]=read();
size[pos[i]]=1;
}
for(int i=1;i<n;i++)
{
int fr=read(),to=read();
add_edge(fr,to);
add_edge(to,fr);
}
return ;
}
void solve()
{
input();
init(1,0);
ans=ans*2%mod;
for(int i=1;i<=m;i++)
dfs(pos[i],0,pos[i]);
for(int i=1;i<=m;i++)
{
for(int j=i+1;j<=m;j++)
{
int cnt=0;
for(int l=1;l<=m;l++)
{
if(i==l||j==l) continue;
if(dis[pos[i]][pos[l]]>dis[pos[i]][pos[j]]) continue;
if(dis[pos[j]][pos[l]]>dis[pos[i]][pos[j]]) continue;
if(dis[pos[i]][pos[l]]==dis[pos[i]][pos[j]]&&l<j) continue;
if(dis[pos[j]][pos[l]]==dis[pos[i]][pos[j]]&&l<i) continue;
cnt++;
}
ans=(ans-(long long)dis[pos[i]][pos[j]]*num[cnt][k-2]%mod+mod)%mod;
}
}
printf("%lld",ans*quick(num[m][k],mod-2)%mod);
return ;
}
int main(int argc,const char *argv[])
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
solve();
return 0;
}
D.Jumping sequence
题意
Solution
Code
result
感觉 A 题很像做过的 P8421 [THUPC2022 决赛] rsraogps 然后暴冲了一发(赛后发现很 saber),B 题一眼状压然而我状压并不 ok 故跳过先往后做(赛后发现很 saber),C 题想了半天然后发现就是虚树的期望边数和直径期望长度乱搞一下就过了,D 题随便写了个 dfs。
期望得分 24+0+100+50=180pts。
然后狠狠挂分了 24+10+5+20=59pts。
T3 数组开小了,改了一下就过了,蚌。

浙公网安备 33010602011771号