2022/10/15 总结
写在最前面
个人认为这次考试的时间安排比较合理:花 \(30min\) 写完暴力思考第一题无果后开始写第二题,花费 \(90min\) 写完调完,并且写了另一份暴力和数据生成器,完整对拍过,第三题写了暴力,第四题试图读懂题意无果后猜了 \(2\) 的结论。做完以上一切后距离考试结束还有大约 \(30min\)。
问题在于第一题其实想到了离线,但没有想到后面的题意转化等,所以没有写出正解;第三题对基环树还不是很熟练,没有想到。
万松园
题目描述
题目描述
万松园地处繁华。其南临武广,东倚中山公园的得天独厚的位置,让生活在其中的人们习惯于四处游玩。然而 \(2019\) 年底疫情的到来,政府不得不采取封控的措施,以抵制疫情的蔓延。但这与万松园居民的习惯背道而驰,直接推行阻力太大。疫情才刚刚开始,卫健委正考虑一种折中的措施:
具体而言,万松园可以看作一颗树,树上有 \(n\) 个节点。调查显示,不同的路径有不同的“受欢迎程度”,一个道路的受欢迎程度越小,其封控的成本越低。由于封控就是让一个人与尽量少的其他人接触,只要将这棵树划分为一些较小的连通块就可以较为轻松地达到封控的目的。现在你要为卫健委写一个程序,支持查询当封控所有“受欢迎程度”低于 \(K\) 的道路时,点 \(v\) 能到达的其他节点数量。
输入格式
第一行输入两个正整数 \(n,q(1≤n,q≤10^5)\),表示图中节点的个数和查询的次数。
之后 \(n-1\) 行,每行三个整数 \(u,v,w\),表示有一条 \(u,v\) 间的路径,“受欢迎程度”为 \(w(1≤u,v≤n,1≤w≤10^9)\)。
之后 \(q\) 行描述了卫健委的 \(q\) 次查询。每行输入两个整数 \(k_i,v_i\),表示当 \(K=k_i\) 时,查询点 \(v_i\) 能到达的其他节点数量。\((1≤k_i≤10^9,1≤v_i≤n)\)
输出格式
输出共 \(q\) 行,对于每次查询,输出一行一个整数表示答案。
提示
对于 \(10\%\) 的数据,\(1≤n,q≤5\);
对于 \(30\%\) 的数据,\(1≤n,q≤1000\);
对于 \(100\%\) 的数据,\(1≤n,q≤10^5,1≤u,v≤n,1≤w≤10^9,1≤k_i≤10^9,1≤v_i≤n\)。
Solution
-
考虑把询问离线。如果按照 \(k_i\) 的值从大到小排个序再询问的话,题目就转化为了一个动态加边询问连通块大小的问题,容易想到用带 \(siz\) 的并查集解决。
-
如果要求在线,则是 \(\mathtt{Kruskal}\) 重构树。
AC code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
const int N=1e5+10;
int n,q;
int ans[N];
int f[N],siz[N];
struct memr{
int u,v,w;
bool operator<(const memr &_)const{
return w>_.w;
}
}e[N];
struct Q{
int id,k,v;
bool operator<(const Q &_)const{
return k>_.k;
}
}qr[N];
int get(int x){
if(f[x]==x) return x;
return f[x]=get(f[x]);
}
void merge(int x,int y){
siz[get(y)]+=siz[get(x)];
siz[get(x)]=0;
f[get(x)]=get(y);
return ;
}
int main(){
n=read(),q=read();
for(int i=1;i<n;++i)
e[i].u=read(),e[i].v=read(),e[i].w=read();
sort(e+1,e+n);
for(int i=1;i<=q;++i)
qr[i].k=read(),qr[i].v=read(),qr[i].id=i;
sort(qr+1,qr+q+1);
int low=0x3f3f3f3f;
int r=0;
for(int i=1;i<=n;++i)
f[i]=i,siz[i]=1;
for(int i=1;i<=q;++i){
if(qr[i].k<low){
while(r<n-1 && e[r+1].w>=qr[i].k){
++r;
merge(e[r].u,e[r].v);
}
}
ans[qr[i].id]=siz[get(qr[i].v)]-1;
}
for(int i=1;i<=q;++i)
printf("%d\n",ans[i]);
return 0;
}
K进制
题目描述
题目描述
你现在在学习二进制,但是你觉得二进制实在是太简单了。看着一道书本上的例题,你决定将它拓展到 \(k\) 进制。
现在给出一个长度为 \(n\) 的 \(k\) 进制数,可能含有前导 0,你需要实现以下 4 种操作,共 \(m\) 次。
1 x y
:将第 \(x\) 位上的数改为 \(y\);
2 l r
:将第 \(l\) 位到第 \(r\) 位升序排列;
3 l r
:将第 \(l\) 位到第 \(r\) 位降序排列;
4 l r
:求第 \(l\) 位到第 \(r\) 位所组成的 \(k\) 进制数转为 10 进制数的结果,结果对 998244353 取模。
输入格式
第一行输入三个正整数 \(n,m,k (n,m≤50000,2≤k≤10)\)。
第二行输入一个正整数,表示需要操作的 \(k\) 进制数。
接下来 \(m\) 行,每行三个正整数,描述一个操作。
输出格式
对于每个操作 4,输出一行一个正整数表示答案。
提示
对于 \(5\%\) 的数据,\(n,m≤10\);
对于 \(10\%\) 的数据,\(n,m≤1000\);
另有 \(20\%\) 的数据,\(k=2\);
对于 \(100\%\) 的数据,\(n,m≤50000,2≤k≤10,1≤x≤n,0≤y≤k−1,1≤l≤r≤n\)。
Solution
-
看数据范围和区间操作,很容易想到线段树。
-
设线段树上每个节点存储的值为这个点表示的区间放在整个 \(n\) 位的 \(k\) 进制数中的大小,预处理一个 \(\mathtt{mul_i}\) 数组表示 \(\pmod {998244353}\) 意义下 \(k^i\),一个 \(\mathtt{inv_i}\) 数组表示 \(k^i\) 的逆元,一个 \(\mathtt{sum_i}\) 表示 \(\mathtt{mul_{0\sim i}}\) 的和(注意为了方便运算,\(\mathtt{mul}\) 和 \(\mathtt{inv}\) 数组都是倒序存储)
-
另外,对于区间排序,考虑在每个节点记录一个桶(\(\mathtt{num_p}\))。
-
需要的操作:
- \(\mathtt{mix(p,x,v)}\):单点修改,把第 \(x\) 位的值改成 \(v\)。注意要乘以这一位对应的 \(k^i\)(\(\mathtt{mul}\));(对应操作 \(1\))
- \(\mathtt{ask(p,l,r)}\):区间查询,查询 \(l\sim r\) 区间的和,由于是作为单独的一个数,所以要乘以 \(r\) 对应的 \(\mathtt{inv}\);(对应操作 \(4\))
- \(\mathtt{change(l,r,x)}\):区间排序,其中 \(x\) 表示升序或降序。需要执行的操作为 \(\mathtt{countup(l,r)}\) 和 \(\mathtt{down(l,r,x)}\);(对应操作 \(2\) 和 \(3\))
- \(\mathtt{countup(l,r)}\):统计区间内的桶。
- \(\mathtt{down(l,r,x)}\):按照 \(x\) 对应的规则以统计出来的桶为基础划分到线段树的段上进行重新赋值;
- \(\mathtt{push(p,a[],x)}\):每一段重新赋值的具体实现。先将整段归零,按照升序或降序的规则扫描已知的桶 \(a[]\),并且重新计算区间和。\(\mathtt{sum_i}\) 在这里用于辅助存在连续多个相同值时的快速计算;
- \(\mathtt{pushdown(p)}\):向下传递标记。具体通过 \(\mathtt{push()}\) 操作实现;
AC code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
#define ll long long
const int mod=998244353;
const int N=5e4+10;
int n,m,k;
int mul[N],inv[N],sum[N];
string st;
struct Add{
int num[10];
Add operator+(const Add &_)const{
Add cnt;
for(int i=0;i<=k;++i)
cnt.num[i]=num[i]+_.num[i];
return cnt;
}
};
struct memr{
int l,r,sum;
Add a;
int tg;
}tr[N<<3];
ll ksm(int x,int y){
ll cnt=1,dx=x;
for(;y;y>>=1,(dx*=dx)%=mod)
if(y&1)
(cnt*=dx)%=mod;
return cnt;
}
void pushup(int p){
tr[p].sum=(0ll+tr[p<<1].sum+tr[p<<1|1].sum)%mod;
tr[p].a=tr[p<<1].a+tr[p<<1|1].a;
return ;
}
void push(int p,Add &a,int x){
memset(tr[p].a.num,0,sizeof(tr[p].a.num));
tr[p].sum=0;
int dx=tr[p].r-tr[p].l+1,d=0;
for(int i=(x==1)?0:k-1;i>-1 &&i<k && dx;i+=x){
if(!a.num[i]) continue;
if(a.num[i]<=dx){
tr[p].a.num[i]=a.num[i];
dx-=a.num[i];
d=a.num[i];
a.num[i]=0;
}
else{
tr[p].a.num[i]=dx;
a.num[i]-=dx;
d=dx;
dx=0;
}
(tr[p].sum+=1ll*sum[d]*i*mul[tr[p].r-dx]%mod)%=mod;
}
tr[p].tg=x;
return ;
}
void pushdown(int p){
if(!tr[p].tg) return ;
Add cnt=tr[p].a;
push(p<<1,cnt,tr[p].tg);
push(p<<1|1,cnt,tr[p].tg);
tr[p].tg=0;
return ;
}
void mix(int p,int x,int v){
if(tr[p].l==x && x==tr[p].r){
memset(tr[p].a.num,0,sizeof(tr[p].a.num));
tr[p].sum=(1ll*v*mul[x])%mod;
++tr[p].a.num[v];
return ;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(x<=mid) mix(p<<1,x,v);
else mix(p<<1|1,x,v);
pushup(p);
return ;
}
Add ans;
void countup(int p,int l,int r){
if(l<=tr[p].l && tr[p].r<=r){
ans=ans+tr[p].a;
return ;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) countup(p<<1,l,r);
if(mid<r) countup(p<<1|1,l,r);
pushup(p);
return ;
}
void down(int p,int l,int r,int x){
if(l<=tr[p].l && tr[p].r<=r){
push(p,ans,x);
tr[p].tg=x;
return ;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) down(p<<1,l,r,x);
if(mid<r) down(p<<1|1,l,r,x);
pushup(p);
return ;
}
void change(int l,int r,int x){
memset(ans.num,0,sizeof(ans.num));
countup(1,l,r);
down(1,l,r,x);
return ;
}
int ask(int p,int l,int r){
if(l<=tr[p].l && tr[p].r<=r){
return tr[p].sum;
}
pushdown(p);
pushup(p);
int mid=(tr[p].l+tr[p].r)>>1;
int cnt=0;
if(l<=mid) (cnt+=0ll+ask(p<<1,l,r))%=mod;
if(mid<r) (cnt+=0ll+ask(p<<1|1,l,r))%=mod;
return cnt;
}
void build(int p,int l,int r){
tr[p].l=l,tr[p].r=r;
tr[p].tg=0;
if(l==r){
memset(tr[p].a.num,0,sizeof(tr[p].a.num));
tr[p].sum=1ll*int(st[l-1]-'0')*mul[l]%mod;
++tr[p].a.num[int(st[l-1]-'0')];
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
return ;
}
void pre(){
mul[n]=inv[n]=1;
for(int i=n-1;i;--i){
mul[i]=(1ll*mul[i+1]*k)%mod;
inv[i]=ksm(mul[i],mod-2);
}
for(int i=1;i<=n;++i)
sum[i]=(0ll+sum[i-1]+mul[n-i+1])%mod;
return ;
}
int main(){
// freopen("ksystem.in","r",stdin);
// freopen("ksystem.out","w",stdout);
n=read(),m=read(),k=read();
pre();
cin>>st;
build(1,1,n);
int opt,x,y;
while(m--){
opt=read(),x=read(),y=read();
if(opt==1)
mix(1,x,y);
else if(opt==2)
change(x,y,1);
else if(opt==3)
change(x,y,-1);
else printf("%d\n",1ll*ask(1,x,y)*inv[y]%mod);
}
return 0;
}