1. 程式人生 > 實用技巧 >8.1 2020牛客暑期多校訓練營(第七場)題解及補題

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;

}