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)),(2
dep(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;

}
posted @ 2020-08-09 22:11  cugbacm03  阅读(230)  评论(0)    收藏  举报