Codeforces Global Round 10(CF1392)

Codeforces Global Round 10

前言

我:我想上红。。。
我的实力和网速:不,你不想。

A to E 略

F Omkar and Landslide

题意

给一个长度为 \(n\) 的递增的数组 \(h_i\) ,每当存在 \(h_j + 2 \leq h_{j+1}\) ,则使 \(h_j\) 增加 \(1\) ,同时使 \(h_{j+1}\) 减小 \(1\) 。输出最终的数组。\(0\leq h_i \leq {10}^{12},1\leq n\leq {10}^{6}\)

题解

没有看这题的题解,说一下我自己的做法吧。。显然初始和最终的数组都可以由若干个逐渐上升 \(1\) 的块组成。
取每个块的最后的数放在一个队列中,我们就可以反推出原来的数组。于是我们得到了表示数组的方法。
观察变化的方式可知,当我们在一个数组末尾增加一个数,只会对最后一个块产生变化,直到最后一个块能与前一个块合并。
逐个将数放入队列中,每次增加的块数是 \(O(1)\) 的,减少的块数的总和是 \(O(n)\) 的,这题就做完了。

\(Code\)

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e6+10;
const int inf=1e9+10;
int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void print(LL x){
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
int n,d,mx;
LL h[N];
int q[N];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%I64d",&h[i]);
    int tp=1;q[1]=1;
    LL nd,ud;
    int x;
    for(int i=2;i<=n;++i){
        while(1){
            if(h[i]==h[q[tp]]){
                q[++tp]=i;
                break;
            }
            if(h[i]==h[q[tp]]+1){
                q[tp]=i;
                break;
            }
            if(tp>=2){
                nd=q[tp]-q[tp-1];
                if(h[q[tp]]+1<=h[i]-nd){
                    h[i]-=nd;
                    h[q[tp]]++;
                    q[tp-1]=q[tp];
                    --tp;
                }
                else{
                    ud=h[i]-(h[q[tp]]+1);
                    q[tp-1]+=ud;
                    q[tp]=i;
                    h[i]=h[i]-ud;
                    break;
                }
            }
            else {
                nd=q[tp];
                if(h[q[tp]]+1<=h[i]-nd){
                    ud=(h[i]-h[q[tp]])/(LL)(nd+1);
                    h[q[tp]]+=ud;
                    h[i]-=ud*nd;
                }
                else{
                    ud=h[i]-(h[q[tp]]+1);
                    q[tp]=ud;
                    q[tp+1]=i;
                    ++tp;
                    h[i]=h[i]-ud;
                    break;
                }
            }
        }
    }
    --tp;
    for(int i=n-1;i>=1;--i){
        h[i]=h[i+1]-1;
        if(tp>0&&q[tp]==i){
            ++h[i];
            --tp;
        }
    }
    for(int i=1;i<=n;++i){
        print(h[i]);putchar(' ');
    }
    puts("");
    return 0;
}

G Omkar and Pies

题意

给两个长度为 \(k\) 的01串,一个是初始串,一个是目标串。现在有 \(n\) 个操作,每个操作有两个参数 \(a_i\)\(b_i\),表示交换当前串第 \(a_i\) 和第 \(b_i\) 位置上的数。现在要求按顺序使用一个区间的所有操作,使得初始串经变化后与目标串相同的位尽量多。要求使用的区间长度不小于 \(m\) 。问最多相同几位,以及在此情况下选择的区间。\(2\leq k \leq 20,1\leq n,m\leq {10}^{6} , 1\leq a_i,b_i \leq k\)

题解

我当时要是做出这题就红了QAQ。我当时一直在确定使用区间后怎么快速得到变化后的串。。然后就崩了。
题解的方法很巧妙,首先有个正确性显然的结论:将初始串和目标串同时进行一样的操作,两串相同的位数不变。
如果在初始串做完区间 \([l,r]\) 的变换之后,两串同时倒着做 \([l,r]\) 的变换,就抵消了初始串的所有变化。
但这样仍然不好做,因为我们不能像做前缀和那样快速得到变化后相同位数。
于是在初始串做完区间 \([l,r]\) 的变换之后,两串同时倒着做 \([1,r]\) 的变换。
相当于初始串只倒着做了 \([1,l-1]\) 的变换,而目标串倒做了 \([1,r]\) 的变换。
此时我们枚举 \(l,r\) 就能快速得到要算最多相同位数的两个串了。但现在答案还是不好统计。
设初始串中1的数量为 \(u\) , 目标串中1的个数为 \(v\) ,两串变化后相同的1的个数为 \(w\) , 两串变化后相同位数 \(x\).
于是有 \(x=k-u-v+w*2\) ,由于 \(u,v,k\) 都是已知的常数,所以相同的1的个数越大,则相同位数越大。
而快速得到最大的相同1的个数的区间可以使用状压dp在 \(O(k2^{k})\) 做完。

\(Code\)

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=3e5+10;
const int inf=1e8;
int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void print(LL x){
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
int n,m,K;
char s[30];
int a[30],b[30],p[30],bin[30];
int aa[30],bb[30];
int cnt[(1<<20)+10];
int dp[2][(1<<20)+10];
int getnum(int *c){
    int re=0;
    for(int i=K;i>=1;--i) re=re<<1|c[i];
    return re;
}
void umin(int &x,int y){if(x>y)x=y;}
void umax(int &x,int y){if(x<y)x=y;}
int main(){
    for(int i=0;i<=(1<<19);++i){
        cnt[i<<1]=cnt[i];
        cnt[i<<1|1]=cnt[i]+1;
    }
    scanf("%d%d%d",&n,&m,&K);
    scanf("%s",s+1);
    for(int i=1;i<=K;++i) a[i]=s[i]-'0';
    scanf("%s",s+1);
    for(int i=1;i<=K;++i) b[i]=s[i]-'0';
    for(int i=0;i<=(1<<20);++i) dp[0][i]=inf;
    for(int i=0;i<=(1<<20);++i) dp[1][i]=-inf;
    umin(dp[0][getnum(a)],0);
    umax(dp[1][getnum(b)],0);
    for(int i=1;i<=K;++i) p[i]=i;
    int l,r;
    for(int i=1;i<=n;++i){
        scanf("%d%d",&l,&r);
        swap(p[l],p[r]);
        for(int j=1;j<=K;++j){
            aa[p[j]]=a[j];
            bb[p[j]]=b[j];
        }
        //for(int j=1;j<=K;++j) cout<<aa[j]<<" ";puts("");
        //for(int j=1;j<=K;++j) cout<<bb[j]<<" ";puts("");
        umin(dp[0][getnum(aa)],i);
        umax(dp[1][getnum(bb)],i);
    }
    for(int i=(1<<K)-1;i>0;--i){
        for(int j=0;j<K;++j){
            if(i&(1<<j)){
                umin(dp[0][i-(1<<j)],dp[0][i]);
                umax(dp[1][i-(1<<j)],dp[1][i]);
            }
        }
    }
    int ans=-1;
    for(int i=0;i<(1<<K);++i){
        if(cnt[i]>ans&&dp[1][i]-dp[0][i]>=m){
            ans=cnt[i];
            l=dp[0][i]+1;
            r=dp[1][i];
        }
    }
    ans=K+ans+ans;
    for(int i=1;i<=K;++i) ans=ans-a[i]-b[i];
    cout<<ans<<endl<<l<<" "<<r<<endl;
    return 0;
}

H ZS Shuffles Cards

题意

\(n+m\) 张牌,其中有 \(m\) 张鬼牌,剩余 \(n\) 张牌有编号 \(1\)\(n\)
每次洗牌之后,在牌堆从上到下按顺序取出牌,若牌不是鬼牌,把这张牌的编号放入一个集合 \(S\) 里;若牌是鬼牌,则考虑 \(S\) 中是否已经包含 \(1\)\(n\) 所有数字,若已经包含,则停止游戏,否则重新洗牌开始新的一轮。每次取出一张牌所需时间1秒,其他操作不消耗时间。问结束时期望需要多少秒。答案对 \(998244353\) 取模。\(1\leq n,m\leq 2 \cdot {10}^{6}\)

题解

首先设 \(f(x)\) 为已经还剩 \(x\) 个不同的数没获得,期望再过 \(f(x)\) 后结束。
枚举下一轮得到新的 \(l\) 个数,且下一轮一共翻了 \(i\) 张数字牌。得到式子:
\(f(x)\) \(=\) \(\sum_{l=0}^{x}{\sum_{i=0}^{n}{ {x \choose l} {n-x \choose i-l} \cdot i! \cdot m \cdot \frac{(n+m-i-1)!}{(n+m)!} \cdot (f(x-l)+i+1)}}\)
为了方便计算,将式子分成两部分分别计算。。
\(A(x)\) \(=\) \(\sum_{l=0}^{x}{\sum_{i=0}^{n}{ {x \choose l} {n-x \choose i-l} \cdot i! \cdot m \cdot \frac{(n+m-i-1)!}{(n+m)!} \cdot (i+1)}}\)
\(B(x)\) \(=\) \(\sum_{l=0}^{x}{\sum_{i=0}^{n}{ {x \choose l} {n-x \choose i-l} \cdot i! \cdot m \cdot \frac{(n+m-i-1)!}{(n+m)!} \cdot f(x-l)}}\)
\(f(x)\) \(=\) \(A(x)+B(x)\)
然后就是推式子。
\(A(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot \sum_{l=0}^{x}{ \frac{(m-1+x-l)! \cdot (l+1)}{(x-l)!} \cdot \sum_{i=0}^{n}{\frac{(i+1)!}{(l+1)! \cdot (i-l)!} \cdot \frac{(n+m-i-1)!}{(n-x-i+l)! \cdot (m-1+x-l)!} }}\)
\(A(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot \sum_{l=0}^{x}{ \frac{(m-1+x-l)! \cdot (l+1)}{(x-l)!} \cdot \sum_{i=0}^{n}{ {i+1 \choose l+1} \cdot {n+m-i-1 \choose m-1+x-l} }}\)
\(A(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot \sum_{l=0}^{x}{ \frac{(m-1+x-l)! \cdot (l+1)}{(x-l)!} \cdot {n+m+1 \choose m+x+1} }\) 这里用到了 \(\sum_{i}{i \choose k}{n-i \choose m-k}\) \(=\) \({n+1 \choose m+1}\) 这条公式,详见本博客的数学的专题总结。。
\(A(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot {n+m+1 \choose m+x+1} \cdot \sum_{l=0}^{x}{ \frac{(m+l-1)! \cdot (x-l+1)}{l!} }\)
后面的 \(\sum_{l=0}^{x}{ \frac{(m+l-1)! \cdot (x-l+1)}{l!} }\) 可以另外dp,转移可以做到 \(O(1)\)
用类似的方法推 \(B(x)\)
\(B(x)\) \(=\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot \sum_{l=0}^{x}{ \frac{(m-1+x-l)! \cdot f(x-l)}{(x-l)!} \cdot \sum_{i=0}^{n}{\frac{i!}{l! \cdot (i-l)!} \cdot \frac{(n+m-i-1)!}{(n-x-i+l)! \cdot (m-1+x-l)!} }}\)
\(B(x)\) \(=\) \(\frac{m \cdot x! \cdot (n-x)!}{(n+m)!} \cdot {n+m \choose m+x} \cdot \sum_{l=0}^{x}{ \frac{(m+l-1)! \cdot f(l)}{l!} }\)
\(B(x)\) \(=\) \(\frac{m \cdot x!}{(m+x)!} \cdot \sum_{l=0}^{x}{ \frac{(m+l-1)! \cdot f(l)}{l!} }\)
类似BZOJ1426收集邮票,这个 \(B(x)\) 的dp式中会有 \(f(x)\) ,因为有可能一轮过去一个新数都没有。
可以取出 \(B(x)\) 中系数有 \(f(x)\) 的项,移到等式左边,然后再除回右边,这里会用到快速幂。所以总的效率为 \(O(nlgP)\)

\(Code\)

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e6+10;
const int inf=1e8;
const LL P=998244353;
int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void print(LL x){
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
LL n,m;
LL jc[N*3],inv[N*3];
LL f[N],A[N],B[N];
LL T[N],G[N],H[N];
void add(LL &x,LL y){
    x+=y;if(x>=P)x-=P;
}
LL qpow(LL x,LL y){
    LL re=1;
    while(y){
        if(y&1) re=re*x%P;
        x=x*x%P;y>>=1;
    }
    return re;
}
int main(){
    jc[0]=jc[1]=inv[0]=inv[1]=1;
    for(LL i=2;i<=N*2;++i){
        jc[i]=jc[i-1]*i%P;
        inv[i]=(P-P/i)*inv[P%i]%P;
    }
    for(LL i=2;i<=N*2;++i){
        inv[i]=inv[i-1]*inv[i]%P;
    }
    cin>>n>>m;
    T[0]=jc[m-1];G[0]=jc[m-1];
    for(LL i=0;i<n;++i){
        G[i+1]=G[i];add(G[i+1],jc[m+i]*inv[i+1]%P);
        T[i+1]=T[i];add(T[i+1],G[i+1]);
    }
    for(LL i=1;i<=n;++i){
        A[i]=m*jc[i]%P*(n+m+1)%P*inv[m+i+1]%P*T[i]%P;
        B[i]=m*jc[i]%P*inv[m+i]%P*H[i-1]%P;
        f[i]=(A[i]+B[i])%P*qpow((P+1-m*inv[m+i]%P*jc[m-1+i]%P)%P,P-2)%P;
        H[i]=H[i-1];add(H[i],f[i]*inv[i]%P*jc[m+i-1]%P);
    }
    cout<<f[n]<<endl;
    return 0;
}

I Kevin and Grid

题意

给定两个数组 \(a_i,b_i\) , 数组长度分别为 \(n\)\(m\) ,由两个数组可以生成一个大小为 \(n \cdot m\) 的矩阵,矩阵的第 \(i\) 行第 \(j\) 列的值为 \(a_i \cdot b_j\)
现在有一个数 \(x\) ,将矩阵中的格子分成两类,一类的数值小于 \(x\) ,另一类不小于 \(x\) 。矩阵可以被分为若干个尽量大的只包含一种类型格子的联通块(既该联通块没有相邻的相同类型的格子)。若一个联通块包含矩阵边界上的格子,则认为这个联通块的价值为 \(1\) ,否则价值为 \(2\)\(Q\)次询问,每次询问给出一个 \(x\) ,问在 \(x\) 的情况下将矩阵分成若干个尽量大的只包含一种类型格子的联通块时,设 \(F1\) 为输出类型是不小于 \(x\) 的极大联通块的总价值, \(F2\) 为输出类型是小于 \(x\) 的极大联通块的总价值。输出 \(F1-F2\) 的值。\(1\leq n,m,Q,a_i,b_i \leq {10}^{5}\) , \(1\leq x \leq 2 \cdot {10}^{5}\)

题解

思路很巧妙。。但是出题人写的题解是真的难理解。。(也可能是我不了解平面图?)
平面图的公式我一直只记得 \(V+F=E+2\) (其中 \(V,F,E\) 分别表示点数,面数,边数)。但这公式直接用在这题是不行的。。
首先那道题,马上就会有一个很简单的 \(O(mnQ)\) 的做法,但显然过不了。
考虑重新建图,建两个图,第一个图中包含所有小于\(x\)类型格子代表的点,并在相邻格子代表的点之间连边。类似的,对于不小于 \(x\) 的格子我们也能这么做。
由于原图是网格图,也是平面图,所以我们得到的两个子图也是平面图。
但我们得到的新图并不是联通的,所以不能直接套 \(V+F=E+2\) 这条最常用的平面图公式。
稍微找下规律就会发现 \(V+F=E+C\) (其中 \(C\) 分别表示图中联通块数+1)
于是容易发现这个 \(C\) 就是我们要求的一部分答案。
然后我们还需知道不包含边界的联通块数。这里出题人给出了一个奇妙的结论:我们得到的两个子图,某一个子图中不包含边界的联通块个数等于另一个子图中的不包含最外面的面数\(F-1\)
若我们得到一个面,那么这个面所包含的另一个子图中的块一定不包含边界格子。
但这里有个问题,某个子图中的\(2*2\)点阵形成的面中不包含另一个子图中的块,我们需要去掉这部分答案,令 \(Q_i\) 表示第i个图中\(2*2\)点阵数。
(出题人原话“However, some faces are not interesting, namely the 2×2 square of adjacent cells.”)这句话我当时懵逼了一天。。

然后我们整理一下已经得到的信息。。\(C-1\)为联通块数,\(F-1-Q_i\)为另一个图的价值为2的联通块数。
列出式子:
\(ans(x)\) \(=\) \((C_1-1)+(F_2-1-Q_2)-(C_2-1)-(F_1-1-Q_1)\)
\(ans(x)\) \(=\) \(C_1+F_2-C_2-F_1+Q_1-Q_2\)
代入入平面图公式
\(ans(x)\) \(=\) \(V_1-E_1-V_2+E_2+Q_1-Q_2\)
现在我们只需求出两个子图中的点数,边数以及\(2*2\)点阵数。
要快速求点数,较快的方式使用fft,具体做法可以看代码,这部分应该算是简单的。
用类似的方法也可以求出另外两个要求的东西。

\(Code\)

#include <bits/stdc++.h>
#define DB long double
#define LL long long
using namespace std;
const int N=4e5+10;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const DB PI=acos(-1);
struct V{
    DB x,y;
    V(DB _x=0,DB _y=0){x=_x;y=_y;}
};
V operator + (V a,V b){return V(a.x+b.x,a.y+b.y);}
V operator - (V a,V b){return V(a.x-b.x,a.y-b.y);}
V operator * (V a,V b){return V(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
struct FastFourierTransform{
    int n,rev[N];
    void init(int m){
        n=1;
        while(n<m) n<<=1;
        int k=0;
        while((1<<k)<n) ++k;
        for(int i=0;i<n;++i){
            int t=0;
            for(int j=0;j<k;++j) if(i&(1<<j)) t|=(1<<(k-j-1));
            rev[i]=t;
        }
    }
    void DFT(V *a,int flag){
        for(int i=0;i<n;++i) if(i<rev[i]) swap(a[i],a[rev[i]]);
        for(int l=2;l<=n;l<<=1){
            int m=l>>1;
            V wn(cos(2*PI/l),flag*sin(2*PI/l));
            for(V *p=a;p!=a+n;p+=l){
                V w(1,0);
                for(int k=0;k<m;++k){
                    V t=w*p[k+m];
                    p[k+m]=p[k]-t;
                    p[k]=p[k]+t;
                    w=w*wn;
                } 
            }
        }
        if(flag==-1) for(int i=0;i<n;++i) a[i].x/=n;
    }
    void Cheng(V *a,V *b,int m){
        init(m);
        DFT(a,1);DFT(b,1);
        for(int i=0;i<n;++i) a[i]=a[i]*b[i];
        DFT(a,-1);
    }
}fft;
V A[N],B[N];
V Amx[N],Bmx[N];
V Amn[N],Bmn[N],C[N],D[N];

int n,m,Q;
int a[N],b[N];
LL ans[N];
int main(){
    scanf("%d%d%d",&n,&m,&Q);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    for(int i=1;i<=n;++i) {
        A[a[i]].x+=1;
        if(i<n){
            Amx[max(a[i],a[i+1])].x+=1;
            Amn[min(a[i],a[i+1])].x+=1;
    }
    }
    for(int i=1;i<=m;++i) scanf("%d",&b[i]);
    for(int i=1;i<=m;++i) {
        B[b[i]].x+=1;
        if(i<m){
            Bmx[max(b[i],b[i+1])].x+=1;
            Bmn[min(b[i],b[i+1])].x+=1;
    }
    }
    int L=200000+3;fft.init(L);
    fft.DFT(A,1);fft.DFT(B,1);
    fft.DFT(Amx,1);fft.DFT(Amn,1);
    fft.DFT(Bmx,1);fft.DFT(Bmn,1);
    for(int i=0;i<fft.n;++i){
        C[i]=Amn[i]*Bmn[i];
        D[i]=Amx[i]*Bmx[i];
        Amn[i]=Amn[i]*B[i];Amx[i]=Amx[i]*B[i];
        Bmn[i]=Bmn[i]*A[i];Bmx[i]=Bmx[i]*A[i];
        A[i]=A[i]*B[i];
    }
    fft.DFT(C,-1);fft.DFT(D,-1);
    fft.DFT(Amn,-1);fft.DFT(Amx,-1);
    fft.DFT(Bmn,-1);fft.DFT(Bmx,-1);
    fft.DFT(A,-1);
    LL now=0;
    for(int i=fft.n-1;i>=0;--i){
        now+=(long long)(A[i].x+0.5);
        ans[i]+=now;
    }
    now=0;
    for(int i=0;i<fft.n;++i){
        now+=(long long)(A[i].x+0.5);
        ans[i+1]-=now;
    }
    now=0;
    for(int i=fft.n-1;i>=0;--i){
        now+=(long long)(C[i].x+0.5);
        ans[i]+=now;
    }
    now=0;
    for(int i=0;i<fft.n;++i){
        now+=(long long)(D[i].x+0.5);
        ans[i+1]-=now;
    }
    now=0;
    for(int i=0;i<fft.n;++i){
        now+=(long long)(Amx[i].x+0.5);
        now+=(long long)(Bmx[i].x+0.5);
        ans[i+1]+=now;
    }
    now=0;
    for(int i=fft.n-1;i>=0;--i){
        now+=(long long)(Amn[i].x+0.5);
        now+=(long long)(Bmn[i].x+0.5);
        ans[i]-=now;
    }
    int x;
    while(Q--){
        scanf("%d",&x);
        printf("%I64d\n",ans[x]);
    }
    return 0;
}

posted @ 2020-08-27 19:10  Iscream-2001  阅读(247)  评论(0编辑  收藏  举报
/* */