首页 > 其他分享 >题解 [NOIP2022] 建造军营

题解 [NOIP2022] 建造军营

时间:2024-10-23 08:59:58浏览次数:1  
标签:wb int 题解 nd times 军营 NOIP2022 dp MOD

树形 \(dp\) 好题。

观察题目发现, 如果B国袭击后,导致A国两个军营不联通,那么B国袭击的一定是一条割边,反之,如果袭击的不是割边,那么不会导致任何影响。

所以先进行边双缩点,变成一棵树,记每个联通块(缩完后)内的点数为 \(wa\),边数为 \(wb\),不妨先考虑对于树的情况如何处理。

将问题进行转化,即在树上选一些点和边,使得所有点可以通过选的边相互到达,询问选点和边的方案数。

首先设 \(f[i][0/1]\) 表示以 \(i\) 为根的子树内没有/有 军营的方案数,那么答案显然是 \(f[1][1]\),但我们发现一种情况很难转移:对于节点 \(i\),有些子树内有军营,有些没有,很难对边进行计数。

出现难以转移的情况,有两种解决办法:

  • 增加 \(dp\) 状态,更加具体地描述每个状态;
  • 不增加状态,但是对状态作出更严格的限制。

经过尝试,即使增加 \(dp\) 状态,仍然难以解决上述问题,所以我们采用第二种方法。

设 \(f[i][0/1]\),其中 \(f[i][0]\) 表示以 \(i\) 为根的子树内没有选点,\(f[i][1]\) 表示以 \(i\) 为根的子树内选了点,且选出的边满足选出的点与 \(i\) 点连通(注意:\(i\) 不一定要选)。

先来考虑答案是什么,显然不是 \(f[1][1]\),需要对每个点单独考虑,具体地,设整棵树除去 \(i\) 为根的子树的边数为 \(cnt[i]\),那么答案就是:

\[\sum_{i=1}^{siz} f[i][1]\times (2^{cnt[i]}-1) \]

解释一下:依次考虑每个点,记当前点为 \(i\),并且钦定只有 \(i\) 子树内选了点,于是 \(i\) 子树外面的边怎么选就无所谓(反正没有点,乱选边也不会有影响),所以每个点选或不选就是 \(2^{cnt[i]}\),但直接这样求会算重,原因如下图所示:

原因是在考虑 \(i\) 时,在选择 \(i\) 外面的边时选择了 \(i\) 头上的边,这种情况恰好包含在 \(f[fa][1]\) 中,因为正好通过这条边与 \(fa\) 相连。

所以考虑 \(i\) 时强制不选 \(i\) 与 \(fa\) 的边,这样就不会导致算重。也就是上述式子中的 \(2^{cnt[i]}-1\)。

那么这种算法能将所有情况统一下来不漏吗?换一个角度考虑,发现任意一种选点方案,会在选的点的 \(\rm LCA\) 及其祖先处进行统计,由于我们规定不选与父亲相连的边,所以在所以祖先处考虑时边集不会重复,且覆盖所有,所以没有问题。

下面来考虑如何 \(dp\)。

\[\begin{cases} f[nd][1]=2\times \prod_{x\in son[nd]} (f[x][1]+2\times f[x][0])-\prod_{x\in son[nd]} 2\times f[x][0]\\ f[nd][0]=\prod_{x\in son[nd]} f[x][0]\times 2 \end{cases} \]

解释:\(f[x][0] \times 2\) 代表 \(nd\) 与 \(x\) 相连的边有选/不选两种可能,\(f[nd][1]\) 后面减的东西表示除去子树全部为 \(0\) 的情况,前面的 \(2\times\) 系数表示 \(nd\) 自己选/不选两种可能,如果 \(nd\) 选了,那么子树选不选都无所谓,但如果 \(nd\) 没选,那么子树必须要选。

这样我们就会了单纯是树的情况。

下面来考虑边双缩点成的树的情况,多了 \(wa\) 和 \(wb\) 两个权值。

其实几乎只有边界条件的不同,若选择点 \(i\),那么有 \((2^{wa[i]}-1)\times 2^{wb[i]}\),若不选,就是\(2^{wb[i]}\),然后剩下按照转移方程计算即可。

时间复杂度:\(\rm O(n)\)。

代码解释:f 同上,dp 表示子树内的边,wa 表示联通块内点数,wb 表示联通块内边数,p 表示 \(2\) 的次幂。

#include<bits/stdc++.h>
using namespace std;

#define PII pair<int,int>
typedef long long LL;
typedef unsigned long long ULL;
LL read() {
    char c=getchar(); LL sum=0,flag=1;
    while(c<'0'||c>'9') {if(c=='-') flag=-1; c=getchar();}
    while(c>='0'&&c<='9') {sum=sum*10+c-'0'; c=getchar();}
    return sum*flag;
}

const int N=5e5+10,M=1e6+10;
const LL MOD=1e9+7;
int n,m;
int low[N],dfn[N],siz,tmst,id[N];
int wa[N],wb[N];
vector<PII> g[N];
struct Edge {
    int l,r;
}a[M]; 
stack<int> st;
LL f[N][2],p[M],dp[N];

void dfs(int nd,int lst) {
    dfn[nd]=low[nd]=++tmst; st.push(nd);
    for(auto t:g[nd]) {
        int x=t.first,id=t.second;
        if(lst==id) continue;
        if(!dfn[x]) {
            dfs(x,id);
            low[nd]=min(low[nd],low[x]);
        }
        else low[nd]=min(low[nd],dfn[x]);
    }
    if(low[nd]==dfn[nd]) {
        int y=0; ++siz;
        do {
            y=st.top(); st.pop();
            id[y]=siz; wa[siz]++;
        } while(y!=nd);
    }
}

void dfs2(int nd,int fa) {
    f[nd][0]=p[wb[nd]];
    f[nd][1]=(p[wa[nd]]-1)*p[wb[nd]]%MOD;
    dp[nd]=wb[nd];
    if(g[nd].size()==1&&g[nd][0].first==fa) return ;
    LL s1=p[wb[nd]];//自己不选
    LL s2=(p[wa[nd]]-1)*p[wb[nd]]%MOD;//自己选
    LL s0=p[wb[nd]];//计算子树全部不选的数量
    for(auto t:g[nd]) {
        int x=t.first; if(x==fa) continue;
        dfs2(x,nd);
        dp[nd]+=dp[x]+1;
        f[nd][0]*=f[x][0]*2; f[nd][0]%=MOD;
        s1*=f[x][1]+f[x][0]*2; s1%=MOD;
        s2*=f[x][1]+f[x][0]*2; s2%=MOD;
        s0*=f[x][0]*2%MOD; s0%=MOD;
    }
    f[nd][1]=s2+s1-s0; f[nd][1]=(f[nd][1]%MOD+MOD)%MOD;
}

int main(){
    n=read(); m=read();
    p[0]=1; for(int i=1;i<=m;i++) p[i]=p[i-1]*2ll%MOD;
    for(int i=1;i<=m;i++) {
        int x=read(),y=read();
        a[i]={x,y};
        g[x].push_back({y,i});
        g[y].push_back({x,i});
    }
    dfs(1,0);
    for(int i=1;i<=n;i++) {
        g[i].clear();
    }
    for(int i=1;i<=m;i++) {
        int x=id[a[i].l],y=id[a[i].r];
        if(x==y) wb[x]++;
        else {
            g[x].push_back({y,0});
            g[y].push_back({x,0});
        }
    }
    dfs2(1,0);

    LL ans=0;
    for(int i=1;i<=siz;i++) {
        ans=(ans+f[i][1]*p[max(dp[1]-dp[i]-1,0ll)]%MOD)%MOD;
    }
    cout<<ans;

    return 0;
}
/*
*/

标签:wb,int,题解,nd,times,军营,NOIP2022,dp,MOD
From: https://www.cnblogs.com/zhangyuzhe/p/18494370

相关文章

  • 20241022 校测T1 链链链(chain)题解
    Problem链链链chain你有一个长度为\(n\)的链,编号为\(i(1≤i<n)\)的边连接着结点\(i\)与\(i+1\)。每个结点\(i\)上有一个整数\(a_i\)。你需要做以下操作\(n−1\)次:•选择一条还未被断开的边,设其连接了点\(i\)与\(i+1\),将其断开。•断边后,对于所......
  • P9751 [CSP-J 2023] 旅游巴士 题解
    思路首先,举一个例子,假如说小Z到了入口,但是没到时间,所以没法进去,该怎么办?当然是等$k$个时间单位呀.除此之外,像到了其他景区,但是还没开门怎么办?继续等$k$的非负整数倍时间呀.知道这个后,我们先定义状态$f_{i,j}$,表示到达点$i$时,路径长度(即时间)$mod$$k$的最早时......
  • P9749 [CSP-J 2023] 公路 题解
    此题贪心食用更佳在输入油价的时候,我们边计算油价的最小值和路程和.当路程之和$>0$时,计算油价并且减去对应路程即可.注意事项要开$long$$long$!!!.代码#include<iostream>#include<cstdio>#include<cmath>#include<cstring>usingnamespacestd;typedeflonglo......
  • P9750 [CSP-J 2023] 一元二次方程 题解
    大模拟此题大模拟即可,只需注意几点:分母$>0$.要给根式化简.分数要约分.求较大根,那就$b^2$加$\bigtriangleup$即可.分母>0因为求根公式中,分母中只有$a$一个未知数,所以我们只需保证$a>0$即可.所以,当$a<0$时,我们把$a,b,c$全部取相反值.但这也是......
  • 题解:P10949 四叶草魔杖
    2024/10/16更新:修改了状态的枚举方式,时间复杂度变为\(O(3^n)\)。题目传送门前言本篇题解默认您已熟练掌握最小生成树、状压dp及其应用,如果您还不会,请先阅读相关博客。分析我们要选出一条边,通过边转移能量,使得所有宝石的能量都为\(0\)。这看上去挺麻烦的,让我们挖掘一......
  • 题解:AT_joisc2019_k 合併 (Mergers)
    题目传送门前言联考题,被初一的我切了。看到题解区里没有Tarjan做法,于是来补一篇Tarjan题解。分析因为相同州的城市不会分裂,所以可以给相同州的成市连边(注意不是两两连边,连成一个环就行),发现把国家分成两个部分就相当于断掉一条道路。那么如果整个国家就是一个边双连通分量,......
  • 题解:P11207 「Cfz Round 9」Rose
    可以考虑把字符串\(s\),\(t\)按\(s_1t_1s_2t_2\dotss_nt_n\)拼接,记为\(a\)。为了方便表述,这里分别把PVW表示为012。Subtask0我会暴力!可以直接在\(a\)上进行dfs,复杂度为\(O(3^{2n})\)。Subtask1我会找性质!注意到答案只有可能是\(0,1,2\),因为在最坏情况下,只......
  • 题解:P11204 「Cfz Round 9」Lone
    首先可以观察出把木棍平均分是最优的。然后平均分后最多只有两种长度的木棒,长度分别为\(\lfloor\frac{m}{n}\rfloor\)和\(\lfloor\frac{m}{n}\rfloor+1\)。最后check一下就行了。代码:#include<bits/stdc++.h>usingnamespacestd;#defineintlonglong#define......
  • P2934 [USACO09JAN] Safe Travel G 题解
    一个用平衡树,不用脑子的写法。(目前没有用平衡树的诶。)题意不经过最短路的最后一条边的最短路,保证最短路唯一。思路看到最短路唯一容易想到建出的最短路DAG其实是最短路树(以\(1\)为根)。那题意转化为求每个节点不经过与父亲的连边,所能到根节点的最短路。容易发现每个点的......
  • 【NOIP2021】方差 题解
    前言题目链接:洛谷;LOJ;UOJ。题意简述给你单调不降序列\(\{a_n\}\),你可以让\(a_i\getsa_{i-1}+a_{i+1}-a_i\),求操作后方差的最小值。\(n\leq10^4\),\(1\leqa_i\leq600\)。题目分析仔细观察操作,发现实际上是将\(a_i\)按照\(a_{i-1}\)和\(a_{i+1}\)的......