1. 程式人生 > 其它 >NOIP提高組模擬賽加1

NOIP提高組模擬賽加1

A. 哪一天她能重回我身邊

學網路瘤學傻了,用費用瘤搞掉第一問就在想怎麼搞方案,然後越想越偏。。。。。

不僅沒有搞出來第二問,而且費用瘤的複雜度。。。。

總之就是掛的很慘。。。。

這題居然是個樹形\(DP\)???

把背面的數向正面的數連邊,翻一張卡相當於把邊反向,我們要用最少次數讓所有點入度小於等於\(1\),並且求出方案數

顯然對每個聯通塊可以分開考慮

如果一個聯通塊\(n>m\)那麼無論如何都無法滿足要求

那麼我們只需要考慮\(n==m\)\(n-1==m\)兩種情況

\(n-1==m\)這不是棵樹嗎

我們再看一眼目的“用最少次數讓所有點入度小於等於\(1\)

在樹上就有且只有一個點入度為\(0\)

,我們令這個點為樹根,然後換根\(DP\)就好了

\(n==m\)基環樹,環上要麼順時針要麼逆時針,其實只有兩種情況,隨便找個環上的邊斷開,分別以兩邊為根\(DFS\)一次即可

最小次數是所有聯通塊最小次數和,方案數是所有聯通塊方案數乘起來。

基環樹那裡有點小細節。。

code
#include<cstdio>
#include<cstring>

using namespace std;

const int mod=998244353;
const int maxn=200005;
const int inf=0x3f;
int n,head[maxn],tot;
struct edge{int net,to,val;}e[maxn<<1|1];
bool vis[maxn];
void add(int u,int v,int w){
    e[++tot].net=head[u];
    head[u]=tot;
    e[tot].to=v;
    e[tot].val=w;
}
void link(int u,int v){
    add(u,v,1);add(v,u,0);
}
int cnt1,cnt2;
void dfs(int x){
    vis[x]=1;++cnt1;
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;++cnt2;
        if(vis[v])continue;
        dfs(v);
    }
}
int jh;

void DFS(int x,int fa){
    bool flag=0;vis[x]=1;
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==x)jh=i;
        if(v==fa){
            if(flag)jh=i;
            else flag=1;
        }else{
            if(vis[v])jh=i;
            else DFS(v,x);
        }
    }
}

int mi,cnt;
void TD_1(int x,int fa,int s1,int s2){
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==fa||i==s1||i==s2)continue;
        if(e[i].val==0)++mi;
        TD_1(v,x,s1,s2);
    }
}
int now;
void TD_2(int x,int fa){
    if(now<mi)mi=now,cnt=0;
    if(now==mi)++cnt;
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==fa)continue;
        if(e[i].val==1){
            ++now;TD_2(v,x);--now;
        }else{
            --now;TD_2(v,x);++now;
        }
    }
}

int main(){
    int T;scanf("%d",&T);
    for(int ask=1;ask<=T;++ask){
        tot=0;for(int i=1;i<=n+n;++i)head[i]=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            int u,v;scanf("%d%d",&u,&v);link(v,u);
        }
        bool flag=1;
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n+n;++i)
            if(!vis[i]){
                cnt1=cnt2=0;
                dfs(i);
                cnt2/=2;
                if(cnt2>cnt1){flag=0;break;}
            }
        if(!flag)printf("-1 -1\n");
        else{
            long long ans2=1;int ans1=0;
            memset(vis,0,sizeof(vis));
            for(int i=1;i<=n+n;++i){
                if(head[i]==0||vis[i])continue;
                jh=0;DFS(i,i);
                if(jh){
                    int u=e[jh].to,v,hj;
                    if(jh%2)hj=jh+1;else hj=jh-1;
                    v=e[hj].to;
                    if(e[jh].val==0){u^=v;v^=u;u^=v;}
                    mi=0;TD_1(u,u,jh,hj);int r1=mi;
                    mi=1;TD_1(v,v,jh,hj);
                    if(r1==mi)ans2=ans2*2%mod;
                    ans1+=r1>mi?mi:r1;
                }else{
                    mi=0;TD_1(i,i,0,0);
                    now=mi;cnt=0;TD_2(i,i);
                    ans2=ans2*cnt%mod;
                    ans1=ans1+mi;
                }    
            }
            printf("%d %lld\n",ans1,ans2);
        }
    }
    return 0;
}

考場推柿子,亂搞半天激動地發現解出了\(S\),趕快實現,,,然後浮點數例外??

仔細觀察,大概就是搞了半天整出的柿子是\(0*S=0\),解個毛線。。。

首先知道\(a\)\(b\),一個簡單的樹形\(DP\)

\(S_i\)表示以\(i\)為根的子樹所有權值的和,整棵樹的根為\(1\)

\(DP\)中我們可以得到\(b_i=b_{fa}-S_i-S_i+S_1\),這個顯然是反推的關鍵

我們可以得到

\(b_i-b_{fa}=S_1-2*S_i\)

錯誤搞法\(0=0\)就不說了。。

正解考慮

\(\sum_{i=2}^{n}b_i-b_{i->fa}=(n-1)*S_1-2*\sum_{i=2}^{n}S_i\)

然後我們再想想當初是怎麼求\(b_1\)的,你會發現\(b_1=\sum_{i=2}^{n}S_i\)

那麼\(\sum_{i=2}^{n}b_i-b_{i->fa}=(n-1)*S_1-2*b_1\)

這裡可以解出\(S_1\),然後剩下的就非常簡單了

code
#include<cstdio>
#include<cstring>

using namespace std;
#define int long long
const int maxn=100000;
int head[maxn],tot,n;
struct edge{int to,net;}e[maxn<<1|1];
void add(int u,int v){
    e[++tot].net=head[u];
    head[u]=tot;
    e[tot].to=v;
}

int re[maxn],pr[maxn],s[maxn],dep[maxn],dt[maxn];

void DFS(int x,int fa){
    if(x!=1)dt[x]=re[x]-re[fa];
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==fa)continue;
        DFS(v,x);
    }
}
void DP(int x,int fa){
    pr[x]=s[x];
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==fa)continue;
        DP(v,x);
        pr[x]-=s[v];
    }
}
void worka(){
    for(int i=1;i<=n;++i)pr[i]=0;
    for(int i=1;i<=n;++i)dt[i]=0;
    for(int i=1;i<=n;++i)s[i]=0;
    DFS(1,1);
    for(int i=2;i<=n;++i)s[1]+=dt[i];
    s[1]+=re[1]+re[1];
    s[1]/=(n-1);
    for(int i=2;i<=n;++i)s[i]=(s[1]-dt[i])/2;
    DP(1,1); 
}

void dfs(int x,int fa){
    s[x]+=re[x];
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==fa)continue;
        dep[v]=dep[x]+1;dfs(v,x);
        s[x]+=s[v];
    }
}
void dp(int x,int fa){
    if(x!=1)pr[x]=pr[fa]-s[x]-s[x]+s[1];
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==fa)continue;
        dp(v,x);
    }
}
void workb(){
    for(int i=1;i<=n;++i)s[i]=0;
    dep[1]=0;dfs(1,1);
    for(int i=1;i<=n;++i)pr[i]=0;
    for(int i=1;i<=n;++i)pr[1]+=dep[i]*re[i];
    dp(1,1);
}

signed main(){
    int T;scanf("%lld",&T);
    for(int ask=1;ask<=T;++ask){
        scanf("%lld",&n);
        for(int i=1;i<=n;++i)head[i]=0;tot=0;
        for(int i=1;i<n;++i){
            int u,v;scanf("%lld%lld",&u,&v);
            add(u,v);add(v,u);
        }
        int type;scanf("%lld",&type);
        for(int i=1;i<=n;++i)scanf("%lld",&re[i]);  
        if(type)worka();
        else workb();
        for(int i=1;i<=n;++i)printf("%lld ",pr[i]);printf("\n");
    }    
    return 0;
}