Educational Codeforces Round 78 (Rated for Div. 2)

A. Shuffle Hashing (CF 1278 A)

题目大意

给定两个字符串\(a,b\),现改变\(a\)串中的字母顺序,问是否存在某种顺序在\(b\)串中出现。

解题思路

先对\(a\)进行排序,由于字符串长度最多只有\(100\),我们就枚举\(b\)串中的起始位置,然后把长度为\(a.size()\)的子串截取下来排序看看是否和\(a\)相等即可。复杂度\(O(n^2logn)\)
\((CF\)上的\(dalao\)的思维好快\(qwq)\)

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;

bool check(void) {
    string a,b;
    cin>>a>>b;
    sort(a.begin(),a.end());
    int la=a.size();
    int lb=b.size();
    for(int i=0;i<lb;++i){
        if (i+la>lb) break;
        string c(b,i,la);
        sort(c.begin(),c.end());
        if (a==c) return true;
    }
    return false;
}

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int kase; 
    cin>>kase;
    while(kase--) if (check()) printf("YES\n"); else printf("NO\n");
    return 0;
}


B. A and B (CF 1278 B)

题目大意

给定两个数\(a,b\),第一次选\(a\)\(b\)\(1\),第二次选\(a\)\(b\)\(2\),第三次选\(a\)\(b\)\(3\),依次类推,问最少多少次操作使得\(a,b\)相等。

解题思路

\(dis=abs(a-b)\),我们进行了\(n\)次操作,这\(n\)次操作中有若干次是使\(dis\)变大,有若干次是使\(dis\)变小,最终应有\(dis+x-y=0\),其中\(x+y=\dfrac {n\left( n+1\right) }{2}\),联立这两个式子消去\(y\)\(dis+2*x=\dfrac {n\left( n+1\right) }{2}\)
这是个关于\(n\)的二次函数,其中\(n\)是单调递增的,那么我们需要找到最小的非负整数\(x\),使得\(n\)是非负整数即可。
解这个一元二次方程即可得到

\[n=\dfrac {-1+\sqrt {1+16x+8dis}}{2} (另解小于0舍去) \]

我们对\(x\)\(0\)开始枚举找到第一个\(1+16x+8dis\)是某奇数的平方即可(代码被注释的那段)
然后我看了下\(CF\)上的前排\(dalao\)们发现他们写的思路清奇且一模一样\(0.0\)
由于\(x\)是非负数所以有\(\dfrac {n\left( n+1\right) }{2}\geq dis\),然后我们对等式\(dis+2*x=\dfrac {n\left( n+1\right) }{2}\)两边对\(2\)取模,即可得到\(dis%2=\dfrac {n\left( n+1\right) }{2} %2\),所以我们对\(n\)\(1\)开始,找到第一个\(n\)使得\(\dfrac {n\left( n+1\right) }{2} \geq dis\)\(dis\)\(\dfrac {n\left( n+1\right) }{2}\)的奇偶性相同,那么就一定存在一个非负整数\(x\)使得\(dis+2*x=\dfrac {n\left( n+1\right) }{2}\)
\((\)这就是大佬\(qwq)\)

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

/* long long a,b,dis,qwq;

bool check(long long x){
    long long qaq=sqrt(x);
    if (qaq*qaq==x) {
        if (qaq&1) return true;
    }
    else return false;
}

void Input(void) {
    read(a);
    read(b);
    //if (dis==0) {printf("0\n"); return;}
    dis=8ll*ABS(a-b)+1ll;
    for(int i=0;1;++i){
        if (check(dis+16*i)) {qwq=sqrt(dis+16*i); break;}
    }
    printf("%lld\n",((qwq-1ll)/2ll));
} */

void Input(void){
    long long a,b;
    read(a); read(b);
    long long dis=ABS(a-b);
    long long sum=0,cnt=0;
    while(sum<dis||((sum&1)!=(dis&1)))
        sum+=++cnt;
    printf("%lld\n",cnt);
}

void Solve(void) {}

void Output(void) {}

int main(void) {
    //ios::sync_with_stdio(false);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int kase; read(kase);
    for (int i = 1; i <= kase; i++) {
        //printf("Case #%d: ", i);
        Input();
        Solve();
        Output();
    }
    return 0;
}


C. Berry Jam (CF 1278 C)

题目大意

\(2n\)个数字,每个数字是\(1\)或者\(2\),现在从中间开始,每次往左或往右删去最近的一个数,现需要使\(1\)\(2\)的个数相等,求最小删除数的个数。

解题思路

我们枚举往左删除的端点,看看右端点最短要延伸到哪里。假设左端点位置为\(i\),除去\([i,n]\)\(1\)\(2\)的个数后,\(1\)\(2\)的差值为\(dis\),我们需要知道最小的右端点\(r\),使得区间\([n+1,r]\)\(1\)\(2\)的差值也为\(dis\),这样我们除去\([n+1,r]\)中的\(1\)\(2\)后,剩余的\(1\)\(2\)的个数就相等了。而最小的\(r\)我们可以用\(unordered\_map\)来维护,记录右半部分\(1\)\(2\)的差值为\(d\)的最小位置为\(map[d]\),那此时删除的个数就是\((n-i+1)+map[d]-n\)。这样复杂度就是\(O(n)\)了。
其实由于\(n\)最多\(1e5\),我们开个\(4e5\)的数组来记录位置也是可以的。

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

unordered_map<int,int> qwq;

int n,dis,ans;

const int N=1e5+8;

int a[N],sum[3];

void Input(void) {
    qwq.clear();
    read(n);
    sum[1]=sum[2]=0;
    for(int i=1;i<=n;++i) {
        read(a[i]);
        ++sum[a[i]];
    }
    int s[3];
    s[1]=s[2]=0;
    for(int u,i=1;i<=n;++i){
        read(u);
        ++s[u];
        if (qwq[s[1]-s[2]]==0) qwq[s[1]-s[2]]=i;
    }
    sum[1]+=s[1];
    sum[2]+=s[2];
}

void Solve(void) {
    dis=sum[1]-sum[2];
    if (dis==0) ans=0;
    else{
        if (qwq[dis]) ans=qwq[dis];
        else ans=2147483647;
        for(int i=n;i>=1;--i){
            if (a[i]==1) --dis;
            else ++dis;
            if (dis==0) ans=MIN(ans,n-i+1);
            else if (qwq[dis]) ans=MIN(ans,n-i+1+qwq[dis]);
        }
    }
    printf("%d\n",ans);
}

void Output(void) {}

int main(void) {
    //ios::sync_with_stdio(false);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int kase; read(kase);
    for (int i = 1; i <= kase; i++) {
        //printf("Case #%d: ", i);
        Input();
        Solve();
        Output();
    }
    return 0;
}


D. Segment Tree (CF 1278 D)

题目大意

真·线段树
给定n条线段,端点值互不相同,如果两条线段有交叉部分(包含不算),则这两条线段之间连一条边。问最终这些线段形成的图是不是棵树。

解题思路

注意到线段右端点\(1\leq l<r\leq 2n\),这就意味着所有线段的两个端点的取值都在\([1,2n]\)中。
我们先对线段的左端点进行排序,依次考虑每个线段的与哪些线段有连边。
当前考虑的是第\(i\)条线段,由于连边条件具有对称性,我们可以只考虑第\(1\)条到第\(i-1\)条线段与第\(i\)条线段。
由于前面的线段的左端点\(l\)均小于\(l_i\),那么我们只要找到其右端点大于\(l_i\)且小于\(r_i\)的线段,它们会有连边。而我们要找的右端点是个连续的范围,那么我们可以把\(1\)\(i-1\)的线段的右端点丢到\(set\)里面,这样我们就可以在\(O(logn)\)的时间内找到对应的线段。
而当连的边数大于等于\(n\)时,此时不可能是棵树,直接\(break\)了。最后再判断边数是否是\(n-1\)并再\(DFS\)遍历一遍看看是不是一个连通块即可。复杂度\(O(nlogn)\)

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=5e5+8;

set<pair<int,int>> qwq;

vector<pair<int,int>> line;

vector<int> edge[N];

vector<bool> sign;

int n;

void DFS(int x){
    sign[x]=true;
    for(auto i:edge[x]) if (!sign[i]) DFS(i);
}

bool Solve(void) {
    read(n);
    for(int u,v,i=1;i<=n;++i){
        read(u);
        read(v);
        line.push_back(make_pair(u,v));
    }
    sort(line.begin(),line.end());
    int cnt=0;
    for(int i=0;i<n;++i){
        auto it=qwq.lower_bound(make_pair(line[i].first,0));
        while(it!=qwq.end()&&(*it).first<=line[i].second){
            ++cnt;
            if (cnt>=n) return false;
            edge[i].push_back((*it).second);
            edge[(*it).second].push_back(i);
            ++it;
        }
        qwq.insert(make_pair(line[i].second,i));
    }
    if (cnt<n-1) return false;
    sign.resize(n);
    DFS(0);
    for(auto i:sign)
        if (i==false) return false;
    return true;
}

int main(void) {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    if (Solve()) printf("YES\n"); else printf("NO\n");
    return 0;
}


E. Tests for problem D (CF 1278 E)

题目大意

上一题的逆向,给定一棵带标号的树,要求构造一组线段使之按照上题连线的规则,形成给定的树。输出每个标号对应的线段的左右端点。端点唯一且小于等于\(2n\).

解题思路

我们考虑第一个线段,\(l_1=1\),而因为第一个线段与\(k_1\)个线段有连线,故\(r_1-l_1-1=k_1\),即第一个线段之间的点用来存放与第一条线段相连的左端点。
然后我们考虑与第一条线段相连的线段,稍加分析可以知道从第一条线段右端点往左开始考虑会比较方便。
则对于第二条线段,它的\(l_2=r_1-1\),除去第一条线段,第二条线段与\(k_2\)条线段相连,那么\(r_2-l_1-1=k_2\)。其他的依次类推,最后我们可以形成一张

看似比较复杂的构造方法,其实用\(DFS\)即可很容易的实现。
我们依次为与一个线段相连的线段的左端点安排位置,安排完后下一个位置就是该线段右端点的位置,然后倒序遍历与这个线段相连的线段,重复同样操作即可。

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=5e5+8;

vector<int> edge[N];

int n,k;

int l[N],r[N];

void DFS(int u,int fa){
    for(auto v:edge[u])
        if (v!=fa) l[v]=++k;
    r[u]=++k;
    reverse(edge[u].begin(),edge[u].end());
    for(auto v:edge[u])
        if (v!=fa) DFS(v,u);
}

int main(void) {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    read(n);
    for(int u,v,i=1;i<=n;++i){
        read(u);
        read(v);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    k=0;
    l[1]=++k;
    DFS(1,1);
    for(int i=1;i<=n;++i) printf("%d %d\n",l[i],r[i]);
    return 0;
}


F. Cards (CF 1278 F)

题目大意

给定\(n,m,k\),有\(m\)张牌,其中一张为鬼牌。现进行\(n\)次操作,每次操作,对\(m\)张拍进行洗牌,从上到下,拿第一张牌,看牌,放牌。记拿的第一张牌是鬼牌的次数为\(x\),问\(x^k\)的期望值。

解题思路

根据期望的定义我们可以很容易知道答案就是:

\[\sum\limits_{i=0}^{n}C_{n}^{i}\times (\dfrac{1}{m})^{i}\times (1-\dfrac{1}{m})^{n-i}\times i^k \]

但直接算会超时,我们得另想办法。

经过观察我们发现这个式子和二项式展开式\((\dfrac{x}{m}+1-\dfrac{1}{m})^n=\sum\limits_{i=0}^{n}C_{n}^{i}\times (\dfrac{1}{m})^{i}\times (1-\dfrac{1}{m})^{n-i}\times x^i\)十分相像,差别就是\(i^k\)

我们可以先对这个式子两边求一阶导,这样右边就变成了\(\sum\limits_{i=0}^{n}C_{n}^{i}\times (\dfrac{1}{m})^{i}\times (1-\dfrac{1}{m})^{n-i}\times i\times x^{i-1}\),我们对等式两边再乘以\(x\),再求一次导,重复\(k\)次,我们右边就有\(i^k\),此时取\(x=1\),右边就是我们想要的式子了,现在考虑左边该如何计算。

由于\(m\)是常数,我们把\(m\)扔到一边,考虑\((x+m-1)^n\)在求导以及乘以\(x\)下的变化。

我们可以发现得到的因子都是形如\(x^i\times (x+m-1)^{n-i}\),那么我们设\(A_i=x^i\times (x+m-1)^{n-i}\),它的系数为\(B_{ij}\)\(j\)表示求了\(j\)次导。那么\(B_{ij}A_i\)求导再乘以\(x\)后产生了两项,一项是\(i\times B_{ij}\times A_{i}\)贡献给了\(B_{i(j+1)}\times A_{i}\),另一项\((n-i)\times B_{ij}\times A_{i+1}\)贡献给了\(B_{(i+1)(j+1)}\times A_{i+1}\)

因此我们可以\(O(k^2)\)计算出求导了\(k\)次后,各个\(A_i\)的系数,代入\(x=1\),计算结果,最后再除以\(m^n\)即可得到最终答案。

注意当\(n<k\)的时候,\(A_n\)求导后的贡献没有\(A_{n+1}\)的项。

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
typedef vector<PII> VPII;
typedef vector<LL> VL;
typedef pair<LL,LL> PLL;
typedef vector<PLL> VPLL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const LL mo=998244353;

LL qpower(LL a,LL b){
    LL qwq=1;
    while(b){
        if (b&1) qwq=qwq*a%mo;
        a=a*a%mo;
        b>>=1;
    }
    return qwq;
}

LL inv(LL x){
    return qpower(x,mo-2);
}

int main(void) {
    LL n,m,k;
    read(n);
    read(m);
    read(k);
    LL a[k+8]={0};
    a[0]=1;
    for(int i=1;i<=k;++i){
        for(int j=i-1;j>=0;--j){
            if (j<n) a[j+1]=(a[j+1]+a[j]*(LL)(n-j)%mo)%mo;
            a[j]=a[j]*(LL)j%mo;
        }
    }
    LL qwq=qpower(m,max(n-k,0ll));
    LL ans=0;
    for(int i=min(n,k);i>=0;--i){
        ans=(ans+qwq*a[i])%mo;
        qwq=qwq*m%mo;
    }
    ans=ans*inv(qpower(m,n))%mo;
    write(ans,'\n');
    return 0;
}



posted @ 2019-12-20 20:49  ~Lanly~  阅读(487)  评论(4编辑  收藏  举报