noip模擬8[星際旅行·砍樹·超級樹·求和]

fengwu2005發表於2021-06-18

也不能算考得好,雖然這次A了一道題,但主要是那道題太簡單了,沒啥成就感,而且有好多人都A掉了

除了那一道,其他的加起來一共拿了25pts,這我能咋辦,無奈的去改題

整場考試的狀態並不是很好啊,不知道是剛放假回來的原因還是昨天晚上沒睡好,

考試的時候一直很困,那種睜不開眼的困,然後導致我這次考試前三個題,屁大點的思路都沒有

所以還是要保養好精神的,畢竟還有這麼多事

所以我下次考試要保持一個好的狀態,然後拿最多的分;;;

這次的考題思路極其怪癖,不對,是清奇!!!而且是想不到的清奇

 

正解::::

T1 星際旅行

題意:一條路徑,經過兩條邊一次,其他的經過兩次,求方案數啊

這題迷的不行,這個讓求方案數,應該很多吧,然後他還不取模,我就懵圈了,這算啥方案數???

做取模的題做多了,以為方案數都特別大,其實也有小一些的,longlong足以解決這個題

整個就是一大模擬,對我來說,可是我一開始沒有看出來咋模擬,

後來聽了碩隊的講解,才明白,這是讓你去邊找尤拉路吶~~~

具體做法就是將所有的路徑拆分成兩條不同方向的路徑,我們就有了2*m條路徑,然後我們在這些路徑中去掉兩條

去掉之後,可以使這個圖上的邊(拆分後的邊),能夠一筆畫完,(找之前別忘了判斷圖上的邊是否全部聯通)

我們考慮尤拉圖存在的條件,有兩個或者沒有連奇數條邊的節點,所以根據這個定義,我們可以拆掉的邊只有三種情況

  1、任意兩個自環(拆自環的話,每個點的邊數減少二,不會影響結果)

  2、兩個連在同一點上的邊(只能這樣拆,因為這樣那個共同的點拆之後還是偶數,如果任意拆兩條邊,那麼會有四個邊數為奇數的點)

  3、任意一個自環和任意一條邊(自環不影響,邊造成兩個奇點)

所以我們在實現的時候,只需要統計有多少自環,有多少邊,每個點的度數,用計數原理乘一乘就好了

注意判斷是否聯通的時候,可以有點沒有遍歷到,但是邊必須都要遍歷到

noip模擬8[星際旅行·砍樹·超級樹·求和]
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=100005;
int n,m;
int to[N*2],nxt[N*2],head[N],rp;
int zi,oi,du[N];
ll ans;
void add_edg(int x,int y){
    to[++rp]=y;
    nxt[rp]=head[x];
    head[x]=rp;
}
bool vis[N],edg[N];
int vi;
void dfs(int x,int f){
    //cout<<x<<" ";
    if(vis[x])return ;
    vis[x]=1;
    for(re i=head[x];i;i=nxt[i]){
        int y=to[i];
        //cout<<x<<" "<<y<<" "<<vi<<" ";
        //if(y==x)vi++;
        //cout<<vi<<endl;
        if(y==f)continue;
        vi++;
        //cout<<x<<" "<<y<<" "<<vi<<endl;
        if(vis[y]==0)dfs(y,x);
    }
}
signed main(){
    scanf("%d%d",&n,&m);
    for(re i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        edg[x]=edg[y]=1;
        if(x==y){
            add_edg(x,y);
            zi++;
        }
        else{
            add_edg(x,y);
            add_edg(y,x);
            oi++;
            du[x]++;
            du[y]++;
        }
    }
    for(re i=1;i<=n;i++){
        dfs(i,0);
        if(vi!=0)break;
        vis[i]=0;
        //cout<<endl;
    }
    for(re i=1;i<=n;i++){
        if(!vis[i]&&edg[i]){
            //cout<<i<<endl;
            printf("0");return 0;
        }
    }
    ans+=1ll*zi*oi;
    ans+=1ll*zi*(zi-1)/2;
    //cout<<ans<<endl;
    for(re i=1;i<=n;i++){
        if(du[i]>1)ans+=1ll*du[i]*(du[i]-1)/2;
    }
    printf("%lld",ans);
}
T1

T2 砍樹

這個題好像比最後一個題還水::::

題意:每隔d天砍一次樹,把比期望值高的那部分砍掉,問你在最多砍k長度的情況下,最大的d是多少

所以這個題是個數論題,我竟然沒看出來,我瞬間感覺我莫比烏斯專題白學了,竟然連這個題都不會推!!!

氣人,所以這個題就是一個簡單的推式子,程式碼寫出來25行不到。。。。

我們要求的就是

      $ \sum \limits_{i=1}^{n} \left ( \left \lceil \frac{a_i}{d}  \right \rceil \times d -a_i\right )\le k $

      $ \sum \limits_{i=1}^{n} \left ( \left \lceil \frac{a_i}{d}  \right \rceil \times d \right )-\sum\limits_{i=1}^{n}a_i\le k $

      $ \sum \limits_{i=1}^{n} \left ( \left \lceil \frac{a_i}{d}  \right \rceil \times d \right )\le\sum\limits_{i=1}^{n}a_i+k $

      設C = $ \sum\limits_{i=1}^{n}a_i+k $

      $ \sum \limits_{i=1}^{n} \left \lceil \frac{a_i}{d}  \right \rceil \le \frac{C}{d}  $

      $ \sum \limits_{i=1}^{n} \left \lceil \frac{a_i}{d}  \right \rceil \le \left \lfloor \frac{C}{d}\right \rfloor  $

所以我們就發現後面的式子就是一個關於數論分塊的東西

我們就可以用分塊去求每個塊的左右邊界了,不會自己找部落格

右側的每一個塊內,一定是右邊界最優,而左邊,顯然隨著d的增加他是遞減的,所以我們只需要每次判斷右邊界的取值然後更新答案即可

noip模擬8[星際旅行·砍樹·超級樹·求和]
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=105;
int n;
ll a[N],k,ans;
signed main(){
    scanf("%d%lld",&n,&k);
    for(re i=1;i<=n;i++)scanf("%lld",&a[i]),k+=a[i];
    for(ll l=1,r;l<=k;l=r+1){
        r=k/(k/l);
        ll tmp=0;
        for(re i=1;i<=n;i++){
            tmp+=a[i]/r;
            if(a[i]%r!=0)tmp++;
        }
        if(tmp*r<=k)ans=r;
    }
    printf("%lld",ans);
}
T2

T3 超級樹

他把所有的點到他祖先的邊都連起來了,這邊數多的要爆炸啊

題意:按照上面說的先構建一顆超級樹,然後尋找樹上的路徑條數,要求要遍歷每一個點不超過一次,就是說可以不走這個點,單點也算一條路徑

這種題,邊數如此之多,我們只能往dp的方向去想,不然你去遍歷整個圖啊,而且你還遍歷不完,這咋辦,雖然考場上我看到這個題就想困,但是我知道他是個dp

但是這個dp方程也太不正常了,我看題解理解了半個多小時,那我要是自己想得想多久啊,真不知道啥境界才能想出正解啊

設dp[i][j]表示目前i層,並且這個i層的超級樹中存在j條不相交的路徑的方案數

這個dp含義很難理解,第一維是沒問題的,我們看看第二維

簡單說就是在這個超級樹裡,有j條路徑,並且這j條路徑所覆蓋的點沒有重複的,就是我們沿著這j條路徑走過一遍,每個點只會走一次或者不走

就是j條路徑上沒有重複的點。。。懂了吧,這dp多麼毒瘤,光看定義就知道有多麻煩了;;;;

為什麼要這樣定義呢,因為我們的轉移方程,是有多條路徑的方案數想1條轉移的,這樣才能求出最後的答案

一共有五個轉移方程,在轉移之前我們一共有三層迴圈,第一層一定是層數i,第二層是左子樹的j,設為l;第三層是右子樹的j,設為r;

設num=dp[i][l]*dp[i][r]         就是乘法原理,兩顆子樹分別有這些情況合併之後自然就是乘起來了。。

      1、我們對這顆樹沒有任何操作:dp[i+1][l+r]+=num    定義的重要性,根節點此時已經空出來了

      2、我們將目前的根節點與左子樹或右子樹中的任意一條路徑相連:dp[i+1][l+r]+=2*num*(l+r) 我們一共有l+r條路徑可以連

            (因為這個根已經和他子樹的每一個節點相連了,每條路徑都可以連上而且兩端是兩種情況,所以×2)

      3、我們把根節點自己算作一個單獨的路徑,就有了:dp[i+1][l+r+1]+=num

      4、我們用根節點分別連線兩顆子樹中的路徑:dp[i+1][l+r-1]+=2*num*l*r

            就是兩顆子樹中分別有一個路徑,然後通過根的連結他變成了一條,左子樹l種,右子樹r種

      5、我們用根節點去連線某一棵子樹內的兩條邊:dp[i+1][l+r-1]+=num*(l*(l-1)+r*(r-1)) 就是左子樹內兩個路徑變一條,或者右子樹內兩條變一條

這樣我們就搞完了轉移方程,然後你發現,這第二維有2^n這麼多種情況誒,那你不超時誰超時,

但是這每一次轉移都只會將每一個的第二維+1或者-1所以我們只需要n個狀態

最後求的是一條路徑的方案數,所以答案就是dp[n][1]啦,定義要好好理解

這樣我們就可以愉愉快快的寫程式碼啦

noip模擬8[星際旅行·砍樹·超級樹·求和]
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=305;
ll n,mod;
ll dp[N][N];
signed main(){
    scanf("%lld%lld",&n,&mod);
    dp[1][0]=dp[1][1]=1%mod;
    for(re i=1;i<n;i++){
        for(re l=0;l<=n;l++){
            for(re r=0;r<=n-l;r++){
                ll num=dp[i][l]*dp[i][r]%mod;
                dp[i+1][l+r]+=num;
                dp[i+1][l+r+1]+=num;
                dp[i+1][l+r]+=2*num*(l+r);
                dp[i+1][l+r-1]+=2*num*l*r;
                dp[i+1][l+r-1]+=num*(l*(l-1)+r*(r-1));
                dp[i+1][l+r]%=mod;
                dp[i+1][l+r+1]%=mod;
                dp[i+1][l+r-1]%=mod;
            }
        }
    }
    printf("%lld",dp[n][1]);
}
T3

 T4 求和

題意:給你一個樹,讓你求某兩個點之間的路徑上的所有邊權的k次方之和

直接預處理字首和,樹鏈剖分求lca,然後減一下,這題真水!!!!

noip模擬8[星際旅行·砍樹·超級樹·求和]
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=310005;
const int mod=998244353;
int n,m;
int to[N*2],nxt[N*2],head[N],rp;
int sum[N][55];
void add_edg(int x,int y){
    to[++rp]=y;
    nxt[rp]=head[x];
    head[x]=rp;
}
int ksm(int x,int y){
    int ret=1;
    while(y){
        if(y&1)ret=1ll*ret*x%mod;
        x=1ll*x*x%mod;
        y>>=1;
    }
    return ret;
}
int dep[N],siz[N],son[N],top[N],fa[N];
void dfs1(int x,int f){
    //cout<<x<<" "<<f<<endl;
    for(re i=1;i<=50;i++)sum[x][i]=(1ll*sum[f][i]+ksm(dep[x],i))%mod;
    //if(x==130934)cout<<"sb"<<endl;
    siz[x]=1;son[x]=0;
    for(re i=head[x];i;i=nxt[i]){
        int y=to[i];
        //if(x==130934)cout<<i<<endl;
        if(y==f)return ;
        //if(x==130934)cout<<y<<endl;
        fa[y]=x;
        dep[y]=dep[x]+1;
        dfs1(y,x);
        siz[x]+=siz[y];
        if(!son[x]||siz[y]>siz[son[x]])son[x]=y;
    }
}
void dfs2(int x,int f){
    top[x]=f;
    if(son[x])dfs2(son[x],f);
    for(re i=head[x];i;i=nxt[i]){
        int y=to[i];
        if(y==son[x]||y==fa[x])continue;
        dfs2(y,y);
    }
}
int LCA(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}
signed main(){
    //int o=time(NULL);
    scanf("%d",&n);
    for(re i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add_edg(x,y);
        add_edg(y,x);
    }
    //cout<<rp<<" "<<to[rp]<<endl;
    dfs1(1,0);
    //cout<<"sb"<<endl;
    dfs2(1,1);
    //cout<<"sb"<<endl;
    scanf("%d",&m);
    for(re i=1;i<=m;i++){
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        int lca=LCA(l,r);
        int ans=(1ll*sum[l][k]+sum[r][k]+2ll*mod-sum[lca][k]-sum[fa[lca]][k])%mod;
        //if(l>300000||r>300000||k>50)cout<<"sb"<<endl;
        printf("%d\n",ans);
    }
    //cout<<time(NULL)-o<<endl;
}
T4

完結