这周主要讲了一些根号算法。
1126 骆源的哈夫曼树
题意:给出
解析:就是裸的哈夫曼树题,具体实现方式就是贪心,将较小权值的数据放到更深的地方。首先将所有的权值加入小根堆中,选出最小的
代码:
//
// Created by 屋顶上的小丑 on 2023/3/26.
//
#include<cstdio>
#include<iostream>
const int maxn=1e5;
const int maxm=1e3;
int n,m,cnt;
int heap[(maxn<<1)+5],f[maxn+5];
long long ans;
void add(long long x)
{
heap[++cnt]=x;
int now=cnt;
while(now>1)
{
int nxt=now>>1;
if(heap[nxt]>heap[now])
std::swap(heap[nxt],heap[now]);
else
return;
now=nxt;
}
}
void del()
{
std::swap(heap[cnt],heap[1]);
cnt--;
int now=1;
while((now<<1)<=cnt)
{
int nxt=now<<1;
if(nxt+1<=cnt&&heap[nxt]>heap[nxt+1])
nxt++;
if(heap[nxt]<heap[now])
std::swap(heap[nxt],heap[now]);
else
return;
now=nxt;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&f[i]);
add(f[i]);
}
while((n-1)%(m-1))
{
add(0ll);
n++;
}
while(cnt>=m)
{
int t=m;
long long now=0;
while(t--)
{
now+=heap[1];
del();
}
ans+=now;
add(now);
}
printf("%lld",ans);
return 0;
}
1822 某科学的加密算法
题意:有
公钥加密算法如下:
- 密钥生成流程
- 随机生成一个
比特的素数 ,满足 ,其中 也是素数; - 找到
的一个原根 ,令 ; - 均匀随机生成一个
的整数 ,令 ; - 生成公钥
,私钥 。
- 随机生成一个
- 加密流程(明文
满足存在 , ) - 均匀随机生成一个
的整数 ; - 令
,密文即为 。
- 均匀随机生成一个
解析:裸的 BSGS 题。
易知
又因为
代码:
//
// Created by 屋顶上的小丑 on 2023/3/27.
//
#include<cstdio>
#include<cmath>
const int mod=1e6+7;
int Q,T;
long long q,g,y;
long long c1,c2;
struct hash_map
{
struct data
{
long long u;
int v;
int nex;
}e[(mod<<1)+5];
int head[mod+5],cnt,tim[(mod<<1)+5];
int hash(long long u) const { return u%mod; }
int find(long long u) const
{
int fir=hash(u);
for(int i=head[fir];i;i=e[i].nex)
if(e[i].u==u)
return e[i].v;
return -1;
}
int& operator[](long long u)
{
int fir=hash(u);
for(int i=head[fir];i;i=e[i].nex)
if(e[i].u==u)
return e[i].v;
e[++cnt]=(data){u,-1,head[fir]};
tim[cnt]=fir;
head[fir]=cnt;
return e[cnt].v;
}
void clear()
{
for(int i=1;i<=cnt;i++)
head[tim[i]]=0;
cnt=0;
}
};
long long power(long long a,long long b,long long p)
{
long long ans=1;
for(;b;b>>=1)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans%p;
}
hash_map hash;
long long BSGS(long long a,long long b,long long p)
{
hash.clear();
b%=p;
if(!a)
{
if(!b)
return 1;
else
return -1;
}
int t=sqrt(p)+1;
long long val=b;
hash[val]=0;
for(int j=1;j<t;j++)
{
val=val*a%p;
hash[val]=j;
}
long long num=power(a,t,p)%p;
val=1;
for(int i=1;i<=t;i++)
{
val=val*num%p;
int j=hash.find(val);
if(j>=0&&i*t-j>=0)
return i*t-j;
}
return -1;
}
int main()
{
scanf("%d",&Q);
while(Q--)
{
scanf("%lld%lld%lld%d",&q,&g,&y,&T);
long long p=(q<<1)+1;
long long x=BSGS(g,y,p);
while(T--)
{
scanf("%lld%lld",&c1,&c2);
printf("%lld ",c2*power(c1,x*(p-2)%(p-1),p)%p);
}
printf("\n");
}
return 0;
}
1823 条件糖果甜度问题
题意:有一个长度为
解析:原题大概是这个[AHOI2013] 作业。
非常典型的莫队题,我们能够维护
容易想到可以用一个权值树状数组来判断,每次如果有一个新的
考虑能不能把这个
代码:
//
// Created by 屋顶上的小丑 on 2023/3/26.
//
#include<cstdio>
#include<cmath>
const int maxn=5e4;
const int maxq=2.5e5;
int n,Q,len,maxx;
int s[maxn+5],bl[maxn+5];
int val[maxn+5],cnt[maxn+5];
int bel[maxn+5],ans[maxq+5];
struct ques
{
int l,r;
int a,b;
int id;
}q[maxq+5],tmp[maxq+5];
bool cmp(ques x,ques y)
{
if(bl[x.l]==bl[y.l])
{
if(bl[x.l]&1)
return x.r<y.r;
return x.r>y.r;
}
return x.l<y.l;
}
void merge_sort(ques* x,int l,int r)
{
if(l==r)
return ;
int mid=(l+r)>>1;
merge_sort(x,l,mid);
merge_sort(x,mid+1,r);
int s1=l,s2=mid+1,s3=0;
while(s1<=mid&&s2<=r)
{
if(cmp(q[s1],q[s2]))
{
tmp[++s3]=q[s1];
s1++;
}
else
{
tmp[++s3]=q[s2];
s2++;
}
}
for(;s1<=mid;s1++) tmp[++s3]=q[s1];
for(;s2<=r;s2++) tmp[++s3]=q[s2];
for(int i=l;i<=r;i++)
q[i]=tmp[i-l+1];
}
void add(int x)
{
val[s[x]]++;
if(val[s[x]]==1)
cnt[bel[s[x]]]++;
}
void del(int x)
{
val[s[x]]--;
if(!val[s[x]])
cnt[bel[s[x]]]--;
}
int query(int l,int r)
{
if(l>maxx) return 0;
int L=bel[l],R=bel[r],ret=0;
if(L==R)
{
for(int i=l;i<=r;i++)
ret+=(bool)val[i];
return ret;
}
for(int i=L+1;i<R;i++)
ret+=cnt[i];
for(int i=l;i<=maxx&&bel[i]==L;i++)
ret+=(bool)val[i];
for(int i=r;i>=1&&bel[i]==R;i--)
ret+=(bool)val[i];
return ret;
}
int main()
{
scanf("%d%d",&n,&Q);
len=n/sqrt(Q*2.0/3.0);
for(int i=1;i<=n;i++)
{
scanf("%d",&s[i]);
if(s[i]>maxx) maxx=s[i];
bl[i]=(i-1)/len+1;
}
len=sqrt(maxx);
if(!len) len=1;
for(int i=1;i<=maxx;i++)
bel[i]=(i-1)/len+1;
for(int i=1;i<=Q;i++)
{
scanf("%d%d%d%d",&q[i].l,&q[i].r,&q[i].a,&q[i].b);
if(q[i].b>maxx) q[i].b=maxx;
q[i].id=i;
}
merge_sort(q,1,Q);
int L=1,R=0;
for(int i=1;i<=Q;i++)
{
while(L>q[i].l) add(--L);
while(R<q[i].r) add(++R);
while(L<q[i].l) del(L++);
while(R>q[i].r) del(R--);
ans[q[i].id]=query(q[i].a,q[i].b);
}
for(int i=1;i<=Q;i++)
printf("%d\n",ans[i]);
return 0;
}
1824 动态调整的势函数
题意:有一棵
- 求出编号为
的节点的 之和,即 ; - 改变某个点的权值
。
解析:考虑分块求解。
对编号分块,维护
现在来到修改操作,显然对于散点,直接在树状数组上修改即可,但是对于整块则不太好处理。我们考虑修改
事实上,对于整块而言,我们并不关心其中哪些节点的 vector 能降到
依然可以用类似上一题的方法去掉这个
代码:
树状数组实现:
//
// Created by 屋顶上的小丑 on 2023/3/27.
//
#include<cstdio>
#include<cmath>
const int maxn=1e5;
const int maxlen=400;
int n,Q,l[maxn+5],r[maxn+5];
int len,bel[maxn+5],tot,root;
int num[maxn+5][maxlen+5],tim;
int head[maxn+5],cnt,w[maxn+5];
long long sum[maxn+5],tr[maxn+5];
long long siz[maxn+5];
struct node
{
int nex;
int to;
}e[(maxn<<1)+5];
void add(int from,int to)
{
e[++cnt].nex=head[from];
e[cnt].to=to;
head[from]=cnt;
}
int lowbit(int x)
{
return x&(-x);
}
void Add(int x,int val)
{
for(int i=x;i<=n;i+=lowbit(i))
tr[i]+=val;
}
long long Query(int x)
{
long long ret=0;
for(int i=x;i;i-=lowbit(i))
ret+=tr[i];
return ret;
}
void dfs(int u,int fa)
{
l[u]=++tim;
num[0][bel[u]]++;
siz[u]=w[u];
Add(l[u],w[u]);
for(int i=1;i<=tot;i++)
num[u][i]=num[0][i];
for(int i=head[u];i;i=e[i].nex)
{
int v=e[i].to;
if(v==fa)
continue;
dfs(v,u);
siz[u]+=siz[v];
}
r[u]=tim;
num[0][bel[u]]--;
sum[bel[u]]+=siz[u];
}
int main()
{
scanf("%d%d",&n,&Q);
len=sqrt(n);
tot=n/len+(n%len!=0);
for(int i=1;i<=n;i++)
{
scanf("%d",&w[i]);
bel[i]=(i-1)/len+1;
}
int u,v;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&u,&v);
if(!u)
root=v;
else
{
add(u,v);
add(v,u);
}
}
dfs(root,0);
int op,x,y;
for(int i=1;i<=Q;i++)
{
scanf("%d%d%d",&op,&x,&y);
if(op==1)
{
int delta=y-w[x];
for(int j=1;j<=tot;j++)
sum[j]+=1ll*num[x][j]*delta;
w[x]=y;
Add(l[x],delta);
}
else
{
long long ans=0;
int L=bel[x],R=bel[y];
if(L==R)
{
for(int j=x;j<=y;j++)
ans+=Query(r[j])-Query(l[j]-1);
}
else
{
for(int j=x;bel[j]==L;j++)
ans+=Query(r[j])-Query(l[j]-1);
for(int j=L+1;j<R;j++)
ans+=sum[j];
for(int j=y;bel[j]==R;j--)
ans+=Query(r[j])-Query(l[j]-1);
}
printf("%lld\n",ans);
}
}
return 0;
}
分块实现:
//
// Created by 屋顶上的小丑 on 2023/3/27.
//
#include<cstdio>
#include<cmath>
const int maxn=1e5;
const int maxlen=400;
int n,Q,l[maxn+5],r[maxn+5];
int len,bel[maxn+5],tot,root;
int num[maxn+5][maxlen+5],tim;
int head[maxn+5],cnt,w[maxn+5];
long long sum[maxn+5],siz[maxn+5];
long long s[maxn+5],tag[maxn+5];
struct node
{
int nex;
int to;
}e[(maxn<<1)+5];
void add(int from,int to)
{
e[++cnt].nex=head[from];
e[cnt].to=to;
head[from]=cnt;
}
void dfs(int u,int fa)
{
l[u]=++tim;
num[0][bel[u]]++;
siz[u]=s[tim]=w[u];
for(int i=1;i<=tot;i++)
num[u][i]=num[0][i];
for(int i=head[u];i;i=e[i].nex)
{
int v=e[i].to;
if(v==fa)
continue;
dfs(v,u);
siz[u]+=siz[v];
}
r[u]=tim;
num[0][bel[u]]--;
sum[bel[u]]+=siz[u];
}
int main()
{
scanf("%d%d",&n,&Q);
len=sqrt(n);
tot=n/len+(n%len!=0);
for(int i=1;i<=n;i++)
{
scanf("%d",&w[i]);
bel[i]=(i-1)/len+1;
}
int u,v;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&u,&v);
if(!u)
root=v;
else
{
add(u,v);
add(v,u);
}
}
dfs(root,0);
for(int i=1;i<=n;i++)
s[i]+=s[i-1];
int op,x,y;
for(int i=1;i<=Q;i++)
{
scanf("%d%d%d",&op,&x,&y);
if(op==1)
{
int delta=y-w[x];
for(int j=1;j<=tot;j++)
sum[j]+=1ll*num[x][j]*delta;
w[x]=y;
int L=l[x];
for(int j=L;bel[j]==bel[L];j++)
s[j]+=delta;
for(int j=bel[L]+1;j<=tot;j++)
tag[j]+=delta;
}
else
{
long long ans=0;
int L=bel[x],R=bel[y];
if(L==R)
{
for(int j=x;j<=y;j++)
ans+=s[r[j]]+tag[bel[r[j]]]-s[l[j]-1]-tag[bel[l[j]-1]];
}
else
{
for(int j=x;bel[j]==L;j++)
ans+=s[r[j]]+tag[bel[r[j]]]-s[l[j]-1]-tag[bel[l[j]-1]];
for(int j=L+1;j<R;j++)
ans+=sum[j];
for(int j=y;bel[j]==R;j--)
ans+=s[r[j]]+tag[bel[r[j]]]-s[l[j]-1]-tag[bel[l[j]-1]];
}
printf("%lld\n",ans);
}
}
return 0;
}

