Educational Codeforces Round 81 (Rated for Div. 2)

A - Display The Number

题意:给n根火柴,拼出最大的数字。

题解:肯定是数字越多越大,所以尽可能多拿最便宜的2根火柴一个“1”,多余的肯定是拿一个“7”,由于n>=2,没有特例。

void test_case() {
    int n;
    scanf("%d", &n);
    if(n % 2 == 1) {
        printf("%d", 7);
        n -= 3;
    }
    while(n) {
        printf("%d", 1);
        n -= 2;
    }
    printf("\n");
}

C - Obtain The String

题意:给两个字符串s和t,要求从t里面选尽可能少的次数,每次选t的一个子序列,然后接在初始为空的字符串z的末尾,用最少的次数使得z变成s。

题解:由于每次选的是子序列,所以每次可以贪心匹配最长的串,所以可以整出一个显而易见的dp。nxt[i][j]表示[i,tl]中的下一个字母j的下标,这个可以倒着直接转移出来,然后枚举s的每个字符,假如还能往后匹配就往后匹配,否则必须从头开始匹配。

D - Same GCDs

题意:给定 \(a\)\(m\) ,求 \(\sum\limits_{i=a}^{a+m-1}[gcd(i,m)=gcd(a,m)]\)

首先设 \(g=gcd(a,m)\) 那么求 \(\sum\limits_{i=a}^{a+m-1}[gcd(i,m)=g]\)

\(\sum\limits_{i=\lfloor\frac{a}{g}\rfloor}^{\lfloor\frac{a+m-1}{g}\rfloor}[gcd(i,\frac{m}{g})=1]\)

变成求一个经典问题, \(n\) 以内与 \(m\) 互质的数的个数。 \(\sum\limits_{i=1}^{n}[gcd(i,m)=1]\)

模板库里面掏出这个问题的接口,调用可以得到结果。


其实由于 \(gcd(a,m)=gcd(a\mod m,m)\) (辗转相除法?欧几里得算法?),

\(\sum\limits_{i=a}^{a+m-1}[gcd(i,m)=g]\)\(\sum\limits_{i=1}^{m}[gcd(i,m)=g]\) 除以g后即为为欧拉函数的定义 \(\varphi(\frac{m}{g})\)

去模板库里面掏出这个问题的接口,调用可以得到结果。

E - Permutation Separation

题意:给一个[1,n]的permutation,称为A。每个数字带有一个权重p,要求把序列初始分为两个非空的前缀L和后缀R,然后搬动其中的元素使得P中的每个元素小于S中的每个元素,或其中之一为空。求最小代价。

题解:有一个显然的n^2暴力,枚举切割前后缀的位置,然后再枚举搬动元素的分界线。昨天晚上学长提了一下线段树,想了一天终于有点明白了,用线段树可以快速转移出不同搬动元素的分界线的最小值,然后只需要枚举切割前后缀的位置就可以了。

从最简单的情况入手:设一棵n个叶子的线段树st,其中每个节点[l,r]表示把[l,r]区间内的数分为前后两块所需要的最小代价cost,初始每个数字叶子会被分出自己的初始属性L或者R,当两个L叶子合并或者两个R叶子合并不需要任何代价。左边是L右边是R的节点本身就合法,合并也不需要任何代价,左边是R右边是L的话,要么把L移动到R,要么把R移动到L,代价就是两者cost的min。

由此出发,考虑更一般的情况设cost(l,r)表示把[l,r]区间分为两堆不交叉的L和R的最小代价,costL(l,r)为把[l,r]中所有初始在R的集合搬到L的代价(维护就是简单加法),costR(l,r)同理。

那么当区间[l,m],[m+1,r]合并,可以口胡出,分界线必定在[l,m]或者[m+1,r]之中。假如分解线在[l,m]中,那么cost(l,r)=cost(l,m)+costR(m+1,r)(把前缀中的大的元素全部移动到后缀),假如分界线在[m+1,r]中,同理cost(l,r)=cost(m+1,r)+costL(l,m)。

所以每次就把原来的permutation里面的分界线元素的costL和costR属性取反,只需要在线段树里面Update一下。

int n;
int valL[200005], valR[200005];
int a[200005];

struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
    static const int MAXN = 200000;
    ll cost[(MAXN << 2) + 5];
    ll costL[(MAXN << 2) + 5];
    ll costR[(MAXN << 2) + 5];

    void PushUp(int o) {
        cost[o] = min(cost[ls] + costR[rs], cost[rs] + costL[ls]);
        costL[o] = costL[ls] + costL[rs];
        costR[o] = costR[ls] + costR[rs];
    }

    void Build(int o, int l, int r) {
        if(l == r) {
            cost[o] = 0;
            costL[o] = valL[l];
            costR[o] = valR[l];
            //printf("[%d,%d],cost=%d costL=%d costR=%d\n",l,r,cost[o],costL[o],costR[o]);
        } else {
            int m = l + r >> 1;
            Build(ls, l, m);
            Build(rs, m + 1, r);
            PushUp(o);
            //printf("[%d,%d],cost=%d costL=%d costR=%d\n",l,r,cost[o],costL[o],costR[o]);
        }
    }

    void Update(int o, int l, int r, int q) {
        if(l == r) {
            cost[o] = 0;
            costL[o] = valL[l];
            costR[o] = valR[l];
            //printf("[%d,%d],cost=%d costL=%d costR=%d\n",l,r,cost[o],costL[o],costR[o]);
        } else {
            int m = l + r >> 1;
            if(q <= m)
                Update(ls, l, m, q);
            if(q >= m + 1)
                Update(rs, m + 1, r, q);
            PushUp(o);
            //printf("[%d,%d],cost=%d costL=%d costR=%d\n",l,r,cost[o],costL[o],costR[o]);
        }
    }

    ll Query(int o, int l, int r, int ql, int qr) {
        if(ql <= l && r <= qr) {
            return cost[o];
        } else {
            int m = l + r >> 1;
            ll res = LINF;
            if(ql <= m)
                res = Query(ls, l, m, ql, qr);
            if(qr >= m + 1)
                res = min(res, Query(rs, m + 1, r, ql, qr));
            return res;
        }
    }
#undef ls
#undef rs
} st;

void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &valL[a[i]]);

    st.Build(1, 1, n);
    ll ans = LINF;
    for(int i = 1; i < n; ++i) {
        valR[a[i]] = valL[a[i]];
        valL[a[i]] = 0;
        /*for(int j=1;j<=n;++j)
            printf(" %d",valL[a[j]]);
        puts("");
        for(int j=1;j<=n;++j)
            printf(" %d",valR[a[j]]);
        puts("");*/
        st.Update(1, 1, n, a[i]);
        ans = min(ans, st.Query(1, 1, n, 1, n));
        //printf("ans=%d\n",st.Query(1, 1, n, 1, n));
        //puts("---");
    }
    printf("%lld\n", ans);
}

F - Good Contest

2020东秦ccpc wannfly camp好像见过?错过了一场上橙的机会,不仅如此还在蓝名苟住了?

题意:给一堆n个(至多50个)[li,ri],第i个数等概率出现在[li,ri]之间,求整个序列没有任何逆序的概率。

题解:因为至多50个,所以可以随便乱搞。假如[l,r]的区间不大,可以有一种简单的dp方法:

\(dp[i][j]\) 表示前 \(i\) 个数没有逆序,且最后一个数大小为 \(j\) 时,整个序列没有任何逆序的概率,注意这里最后一位是 \(j\) 的概率应该是 \(1\)


\(j\) 在合法范围内:
\(dp[i][j]=\frac{1}{r_{i-1}-l_{r-1}+1}\sum\limits_{k=0}^{j}dp[i-1][k]\)

否则:
\(dp[i][j]=0\)

可惜这里[l,r]范围好大哦。

posted @ 2020-01-30 01:50  KisekiPurin2019  阅读(210)  评论(0编辑  收藏  举报