zoukankan      html  css  js  c++  java
  • 20210628模拟赛解题报告

    写在前面

    期望得分:(100+100+20 sim 100 = 220 sim 300pts)
    实际得分:(100+0+30 = 130pts) (因为教师机上的 lemon 不能用 lower_bound,这太傻逼了)
    实际得分:(100+100+30=230pts) (换了台能用的机子,结果 2e8 没跑过去,这太傻逼了)
    洛谷得分:(100+100+100=300pts) (把数据点扔洛谷上,啪的一下就过了,我 AK 了?!


    T1 是个树剖求 LCA 的板子, 10min 切掉,然后花了 10min 想了几种很怪的情况特判了;

    然后开 T2,一眼是期望,用了 10min 想到一个不错的思路,感觉很可做,20min 写出来了,过了样例,稍微算了一下没爆 longlong 就扔掉看 T3 了。

    T3 一眼以为是一个数位 DP,教练的题怎么天天考数位DP,设了个五维的状态 (f_{i,j,x,y,k}),发现连 (20\%) 的数据都开不下,并且不会转移。然后就弃了发呆。中间上撤锁吃了顿饭,感觉思路如泉水般涌现。回来稍微完善了一下就码出来了,感觉自己阿克了,找 KnightL 的暴力拍了一下,发现 (n&1=1) 时被 Hack 了,并且在本地机子和极限数据下代码跑了 5s+,感觉要凉。经过一阵紧张的调码环节后发现是自己式子推错了,改过了被 Hack 的部分过了,但在极限数据下依旧跑的很慢mmp。


    题目扔这儿,有兴趣的可以爆切了:T1,T2,T3

    题解写的太拉了,所以我成为了新的题解 /cy

    题解

    T1

    可以利用 dfs 序来求解,如果 (a)(b) 的祖先,当且仅当 (dfn_a le dfn_b < dfn_a + siz_a),预处理 (O(n)),查询 (O(1))

    也可以使用树剖求 LCA 去判断两个点之间的关系,时间复杂度 (O(n log n))

    也可以倍增求 LCA,需要 (O(n log n)) 的预处理,不过查询的复杂度都是 (O(log n))

    反正随便过啦

    /*
    Work by: Suzt_ilymics
    Problem: 不知名屑题
    Knowledge: 垃圾算法
    Time: O(能过)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define LL long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 1e5+5;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    struct edge {
        int to, nxt;
    }e[MAXN << 1];
    int head[MAXN], num_edge = 1;
    
    int n, m, rt;
    int dep[MAXN], fath[MAXN], siz[MAXN], son[MAXN], top[MAXN];
    bool vis[MAXN];
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    void add_edge(int from, int to) { e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge; }
    
    void dfs(int u, int fa) {
        dep[u] = dep[fa] + 1, fath[u] = fa, siz[u] = 1;
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            if(v == fa) continue;
            dfs(v, u);
            siz[u] += siz[v];
            if(siz[son[u]] < siz[v]) son[u] = v;
        }
    }
    
    void dfs2(int u, int tp) {
        top[u] = tp;
        if(son[u]) dfs2(son[u], tp);
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            if(v == fath[u] || v == son[u]) continue;
            dfs2(v, v);
        }
    }
    
    int Get_Lca(int x, int y) {
        while(top[x] != top[y]) dep[top[x]] < dep[top[y]] ? y = fath[top[y]] : x = fath[top[x]];
        return dep[x] < dep[y] ? x : y;
    }
    
    int main()
    {
        freopen("tree.in","r",stdin);
        freopen("tree.out","w",stdout);
        n = read();
        for(int i = 1, u, v; i <= n; ++i) {
            u = read(), v = read();
            if(v == -1) rt = u;
            else {
                add_edge(u, v), add_edge(v, u);
                vis[u] = vis[v] = true;
            }
        }
        dfs(rt, 0), dfs2(rt, rt);
        m = read();
        for(int i = 1, u, v; i <= m; ++i) {
            u = read(), v = read();
            int lca = Get_Lca(u, v);
            if(u == v || !vis[u] || !vis[v]) puts("0");
            else if(lca == u) puts("1");
            else if(lca == v) puts("2");
            else puts("0");
        }
        return 0;
    }
    

    T2

    考虑固定 (A) 序列,然后随便安排 (B) 的位置,显然这依旧是合法的。方案数是全排列的方案数 (n!)

    发现全排列不好枚举,考虑计算所有可能下 (a_i)(b_j) 的对决次数。

    固定这两个位置,其他的随便枚举,所以对决次数为 ((n-1)!),所以这两个人对 (A) 队分数的贡献为 (frac{(a_i - b_j)^2}{n} [a_i > b_j])

    然后枚举所有 (a_i) 与所有 (b_j) 对决就是答案

    (B) 队同理。所以:

    [ansa = sum_{i=1}^{n}sum_{j=1 && a_i > b_j}^n frac{(a_i - b_j)^2}{n} ]

    [ansb = sum_{i=1}^{n}sum_{j=1 && b_i > a_j}^n frac{(b_i - a_j)^2}{n} ]

    这样的复杂度是 (O(n^2)),显然过不掉。

    发现排序对答案没有影响,所以先对两个数组排序。

    (lower\_bound) 找到第一个比 (a_i) 大的点的位置 (x),答案变为

    [ansa = sum_{i=1}^{n}sum_{j=1}^x frac{(a_i - b_j)^2}{n} ]

    然后把平方差拆开,发现可以预处理前缀和,前缀平方和。然后每个元素就可以 (O(1)) 算了。

    总时间复杂度 (O(n log n)) ,瓶颈在二分那,不过也能过了。

    发现两个队都是单调的,所以每次二分找的位置一定是单调增的,可以用个指针,指针只会向右移动,所以复杂度是 (O(n)) 的。

    为了避免精度问题,可以最后除以 (n),算一下开 longlong 是不会爆的。

    代码是使用 (lower\_bound) 的版本。

    /*
    Work by: Suzt_ilymics
    Problem: 不知名屑题
    Knowledge: 垃圾算法
    Time: O(能过)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define LL long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 1e5+5;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    int n;
    LL a[MAXN], b[MAXN];
    LL suma[MAXN], sumb[MAXN];
    LL suma2[MAXN], sumb2[MAXN];
    LL ansa = 0, ansb = 0;
    double Ans;
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    int main()
    {
    //    freopen("mat.in","r",stdin);
    //    freopen("mat.out","w",stdout);
        n = read();
        for(int i = 1; i <= n; ++i) a[i] = read();
        for(int i = 1; i <= n; ++i) b[i] = read();
        sort(a + 1, a + n + 1);
        sort(b + 1, b + n + 1);
        for(int i = 1; i <= n; ++i) suma[i] = suma[i - 1] + a[i];
        for(int i = 1; i <= n; ++i) sumb[i] = sumb[i - 1] + b[i];
        for(int i = 1; i <= n; ++i) suma2[i] = suma2[i - 1] + a[i] * a[i];
        for(int i = 1; i <= n; ++i) sumb2[i] = sumb2[i - 1] + b[i] * b[i];
        for(int i = 1; i <= n; ++i) {
            int wz = lower_bound(b + 1, b + n + 1, a[i]) - b - 1;
            ansa += wz * a[i] * a[i] - 2 * a[i] * sumb[wz] + sumb2[wz];
        }
        for(int i = 1; i <= n; ++i) {
            int wz = lower_bound(a + 1, a + n + 1, b[i]) - a - 1;
            ansb += wz * b[i] * b[i] - 2 * b[i] * suma[wz] + suma2[wz];
        }
        ansa -= ansb;
        Ans = (double)ansa / n;
        printf("%.1f", Ans);
        return 0;
    }
    
    

    T3

    可以设个五维的状态 (f_{i,j,x,y,k}) 分别表示 当前位数,上一次填的数,总和,前 (n) 个数的和,奇数位的和 然后数位 DP。发现空间在 (20 \%) 的数据下都开不下,笑死。

    考虑换个思路,设 (f_{i,j}) 表示填了 (i) 位,总和为 (j) 的方案数。

    如何求 (f_{i,j})

    把它当做一个 01背包 ,一共 (|S|) 件物品选 (n) 次(为什么只需要算到 (n) 到后面就自然明白)。

    初始化 (|S|) 中的每个元素 (x) 选一次都是 (1),即 (f_{1,x} = 1)

    考虑只有前 (n) 个数和后 (n) 个数和相同的情况。利用乘法原理,总方案数为:

    [sum_{x=0}^{Max}f_{n,x}^2 ]

    其中 (Max) 表示所填的数的和的上限。

    在考虑第二个限制,发现和第一个限制一样都是填两次 (n) 个数,然后两次和相同,只不过填的位置换了换。

    所以在上面的基础上 ( imes 2) 即可。

    同时满足的情况重复计算了,考虑如何筛去。同时考虑两个条件的性质应该不难想。

    (k = n / 2) 表示前面 (n) 个数中有几个偶数位,设 (x) 表示前 (n) 个数中的偶数位和为 (x),设前 (n) 数总和为 (s)。其方案数可以用 (f_{k,x}) 表示。

    那么,前 (n) 个数中有 (n-k) 个奇数位,奇数位的和为 (k-x),方案数为 (f_{n-k,s-x})

    同理,后 (n) 个数中奇数位的情况应当与前 (n) 个数中偶数位的情况相同,后 (n) 个数中偶数位的情况应当与前 (n) 个数中奇数位的情况相同,方案数分别为 (f_{k,x},f_{n-k,s-x})

    利用乘法原理,要减去的总的贡献为

    [sum_{s=0}^{Max}sum_{x=0}^{s} f_{k,x}^2 imes f_{n-k,s-x}^2 ]

    极限情况下复杂度为 (O(10^8)),正常评测机是可以过的,但学校的古董机过不去。

    我们发现,前 (n) 个数奇数位填的数只和后 (n) 个数偶数位填的数相关。与另外两个无关。所以考虑把两部分先单独算出来在用乘法原理。

    (Max1,Max0) 分别表示前 (n) 个数奇数位填的最大位数和和偶数位填的最大位数和。

    要减去的总的方案数为

    [(sum_{x=0}^{Max0}f_{k,x}^2) imes (sum_{y=0}^{Max1} f_{n-k,y}) ]

    减去即可。

    复杂度为 (O(Max0+Max1))你会发现预处理极限情况下依旧是 (O(10^8))

    /*
    Work by: Suzt_ilymics
    Problem: 不知名屑题
    Knowledge: 垃圾算法
    Time: O(能过)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define int long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 1e5+5;
    const int INF = 1e9+7;
    const int mod = 999983;
    
    char s[11];
    int n, Ans = 0, sum = 0, k;
    int stc[11], sc = 0;
    int f[1010][10010];
    //int sum[10010], sum2[10010];
    // f[i][j] 表示填了 i 位,总和为 j 的方案数 
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    signed main()
    {
    //    freopen("num.in","r",stdin);
    //    freopen("num.out","w",stdout);
        n = read(); k = n / 2;
        cin >> s + 1;
        int len = strlen(s + 1);
        for(int i = 1; i <= len; ++i) {
            stc[++sc] = s[i] - '0'; // 其实这个行为挺傻逼的 
            f[1][stc[sc]] = 1;
        }
        if(n == 1) {
            cout<<sc<<endl;
            return 0;
        } 
        if(sc == 1) {
            puts("1");
            return 0;
        }
        sort(stc + 1, stc + sc + 1);
        int Max = n * stc[sc];
        for(int i = 2; i <= n; ++i) {
            for(int k = Max; k >= 0; --k) {
                for(int j = 1; j <= sc; ++j) {
                    f[i][k] = f[i][k] + f[i-1][k - stc[j]];
                    if(f[i][k] > mod) f[i][k] -= mod;
                }
            }
        }
        for(int i = 0; i <= Max; ++i) {
            Ans = Ans + f[n][i] * f[n][i] % mod;
            if(Ans > mod) Ans -= mod;
        }
        Ans = Ans * 2 % mod;
    //    cout<<Ans<<endl;
        for(int i = 0; i <= Max; ++i) {
            for(int j = 0; j <= i; ++j) {
    //            sum = f[k][j] * f[n-k][i-j] * f[n-k][i-j] % mod;
    //            sum = sum * f[k][j] % mod;
    //            cout<<"前 n 个偶数: "<<k<<" 前 n 个奇数:"<<n-k<<" 前n个数和:"<<i<<" 偶数占的和:"<<j<<"
    ";
    //            cout<<f[k][j]<<" "<<f[n-k][i-j]<<" "<<f[n-k][i-j]<<" "<<f[k][j]<<"
    ";
    //            cout<<i<<" "<<j<<" "<<sum<<endl;
    //            Ans -= sum;
                
    //            cout<<i<<" "<<j<<endl;
                Ans = (Ans - f[k][j] * f[n - k][i-j] % mod * f[n-k][i-j] % mod * f[k][j] % mod) % mod;
                //          前n个的偶数    前 n 个的奇数     后 n 个的偶数     后 n 个的奇数 
            }
        }
        Ans = (Ans + mod) % mod;
        cout<<Ans<<endl;
    //    for(int i = 0; i <= 6;++i) {
    //        for(int j = 0; j <= 8; ++j) {
    //            cout<<f[i][j]<<" ";
    //        }puts("");
    //    }
        return 0;
    }
    
    
  • 相关阅读:
    ul不加宽高
    获取元素的外部样式问题
    设置定时器、重启定时器要注意的问题
    php的var关键字
    抽象类(abstract class)和 接口(interface)
    __sleep和__wakeup
    类型约束
    TensorFlow 拾遗
    Datasets and Evaluation Metrics used in Recommendation System
    触龙——可解释推荐系统
  • 原文地址:https://www.cnblogs.com/Silymtics/p/14945293.html
Copyright © 2011-2022 走看看