8.1 2020牛客暑期多校训练营(第七场)题解及补题
8.1 2020牛客暑期多校训练营(第七场)题解及补题
比赛过程
D题开场算是还行,最后卡的只出1题,确实是有待提升
题解
B Mask Allocation
题意
解法
代码
//将内容替换成代码
C A National Pandemic
题意
给一个树,有m个操作:
(1)在x点处增加w,树上每个点y的值+=w-dis(x,y)。
(2)将x点处值和0取min
(3)查询x点的值
解法
本题需要用到树链剖分的知识,这个题也可以帮助我们学习一下这部分。
树链剖分实际上就是将一棵树划分成若干条链,用数据结构去维护每条链,复杂度为O(logN),比较常见的是轻重链剖分。
先回顾两个问题:
1,将树从x到y结点最短路径上所有节点的值都加上z
这是个模板题,树上差分可以以O(n+m)的优秀复杂度解决这个问题
2,求树从x到y结点最短路径上所有节点的值之和
lca水题,可以想到,dfs O(n)预处理每个节点的dis(即到根节点的最短路径长度)
然后对于每个询问,求出x,y两点的lca,利用lca的性质distance(x,y)=dis(x)+dis(y)-2*dis(lca)求出结果,时间复杂度O(mlogn+n)
现在来思考一个bug:如果刚才的两个问题结合起来,成为一道题的两种操作呢?
刚才的方法显然就不够优秀了(每次询问之前要跑dfs更新dis,这时就需要用到树链剖分。
明白了这些,这个题就很清楚了,本题实际上就是这两个问题的综合。
将每个点x 的值展开=w-dep(x)-dep(y)+2dep(lca)。
分成(w-dep(x)-(dep(y)),(2dep(lca))来算,
对于前俩部分可以在边操作时边统计出来,对于第三部分我们用树剖维护,若在x进行1操作,那么就在x与根节点路径上的所有点都加2,查询就查询出的x'到根节点路径和就是第三部分的值
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define lson root<<1,l,midd
#define rson root<<1|1,midd+1,r
const int inf=0x3f3f3f3f;
const ll INF=1e18;
const int M=2e5+5;
const int mod=998244353;
vector<int>g[M];
int n,m,tot,sz[M],fa[M],id[M],son[M],top[M],dep[M];
int tr[M<<2],lz[M<<2];
int lasans[M],lasans1[M],lasnum[M];
void dfs1(int u,int f){
sz[u]=1,fa[u]=f;
dep[u]=dep[f]+1;
for(auto v:g[u]){
if(v!=f){
dfs1(v,u);
sz[u]+=sz[v];
if(!son[u]||sz[v]>sz[son[u]])
son[u]=v;
}
}
}
void dfs2(int u,int tp){
id[u]=++tot,top[u]=tp;
if(son[u])
dfs2(son[u],tp);
for(auto v:g[u]){
if(v!=fa[u]&&v!=son[u])
dfs2(v,v);
}
}
void build(int root,int l,int r){
tr[root]=lz[root]=0;
if(l==r)return;
int midd=(l+r)>>1;
build(lson);
build(rson);
}
void up(int root){
tr[root]=tr[root<<1]+tr[root<<1|1];
}
void pushdown(int root,int l,int r){
if(lz[root]){
int midd=(l+r)>>1;
tr[root<<1]+=(midd-l+1)*2*lz[root];
tr[root<<1|1]+=(r-midd)*2*lz[root];
lz[root<<1]+=lz[root];
lz[root<<1|1]+=lz[root];
lz[root]=0;
return ;
}
}
void update(int L,int R,int root,int l,int r){
if(L<=l&&r<=R){
tr[root]+=(r-l+1)*2;
lz[root]++;
return ;
}
pushdown(root,l,r);
int midd=(l+r)>>1;
if(L<=midd)
update(L,R,lson);
if(R>midd)
update(L,R,rson);
up(root);
}
int query(int L,int R,int root,int l,int r){
///cout<<l<<"##"<<r<<endl;
if(L<=l&&r<=R){
return tr[root];
}
int midd=(l+r)>>1;
pushdown(root,l,r);
int res=0;
if(L<=midd)
res+=query(L,R,lson);
if(R>midd)
res+=query(L,R,rson);
return res;
}
void solve1(int u){
while(u){
update(id[top[u]],id[u],1,1,n);
u=fa[top[u]];
}
}
int solve2(int u){
int ans=0;
while(u){
ans+=query(id[top[u]],id[u],1,1,n);
u=fa[top[u]];
}
return ans;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
tot=0;
for(int i=0;i<=n;i++)
g[i].clear(),lasans[i]=lasans1[i]=lasnum[i]=0,son[i]=0;
for(int u,v,i=1;i<n;i++){
scanf("%d%d",&u,&v);
g[u].pb(v),g[v].pb(u);
}
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
int ans1=0;
int num=0;///1操作插入的点数
while(m--){
int op,x,w;
scanf("%d%d",&op,&x);
if(op==1){
scanf("%d",&w);
solve1(x);
ans1+=w-dep[x];
num++;
}
else if(op==2){
int ans2=solve2(x);
int ans=lasans[x]+(ans1-lasans1[x])-(num-lasnum[x])*dep[x]+ans2;
if(ans<=0)
lasans[x]=ans-ans2;///保持不变,所以保持前俩项
else
lasans[x]=-ans2;///全不要,而ans2是单独算的,所以在下次算的时候会抵消这次-的
lasans1[x]=ans1;
lasnum[x]=num;
}
else{
int ans2=solve2(x);
int ans=lasans[x]+(ans1-lasans1[x])-(num-lasnum[x])*dep[x]+ans2;
lasans[x]=ans-ans2;
lasans1[x]=ans1;
lasnum[x]=num;
printf("%d\n",ans);
}
}
}
return 0;
}
D Fake News
题意
解法
代码
//将内容替换成代码
H Dividing
题意
定义Legend Tuple 元组如下:
1 (1 , k )是一个Legend Tuple
2 如果(n,k)是一个Legend Tuple,那么(n+k,k)也是一个Legend Tuple
3 如果(n,k)是一个Legend Tuple,那么(nk,k)也是一个Legend Tuple
给出N , K 求出(1<=n<=N , 1<=k<=K),有多少个Legend Tuple:(n,k)
解法
找规律+数论分块,这个题其实并不难,打多校的时候也看出来很明显的分块,但是由于找规律找错了,所以一直wa到怀疑人生
规律就是对n进行一次除法分块,然后对n-1进行一次除法分块即可。
代码
#include<bits/stdc++.h>
#define ll long long
//1000000000000 1000000000000
using namespace std;
const ll mod=1e9+7;
ll solve(ll n,ll k){
ll l= 2,r;
ll tot=0;
while (l <= k){
if(n/l!=0)
r=min(n/(n/l),k);
else
r=k;
// cout<<r<<endl;
tot+=((n/l)%mod)*((r-l+1))%mod;
tot%=mod;
l=r+1;
}
return tot%mod;
}
ll get_num(ll n,ll k){
ll tot=1;
for(ll i=2;i*i<=n&&i<=k;++i){
if(n%i==0){
tot=(tot+1)%mod;
}
// cout<<i<<endl;
}
// cout<<"--"<<tot<<endl;
return (k%mod-tot-1+mod)%mod;
}
int main(){
ll n,k;
scanf("%lld%lld",&n,&k);
ll ans=0;
if(k>n){
ans=(ans+k%mod-n%mod)%mod;
k=n;
}
ans=(ans+n%mod)%mod;
ans=(ans+solve(n,k))%mod;
ans=(ans+solve(n-1,k)+k%mod-1+mod)%mod;
cout<<ans<<endl;
}
J Pointer Analysis
题意
这个题主要就是看懂题意,出题人也说了,这个题其实是个签到提,本身不太难。
给出指针与对象之间的赋值关系,求最后每个指针可能指向哪些对象,为了方便理解四种操作,提前在这里说一下,每个对象中是有 26 个变量的,这 26 个变量分别是 “对象指针” ,也是可以指向对象的,那么四种操作解释如下:
1 A = x:A 指针指向对象 x
2 A = B:A 指针指向指针 B 所指向的对象
3 A.f = B:A 指针所指向的对象的 f 指针指向 B 指针所指向的对象
4 A = B.f:A 指针指向 B 指针所指向的对象的 f 指针所指向的对象
解法
模拟即可。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=210;
char s1[N][5],s2[N][5];
int s[26][26][26];//s[对象][对象的指针][对象的指针的指针]
bool ans[26][26];//ans[指针][变量]
int n;
void solve()
{
for(int i=1;i<=n;i++)
{
int len1=strlen(s1[i]),len2=strlen(s2[i]);
if(islower(s2[i][0]))//A = x
{
ans[s1[i][0]-'A'][s2[i][0]-'a']=true;
}
else if(len1==1&&len2==1)//A = B
{
for(int j=0;j<26;j++)
ans[s1[i][0]-'A'][j]|=ans[s2[i][0]-'A'][j];
}
else if(len1==3)//A.f = B
{
for(int j=0;j<26;j++)//枚举A指针的对象
if(ans[s1[i][0]-'A'][j])//如果对象存在
for(int k=0;k<26;k++)//枚举对象的指针
s[j][s1[i][2]-'a'][k]|=ans[s2[i][0]-'A'][k];
}
else if(len2==3)//A = B.f
{
for(int j=0;j<26;j++)
if(ans[s2[i][0]-'A'][j])
for(int k=0;k<26;k++)
ans[s1[i][0]-'A'][k]|=s[j][s2[i][2]-'a'][k];
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%s%*s%s",s1[i],s2[i]);
for(int i=1;i<=n;i++)
solve();
for(int i=0;i<26;i++)
{
printf("%c: ",'A'+i);
for(int j=0;j<26;j++)
if(ans[i][j])
putchar('a'+j);
putchar('\n');
}
return 0;
}

浙公网安备 33010602011771号