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

posted @ 2022-05-26 12:12  Chen_jr  阅读(47)  评论(0编辑  收藏  举报