干千翻 發表於 2025-5-4 15:20:00

AtCoder Beginner Contest 404 C-G(无F)题解

<h2 id="c-cycle-graph">C. Cycle Graph?</h2>
<h3 id="题意">题意</h3>
<p>给你一个 <span class="math inline">\(N\)</span> 个顶点 <span class="math inline">\(M\)</span> 条边的简单(无重边、自环)无向图,第 <span class="math inline">\(i\)</span> 条边连接节点 <span class="math inline">\(A_i\)</span> 和 <span class="math inline">\(B_i\)</span>,判断这个图是不是一个环。</p>
<h3 id="思路">思路</h3>
<p>首先一个图是环,要满足点数等于边数,即 <span class="math inline">\(N=M\)</span>;</p>
<p>其次,这个图必须连通,可以通过 <span class="math inline">\(\text{DFS}\)</span> 或 <span class="math inline">\(\text{BFS}\)</span> 搜索判断是否连通(从任意一点开始搜,结束后检查是否每个点都已到达过);</p>
<p>最后,每个点的度数(所连接的顶点数)必须为 <span class="math inline">\(2\)</span>。</p>
<p>可以证明,只要满足上述三个条件,这个图一定是一个环。</p>
<h3 id="c-代码">C++ 代码</h3>
<pre><code class="language-cpp">#include&lt;bits/stdc++.h&gt;
using namespace std;
const int maxn=200005;
int n,m;
int deg;
vector&lt;int&gt; g;
bool used;
void dfs(int v){
        used=true;
        for(int x:g){
                if(!used){
                        dfs(x);
                }
        }
}
int main(){
        cin&gt;&gt;n&gt;&gt;m;
        if(n!=m){
                cout&lt;&lt;"No\n";
                return 0;
        }
        for(int i=1;i&lt;=m;i++){
                int u,v; cin&gt;&gt;u&gt;&gt;v;
                g.push_back(v);
                g.push_back(u);
        }
        dfs(1);
        for(int i=1;i&lt;=n;i++){
                if(!used||g.size()!=2){
                        cout&lt;&lt;"No\n";
                        return 0;
                }
        }
        cout&lt;&lt;"Yes\n";
        return 0;
}
</code></pre>
<h2 id="d-goin-to-the-zoo">D. Goin' to the Zoo</h2>
<h3 id="题意-1">题意</h3>
<p><span class="math inline">\(N\)</span> 个动物园,动物园 <span class="math inline">\(i\)</span> 入场费为 <span class="math inline">\(C_i\)</span>。<span class="math inline">\(M\)</span> 种动物,第 <span class="math inline">\(j\)</span> 种动物可以在共 <span class="math inline">\(K_j\)</span> 个动物园看到,分别为动物园 <span class="math inline">\(A_{j,1},A_{j,2},\ ...,A_{j,K_j}\)</span>。</p>
<p>要看每种动物至少两次,至少花多少钱。</p>
<p><strong>注:只要花一次 <span class="math inline">\(C_i\)</span>,就可以进入动物园 <span class="math inline">\(i\)</span> 一次,就可以看里面的每个动物一次;若花两次,则可进入两次,看里面的动物两次</strong></p>
<h3 id="思路-1">思路</h3>
<p>由于 <span class="math inline">\(N \le 10,M\le 100\)</span>,可以想到用状态压缩(不是dp)枚举。</p>
<p>状态压缩,就是把状态压缩到一个数字里</p>
<p>大致思路如下:</p>
<p>以 <span class="math inline">\(140\)</span> 为例,三进制数为 <span class="math inline">\(12012\)</span>:</p>
<p><img src="https://cdn.luogu.com.cn/upload/image_hosting/o79vgk67.png"></p>
<p>这样,<span class="math inline">\(1\sim 3^N\)</span> 的每个数字都有了实际含义</p>
<p>只要枚举 <span class="math inline">\(1\sim3^N\)</span> 的每个数,判断这样参观动物园能否达成“每种动物至少看两次”的目标,若可以,则记录答案,取最小值。</p>
<p>时间复杂度 <span class="math inline">\(O(NM·3^N )\)</span></p>
<h3 id="c-代码-1">C++ 代码</h3>
<pre><code class="language-cpp">#include&lt;bits/stdc++.h&gt;
#define int long long
using namespace std;
const int inf=3e18;
const int maxn=1000005;
int c;
int k;
int v;
int mask;
int n,m;
signed main(){
        cin&gt;&gt;n&gt;&gt;m;
        for(int i=1;i&lt;=n;i++){
                cin&gt;&gt;c;
        }
        for(int i=1;i&lt;=m;i++){
                cin&gt;&gt;k;
                for(int j=0;j&lt;k;j++){
                        cin&gt;&gt;v;
                }
        }
        int ans=inf;
        for(int msk=0;msk&lt;pow(3ll,n);msk++){
                memset(mask,0,sizeof(mask));//mask为当前数字所对应的三进制数
                int num=0;//num为花费
                int p=msk;
                for(int i=1;i&lt;=n;i++){
                        num+=(p%3)*c;
                        for(int j=1;j&lt;=m;j++){
                                for(int a=0;a&lt;k;a++){
                                        if(v==i) mask+=p%3;
                                }
                        }
                        p/=3;
                }
                bool flag=true;
                for(int j=1;j&lt;=m;j++){
            if(mask&lt;2) flag=false;
      }
                if(flag) ans=min(ans,num);
        }
        cout&lt;&lt;ans&lt;&lt;endl;
        return 0;
}
</code></pre>
<h2 id="e-bowls-and-beans">E. Bowls and Beans</h2>
<h3 id="题意-2">题意</h3>
<p><span class="math inline">\(N\)</span> 个碗排成一排,编号为 <span class="math inline">\(0\sim N-1\)</span>,碗 <span class="math inline">\(i\)</span> 中有 <span class="math inline">\(A_i\)</span> 个豆子,上面写着一个数字 <span class="math inline">\(C_i\)</span>。</p>
<p>每次操作可以将碗 <span class="math inline">\(i\)</span> 里的豆子可以放到之前 <span class="math inline">\(i-C_i \sim i-1\)</span> 中的任意碗里,并且可以任意分配每个碗里放几颗。</p>
<p>最初碗 <span class="math inline">\(0\)</span> 中没有豆子,问:将所有豆子都移到碗 <span class="math inline">\(0\)</span> 中,最少需要多少步。</p>
<h3 id="思路-2">思路</h3>
<p><s>贪心好像也可以,但是我不会!!</s></p>
<p><span class="math inline">\(N \le 2000\)</span>,考虑 <span class="math inline">\(O(n^2)\)</span> 动态规划。</p>
<p>动态规划基本三步:</p>
<ol>
<li>设计 <span class="math inline">\(\text{DP}\)</span> 状态:</li>
</ol>
<p>​        定义 <span class="math inline">\(f_i\)</span> 表示将编号 <span class="math inline">\(\ge i\)</span> 的所有碗中的豆子全部移到碗 <span class="math inline">\(i\)</span> 中的最小步骤;</p>
<ol start="2">
<li>初始化:</li>
</ol>
<p>​        设最后一个有豆子的碗为 <span class="math inline">\(p\)</span>,则对于 <span class="math inline">\(i=p\sim n-1\)</span>,<span class="math inline">\(f_i=0\)</span>(不需要操作),其余初始 <span class="math inline">\(f_i=\infty\)</span>;</p>
<ol start="3">
<li>
<p>转移顺序及转移方程:</p>
<p>顺序:由于每个碗里的豆子只能往前移,为避免转移产生影响后续计算,应从后往前转移;</p>
<p>满足以下条件时,<span class="math inline">\(f_i=\min(f_i,f_j+1)\)</span>:</p>
<ul>
<li>
<p>条件1:<span class="math inline">\(j&gt;i\)</span>;</p>
</li>
<li>
<p>条件2:碗 <span class="math inline">\(j\)</span> 的豆子可以移到碗 <span class="math inline">\(i\)</span> 中,即<span class="math inline">\(j-i \le C_j\)</span>;</p>
</li>
<li>
<p>条件3:若 <span class="math inline">\(i-j\ge2\)</span>,<span class="math inline">\(i+1\)</span> 到 <span class="math inline">\(j-1\)</span> 之间的任何一个碗都没有豆子(否则不可能一步就完成 <span class="math inline">\(j \rightarrow i\)</span> 的操作)。</p>
</li>
</ul>
</li>
</ol>
<pre><code class="language-cpp">#include&lt;bits/stdc++.h&gt;
#define int long long
using namespace std;
const int inf=3e18;
const int maxn=2005;
int n;
int c,f;
bool a;
signed main(){
        cin&gt;&gt;n;
        for(int i=1;i&lt;n;i++) cin&gt;&gt;c;
        for(int i=1;i&lt;n;i++) cin&gt;&gt;a;
   
    //初始化
        for(int i=0;i&lt;n;i++) f=inf;
    int pos;
    for(int i=n-1;i&gt;=0;i--){
      f=0;
      if(a){
            pos=i-1;
              break;
      }
    }
   
    //转移
        for(int i=pos;i&gt;=0;i--){//为了避免产生后效性,从后往前遍历
                for(int j=i+1;j&lt;n;j++){//为了满足条件1,j=i+1开始
                        if(j-i&lt;=c) f=min(f,f+1);//为了满足条件2
                        if(a) break;//为了满足条件3,只要遇到了有豆子的碗就退出
                }
        }
        cout&lt;&lt;f&lt;&lt;endl;
        return 0;
}
</code></pre>
<h2 id="g-specified-range-sums">G. Specified Range Sums</h2>
<h3 id="题意-3">题意</h3>
<p>有三个长度为 <span class="math inline">\(M\)</span> 的序列 <span class="math inline">\(L,R,S\)</span>,你要判断是否存在一个长度为 <span class="math inline">\(N\)</span> 的 <strong>正整数</strong> 序列 <span class="math inline">\(A\)</span>,满足以 <span class="math inline">\(\sum_{j=L_i}^{R_i} A_j=S_i\)</span>。</p>
<p>若存在,找到最小的 $\sum_{j=1}^N A_j $;否则,输出 <code>-1</code>。</p>
<h3 id="思路-3">思路</h3>
<p>首先,我们考虑将求和转换为前缀和,即定义 <span class="math inline">\(C_i=\sum_{j=1}^iA_j\)</span>,则<span class="math inline">\(C_{R_i}-C_{L_i-1}=S_i\)</span>。</p>
<p>建立有向图,顶点编号为 <span class="math inline">\(0 \sim n\)</span>,这样连边:<span class="math inline">\((L_i-1,R_i)=S_i\)</span>,<span class="math inline">\((R_i,L_i-1)=-S_i\)</span>。另外,由于是正整数序列,所以 <span class="math inline">\((i+1,i)=-1\)</span>。</p>
<p>我们需要计算 <span class="math inline">\(n \rightarrow 0\)</span> 的最短路,答案即为这个值的相反数。</p>
<p>注意:无解时图中有负环,所以 <span class="math inline">\(\text{Dijkstra}\)</span> 不可以。考虑可以处理负环的 <span class="math inline">\(\text{Bellman-Ford}\)</span> 算法(不会没关系,下面讲):</p>
<p>与图上动态规划相似,定义 <span class="math inline">\(dis_i\)</span> 表示 从 <span class="math inline">\(n\)</span> 到 <span class="math inline">\(i\)</span> 的最短路,<span class="math inline">\(dis_n=0\)</span>,其余为 <span class="math inline">\(\infty\)</span>。</p>
<p>共进行 <span class="math inline">\(N\)</span> 次操作,每次操作如下:</p>
<ul>
<li>对于每一条有向边 <span class="math inline">\((u,v)=w\)</span>,<span class="math inline">\(dis_v=\min(dis_v,dis_u+w)\)</span>,共 <span class="math inline">\(M\)</span> 条边。</li>
</ul>
<p>复杂度为 <span class="math inline">\(O(NM)\)</span>,通常把上述操作称作 <strong>松弛(relax)</strong>。</p>
<p>在这 <span class="math inline">\(N\)</span> 次松弛之后,再执行第 <span class="math inline">\(N+1\)</span> 次操作,若还可以继续执行松弛操作,就说明图中存在负环,无解,输出 <span class="math inline">\(-1\)</span>。</p>
<p>最终答案即为 <span class="math inline">\(-dis_0\)</span>。</p>
<h3 id="c-代码-2">C++ 代码</h3>
<pre><code class="language-cpp">#include&lt;bits/stdc++.h&gt;
#define int long long
using namespace std;
const int inf=3e18;
const int maxn=4005;
int n,m;
struct Node{
        int u,v,w;
};
vector&lt;Node&gt; v;
int dis;
signed main(){
        cin&gt;&gt;n&gt;&gt;m;
   
    //建图 连边
        for(int i=1;i&lt;=m;i++){
                int l,r,s;
                cin&gt;&gt;l&gt;&gt;r&gt;&gt;s;
                v.push_back({l-1,r,s});
                v.push_back({r,l-1,-s});
        }
        for(int i=0;i&lt;n;i++) v.push_back({i+1,i,-1});
   
        //初始化
        for(int i=1;i&lt;=n;i++) dis=inf;
        dis=0;
   
    //Bellman-Ford计算最短路直接将第N+1次操作放入循环中
        for(int i=1;i&lt;=n+1;i++){
                for(Node e:v){
                        if(dis&gt;dis+e.w){
                                if(i==n+1){//若已经执行完n+1次松弛还可以继续执行,则无解
                                        cout&lt;&lt;-1&lt;&lt;endl;
                                        return 0;
                                }
                                dis=dis+e.w;
                        }
                }
        }
        cout&lt;&lt;-dis&lt;&lt;endl;       
        return 0;
}
</code></pre><br><br>
来源:https://www.cnblogs.com/AKDreamer-HeXY/p/18859257
頁: [1]
查看完整版本: AtCoder Beginner Contest 404 C-G(无F)题解