2019年杭电暑期多校(第二场)

1005 Everything Is Generated In Equal Probability

题意

太长啦,就是模拟运行一个程序输出结果的期望值。

思路

绝大多数人都是打表找规律吧,反正最终结果就是 n 2 − 1 9 \frac {n^2-1} 9 9n21
然而我在现场的时候头铁自己推公式,就差一点就推出来了。。。。。赛后特别不甘心,就用自己推出来的公式重做了一次。

推导过程

①首先可以求出长度为n的序列,它的逆序数期望为 n ( n − 1 ) 4 \frac {n(n-1)} 4 4n(n1)
这是怎么来的呢,首先长度为1的时候贡献肯定为0,也就是 E [ 1 ] = 0 E[1]=0 E[1]=0。而长度为2时,看作在长度为1的基础上插入一个数,那么共有两个位置可以插入,那么这个序列为{1,2}和{2,1},其中{2,1}的贡献为1,那么 E [ 2 ] = E [ 1 ] + 1 2 E[2]=E[1]+\frac 1 2 E[2]=E[1]+21。那么多推几个就可以得到通项 E [ n ] = E [ n − 1 ] + n − 1 2 E[n]=E[n-1]+\frac {n-1} 2 E[n]=E[n1]+2n1,那么再等差数列求和一下,就可以知道 E [ n ] = n ( n − 1 ) 4 E[n]=\frac {n(n-1)} 4 E[n]=4n(n1)
于是乎完成了万里长征的第一步
②我们设 f [ i ] f[i] f[i]为长度为 i i i的序列运行程序的结果的期望
怎么求 f [ n ] f[n] f[n]的通项呢?首先 f [ 0 ] = f [ 1 ] = 0 f[0]=f[1]=0 f[0]=f[1]=0
设当前数组长度为n(n和N代表的意义不同),我们想要求 f [ n ] f[n] f[n],按照代码的意思,我们需要取这个数组的子序列递归运行求逆序数。若你从这n个数中取出r个数进行递归求解,本质上与一开始直接放入r个数求解是一样的,也就意味着得到的值就是 f [ r ] f[r] f[r]
那么从n个数里取长度为r的序列共有 C n r C_n^r Cnr种,题目还说了自己本身和空串都算作是子序列,n个数的子序列个数为 2 n 2^n 2n。嚯嚯嚯,这样 f [ n ] f[n] f[n]的通项也就可以得到了 f [ n ] = n ( n − 1 ) 4 + ∑ i = 1 n C n i f [ i ] 2 n f[n]=\frac {n(n-1)} 4+\frac {\sum_{i=1}^n{C_n^if[i]}}{2^n} f[n]=4n(n1)+2ni=1nCnif[i]
再将右侧的一个 f [ n ] f[n] f[n]移项化简得到
f [ n ] = n ( n − 1 ) 4 2 n 2 n − 1 + ∑ i = 1 n − 1 C n i f [ i ] 2 n − 1 f[n]=\frac {n(n-1)} {4} \frac{2^n}{2^n-1}+\frac {\sum_{i=1}^{n-1}{C_n^if[i]}}{2^n-1} f[n]=4n(n1)2n12n+2n1i=1n1Cnif[i]

③那么我们所求的最终答案就是 a n s = 1 N ∑ i = 1 N f [ i ] ans=\frac 1 N \sum_{i=1}^{N} f[i] ans=N1i=1Nf[i]
推导完毕,接下来只要用 O ( n 2 ) O(n^2) O(n2)预处理出 f [ i ] f[i] f[i],再求一次前缀和就好了,理论上这个式子还可以化简,但是实在化不动了,反正这个复杂度也能轻松过

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const int maxn = 3e3+20;


ll fa[maxn];
ll ifa[maxn];
ll f[maxn];
ll qmod(ll a,ll b)
{
    ll c=1;
    while(b)
    {
        if(b&1)
        {
            c=c*a%mod;
        }
        a=a*a%mod;
        b>>=1;
    }
    return c;
}

inline add(ll &a,ll b)
{
    a+=b;
    if(a>mod)
        a-=mod;
}

void init()
{
    f[1]=0;
    f[0]=0;
    fa[0]=ifa[0]=1;
    for(int i=1;i<=3000;i++)
    {
        fa[i]=fa[i-1]*i%mod;
    }
    ifa[3000]=qmod(fa[3000],mod-2);
    for(int i=3000-1;i>0;i--)
    {
        ifa[i]=ifa[i+1]*(i+1)%mod;
    }

    for(int i=2;i<=3000;i++)
    {
        f[i]=i*(i-1)*qmod(4,mod-2)%mod*qmod(2,i)%mod;
        for(int j=1;j<i;j++)
        {
            add(f[i],fa[i]*ifa[i-j]%mod*ifa[j]%mod*f[j]%mod);
        }
        f[i]=f[i]*qmod((qmod(2,i)-1),mod-2)%mod;
    }
    for(int i=2;i<=3000;i++)
    {
        add(f[i],f[i-1]);
    }
}

int main() {
    ll N;
    init();
    while(cin>>N)
    {
        cout<<f[N]*qmod(N,mod-2)%mod<<endl;
    }

    return 0;
}

1011 Keen On Everything But Triangle

题意

求区间内能构成的最大三角形的周长

题解

因为若无法构成三角形,当区间内的数满足斐波那契数列时,区间内的数才最多,然而即使这样,也不超过50个数,也就意味着区间内的数只要超过50个,一定能构成三角形。那么剩下的事情就是用主席树求区间第一大,第二大,一直往下跑就完事了。这里算三角形时范围是3e9,爆int了

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

const int maxn = 2e5 + 20;
int lson[maxn * 30], rson[maxn * 30], tree[maxn * 30];
ll a[maxn], ha[maxn], T[maxn];
int n, q, len, tot;

void init_hash()
{
    sort(ha + 1, ha + 1 + n);
    len = unique(ha + 1, ha + 1 + n) - ha - 1;
    for (int i = 1; i <= n; i++)
    {
        a[i] = lower_bound(ha + 1, ha + 1 + len, a[i]) - ha;
    }
}
void init()
{
    tot = 0;
    init_hash();
}

int build(int l, int r)
{
    int rt = tot++;
    tree[rt] = 0;
    if (l != r)
    {
        int mid = (l + r) >> 1;
        lson[rt] = build(l, mid);
        rson[rt] = build(mid + 1, r);
    }
    return rt;
}

int update(int rt, int pos, int val)
{
    int newroot = tot++;
    int temp = newroot;
    tree[newroot] = tree[newroot] + val; //更新区间和
    int l = 1, r = len;
    while (l < r) //这里迭代更新比递归快一点
    {
        int mid = (l + r) >> 1;
        if (pos <= mid)
        {

            lson[newroot] = tot++;
            rson[newroot] = rson[rt];
            newroot = lson[newroot];
            rt = lson[rt];
            r = mid;
        }
        else
        {
            rson[newroot] = tot++;
            lson[newroot] = lson[rt];
            newroot = rson[newroot];
            rt = rson[rt];
            l = mid + 1;
        }
        tree[newroot] = tree[rt] + val;
    }
    return temp;
}

int query(int st, int end, int k)
{
    int l = 1, r = len;
    while (l < r)
    {
        int mid = (l + r) >> 1;
        if (tree[lson[end]] - tree[lson[st]] >= k)
        {
            r = mid;
            st = lson[st];
            end = lson[end];
        }
        else
        {
            l = mid + 1;
            k -= tree[lson[end]] - tree[lson[st]];
            st = rson[st];
            end = rson[end];
        }
    }
    return l;
}

int main()
{
    while(scanf("%d%d", &n, &q)!=EOF){
        for (int i = 1; i <= n; i++)
        {
            scanf("%lld", &a[i]);
            ha[i] = a[i];
        }
        init();
        T[0] = build(1, len);
        for (int i = 1; i <= n; i++)
        {
            T[i] = update(T[i - 1], a[i], 1);
        }
        for (int i = 1; i <= q; i++)
        {
            int l, r;
            scanf("%d%d", &l, &r);
            if (r - l + 1 < 3)
            {
                printf("-1\n");
                continue;
            }
            else
            {
                int x, y, z;
                ll ans = -1;
                x = query(T[l - 1], T[r], r-l+1);
                y  =query(T[l - 1], T[r], r-l);

                for (int i = r - l -1; i > 0;i--)
                {
                    z = query(T[l - 1], T[r], i);
                    if(ha[x]<ha[y]+ha[z])
                    {
                        ans = ha[z] + ha[x] + ha[y];
                        break;
                    }
                    x = y, y = z;
                }
                printf("%lld\n", ans);
            }
        }
    }  
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值