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;
}