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

原文地址:https://www.cnblogs.com/SINXIII/p/11206369.html