• HZOI0718 NOIp模拟 考试报告//0718




    写在前面:

    我是机房透明人



     考试概况:

    考试风格为思维题,普遍思维难度大而代码量小.

    T1 : 图论+答案统计  tps : 0  time : 0.5h

    T2 : 线性数学优化  tps : 30  time : 1h

    T3 : 树形dp  tps : 5  time : 2h

    sum : 35  rank33



    T1

    考场上没想到性质,直接跳了.后来想想也许搞T1能搞出来.

    简化题意,把每条边拆成两条,并且这两条的编号相同,要求拆掉两条编号不同的边之后能跑一个欧拉通路,求有多少种不同的拆法.

    由于每条边都拆成了两条所以每一个点在刚开始时度数都是偶数,随便跑欧拉回路.

    考虑怎么拆能合法,手活样例之后发现只要拆有相同点的两条边就行了;

    [实际上,也可以理解为跑一个欧拉回路后后退两步,因为原图上任选一个起点都能跑欧拉回路,所以后退的两步也是随意的,只要这两步是连着的就行了]

    用d[]表示点的度数(同一编号不重复计算)

    ans += (d[i]-1) * d[i] / 2;

    再考虑自环问题,原题说可以存在自环,自环对于能不能跑欧拉通路没有影响,但自环会影响拆边

    再手活数据发现可以拆一个环加任意一条正常边,或者是拆两个不同的环

    ans += clr * (m - clr) + clr * (clr - 1) / 2

    注意除二的问题,同机房两个同学除二出问题导致只有40分

    再考虑联通问题,合法的图应该是一张联通图,或者是一张连通图加上一堆没有边的点

    不合法的图就是两张或更多的图或者是散点上有自环

    比较科学的判法是用并查集,每次merge边的两个端点,自环不连边(因为连了也只是fa[x]=x)

    弄完之后再遍历一遍所有的边,查每条边的两个端点是不是在同一个集里面

    发现由于刚开始merge了每条边的两个点所以查每条边的一个点就行了

    鬼数据不全开long long不给过(什么东西)

    #include<bits/stdc++.h>
    
    using namespace std ;
    
    const long long MAXN = 100010;
    long long a[MAXN],b[MAXN],fa[MAXN],d[MAXN],n,m;
    long long ans,crl;
    
    long long Find(long long x){
        return (fa[x] == x) ? x : fa[x] = Find(fa[x]);
    }
    
    void Merge(long long x,long long y){
        long long xx = Find(x),yy = Find(y);
        fa[xx] = yy;
    }
    
    signed main(){
        ios::sync_with_stdio(false);
        cin>>n>>m;
        for(long long i=1;i<=n;i++){
            fa[i] = i;
        }
        for(long long i=1;i<=m;i++){
            cin>>a[i]>>b[i];
            if(a[i]==b[i]){
                crl ++;
            }
            else {
                ++d[a[i]];
                ++d[b[i]];
            }
        }
        long long FUCK_YOU = Find(a[1]);//
        for(long long i=2;i<=m;i++){
            long long NMSL = Find(a[i]);
            if(NMSL != FUCK_YOU){
                cout<<"0"<<endl;
                return 0;
            }
        }
        for(long long i=1;i<=n;i++){
            ans += (d[i] * (d[i]-1)) /2;
        }
        ans += crl * ((long long)m - crl);
        ans += crl * (crl-1) /2;
        cout<<ans<<endl;
        return 0;
    }


     T2 : 砍树

    不会,我滚

    考场上打了个暴力 30tps



    T3 : 超级树

    一个人疯狂的爱上某一个人,某一件事,某一个物,终究爱的只是脑海里的那种执念.

    考场上想的深度还是不够,或者说是想偏了

    首先有一个非常明显的做法,一棵k层的超级树可以由两棵k-1的超级树上的每一个点都向一个额外的点连边而构成

    形象点就是两棵同样大小的树拼起来就能成一棵更大的树;

    那么考虑设计状态为dp[i]为有k层的超级树具有的答案,由dp[i-1]向dp[i]转移

    分为4种情况:

      1 . 这条路径不过根节点,经过一棵子树

      2 . 这条路径过根节点,经过一棵子树

      3 . 这条路径过根节点,经过两颗子树

      4 . 这条路径经过根节点,不经过子树

    对于1,分别有左右两棵子树 dp[i] += dp[i-1] * 2

    对于3,每条dp[i-1]里的每条路径末端都可以连向根节点,另一棵子树也是如此,那么考虑顺序(乘二) dp[i] += dp[i-1] * dp[i-1] * 2

    对于4,dp[i] += 1

    对于2,无法统计.(我以考场上的2h庄严宣判无法统计)

    很容易发现对于在一棵超级树上加一个点这种操作来说,可以与原来路径的头或者尾相连,但是同时他也可以作为连接两条路径中间的点,

    而这种统计是现有的状态无法做到的,我们需要加另一个状态来让他可以支持这种统计。

    但是这个状态很难想,观察我们的无法维护的点是将几条路径连起来,那么我们可以设置另一个状态为有j条不交路径的方案数有dp[i][j]种

    边界为dp[1][0] = dp[1][1] = 1,目标为dp[n][1]

    例如dp[2][0] = 1,dp[2][1] = 9,dp[2][2] = 7,dp[2][3]=1;

    然后就可以开始写方程了:

    1.新点不连边并且不加入统计 dp[i][l+r] += dp[i-1][l] * dp[i-1][r]

    2.新点不连边并且加入统计 dp[i][l+r+1] += dp[i-1][l] * dp[i-1][r]

    3.新点连边,与左右子树中任意一条路径的首尾分别相连dp[i][l+r] += dp[i-1][l] * dp[i-1][r] * (l+r) * 2

    4.新点连边,与子树中两条路径相连(选定两条路径后有两种连法),dp[i][l+r-1] += dp[i-1][l] *dp[i-1][r] * (l+r) * (l+r-1) / 2 * 2

    写完之后开始观察数据范围,k<=300,而第二维的上限看上去是2^300的,难道这种设计不可取吗?

    注意观察dp方程,第二维每次推动时最多减少1,而我们的目标是dp[n][1]

    那么对于每一层来说,能影响到dp[n][1]的最远第二维下标就是dp[i][n-i+1]

    所以每次循环到n-i+1就行了,但发现这种写法样例都过不了,why?

    回头看n-i+1的地方,计算机每次都处理到整数部分,所以那个看上去像y=-x的方程应该是向外的阶梯型函数,

    试着把循环上界变为n-i+2,发现就能过了。

    奇怪的是这个代码只能过95分(全开longlong还挂)

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int MAXN = 410;
    long long mod, n;
    long long dp[MAXN][MAXN];
    
    long long DP(){
        dp[0][1] = 1;
        dp[1][0] = dp[1][1] = 1;
        for(int i=2;i<=n;i++){
            for(int l=0;l<=n;l++){
                for(int r=0;r<=n;r++){
                    long long num  = dp[i-1][l] * dp[i-1][r] % mod;
                    if(l+r <= n-i+2){
                        dp[i][l+r] += num;
                        dp[i][l+r] %= mod;
                        dp[i][l+r] += num * (l+r)  * 2;
                        dp[i][l+r] %= mod;
                    }
                    if(l+r+1 <= n-i+2){
                        dp[i][l+r+1] += num;
                        dp[i][l+r+1] %= mod;
                    }
                    if(l+r-1 <= n-i+2){
                        dp[i][l+r-1] += num * (l+r) * (l+r-1);
                        dp[i][l+r-1] %= mod;
                    }
                }
            }
        }
        return dp[n][1];
    }
    
    int main(){
        ios::sync_with_stdio(false);
        cin>>n>>mod;
        cout<<DP()<<endl;
        return 0;
    }

    TAG : SIN_XIII

    因为Linux打不出来圈九了QAQ

  • 相关阅读:
    中国科学院大学数字集成电路作业开源——组合逻辑章节
    中国科学院大学数字集成电路作业开源——时序逻辑与存储器章节
    模拟集成电路实践记录_共源极差分放大器
    中国科学院大学数字集成电路作业开源——64位FFT/IFFT数字逻辑运算电路的Verilog实现
    中国科学院大学数字集成电路作业开源——函数运算器章节
    中国科学院大学数字集成电路作业开源——算数逻辑章节
    【20220306】连岳摘抄
    【20220311】连岳摘抄
    【20220304】连岳摘抄
    【20220312】连岳摘抄
  • 原文地址:https://www.cnblogs.com/SINXIII/p/11206369.html
Copyright © 2020-2023  润新知