码迷,mamicode.com
首页 > 其他好文 > 详细

AtCoder Grand Contest 019 题解

时间:2019-10-03 10:25:09      阅读:46      评论:0      收藏:0      [点我收藏+]

标签:fine   test   多项式   区间   cpp   line   n+1   amp   网格   

传送门

\(A\)

咕咕

int a,b,c,d,n,t;
int main(){
    scanf("%d%d%d%d%d",&a,&b,&c,&d,&n);
    t=n&1,n-=t,a*=8,b*=4,c*=2;
    printf("%lld\n",1ll*min(min(a,b),min(c,d))*(n>>1)+(min(min(a,b),c)>>1)*t);
    return 0;
}

\(B\)

先不考虑结果不变的操作,发现如果一个翻转的区间\([l,r]\)满足\(s[l]=s[r]\)那么我们现在可以选择操作\([l+1,r-1]\)而使得最终的序列和翻转\([l,r]\)之后的序列相等,那么我们就不计数\([l,r]\),这样我们只要数出有多少对\([l,r]\)满足\(s[l]\\neq s[r]\),最后答案\(+1\)即可

typedef long long ll;
const int N=2e5+5;
char s[N];int cnt[26],n,sum;ll res;
int main(){
    scanf("%s",s+1),n=strlen(s+1);
    fp(i,1,n)res+=sum-cnt[s[i]-'a'],++cnt[s[i]-'a'],++sum;
    printf("%lld\n",res+1);
    return 0;
}

\(C\)

直接把那个矩阵中的点拿出来,然后按纵坐标从小到大排序,以\(x\)做一个最长上升子序列,如果长度\(k < min(x_2 - x_1,y_2 - y_1) + 1\),那么可以拐角\(k\)次,减少\((20-5\pi)k\)

否则只能拐角\(k-1\)次然后再走一个半圆,减少\((20-5\pi)(k-1)\)再加上\((10\pi -20)\)

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=2e5+5;const double Pi=acos(-1.0);
int x[N],y[N],id[N],f[N],num[N];
int n,tot,cnt;
inline bool cmp(const int &a,const int &b){return y[a]<y[b];}
int main(){
    int x1,y1,x2,y2;
    scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&n);
    fp(i,1,n)scanf("%d%d",&x[i],&y[i]);
    if(x1>x2)swap(x1,x2),swap(y1,y2);
    if(y1>y2){
        swap(y1,y2);
        fp(i,1,n)y[i]=y1+y2-y[i];
    }
    fp(i,1,n)id[i]=i;
    sort(id+1,id+1+n,cmp);
    fp(i,1,n){
        R int u=id[i];
        if(y[u]>=y1&&y[u]<=y2&&x[u]>=x1&&x[u]<=x2)
            num[++tot]=x[u];
    }
    cnt=0,f[cnt]=-1;
    fp(i,1,tot){
        if(num[i]>f[cnt])f[++cnt]=num[i];
        else{
            R int k=lower_bound(f+1,f+1+cnt,num[i])-f;
            f[k]=num[i];
        }
    }
    R double res;
    if(cnt<min(x2-x1,y2-y1)+1)
        res=100.0*(x2-x1+y2-y1)-(20-Pi*5)*cnt;
    else res=100.0*(x2-x1+y2-y1)-(20-Pi*5)*(cnt-1)+(10*Pi-20);
    printf("%.12lf\n",res);
    return 0;
}

\(D\)

首先枚举\(A\)中的那一个位置最终和\(B_1\)匹配,然后对于每一个位置如果和最终的位置不等,那么判一下在过程中是否会碰到\(1\),如果是的话可以在那里变成

否则我们对于每一个无法达到的点求出额外向左\(/\)右移动的最小距离,如果令一个左边最远的走左边,那么剩下的也都可以走左边,否则这个左边最远的必须走右边。同理次远也是一样

总的复杂度\(O(n^2\log n)\)

//quming
#include<bits/stdc++.h>
#define R register
#define fi first
#define se second
#define pb emplace_back
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
typedef pair<int,int> pi;
const int N=2005,inf=0x3f3f3f3f;
char s[N*3],s1[N];
int sum[N*3],nl[N*3],nr[N*3],res,len;
vector<pi>vc;
void init(){
    nl[0]=-inf;
    fp(i,1,len*3)nl[i]=(s[i]=='1'?i:nl[i-1]);
    nr[len*3+1]=inf;
    fd(i,len*3,1)nr[i]=(s[i]=='1'?i:nr[i+1]);
    fp(i,1,len*3)sum[i]=sum[i-1]+(s[i]=='1');
}
inline int findl(R int x){return min(inf,x-nl[x]);}
inline int findr(R int x){return min(inf,nr[x]-x);}
int main(){
    scanf("%s%s",s1+1,s+1),res=inf;
    len=strlen(s+1);
    fp(i,len+1,len*3)s[i]=s[i-len];
    init();
    fp(i,1,len*2+1){
        vc.clear();
        R int c=0;
        fp(j,1,len)if(s1[j]!=s[i+j-1]){
            ++c;
            R int r=j+len,l=j+i-1;
            if(l>r)swap(l,r);
            if(sum[r]-sum[l-1]==0)
            vc.pb(pi(findl(l),findr(r)));
        }
        vc.pb(pi(0,0));
        sort(vc.begin(),vc.end());
        reverse(vc.begin(),vc.end());
        R int tmp=inf,mx=0;
        for(auto p:vc){
            if(p.fi!=inf&&mx!=inf)cmin(tmp,mx*2+p.fi*2);
            cmax(mx,p.se);
            if(mx==inf)break;
        }
        if(tmp!=inf)cmin(res,tmp+abs(len+1-i)+c);
    }
    if(res==inf)res=-1;
    printf("%d\n",res);
    return 0;
}

\(E\)

想到这些方法的都是什么神仙啊……

首先有用的位置只有两个,两个全\(1\)或者一个\(1\)一个\(0\),方便起见把全\(1\)记做中间点,一个\(1\)一个\(0\)记做左点,一个\(0\)一个\(1\)记做右点。记中间点个数为\(A\),左点和右点的个数都是\(B\)

我们先匹配再考虑顺序,对于一个\((a_i,b_i)\)的匹配,把\(a_i\)\(b_i\)连一条边,那么最终的图肯定是若干个环加若干条链,环上都是中间点,链上开头和结尾分别是左右点,中间都是中间点。对于环,上面操作顺序任意,对于链,操作顺序必须固定

sol1

\(f[i][j]\)表示把\(j\)个中间点放到\(i\)条链中的方案数,则\(f[i][j]=\sum _{k\geq 0}{f[i-1][j-k]\over (k+1)!}\),分母表示链上\(k+1\)条边的顺序固定,那么最终的答案就是\(A!B!(A+B)!\sum_{i=0}^A f[B][i]\),其中\((A+B)!\)表示边随便排列,而\(A!\)\(B!\)代表两种点之间有标号

然后我们每一次转移相等于乘上一个多项式\(\sum_{i\geq 0}{1\over (i+1)!}x^i\),那么多项式快速幂就行了,复杂度\(O(n\log n)\)

sol2

另一种方法是设\(f[i][j]\)表示一共放了\(i\)个中间点和\(j\)个左点,则

\[ \begin{aligned} f[i][j]=i\times j\times f[i-1][j]+j^2\times f[i][j-1] \end{aligned} \]

前一个是表示插入一个中间点,它的标号任意,即可以随便和之前一个交换标号,所以乘\(i\),然后可以插在任意一条链的后面,所以乘\(j\)

后一个是表示插入一个新的左点,同理标号任意所以乘\(j\),又因为可以选择任意一个右点所以再乘\(j\)

复杂度\(O(n^2)\)已经可以通过了

不过还可以用多项式优化,设\(g[i][j]={f[i][j]\over (j!)^2(i!)}\),则有\(g[i][j]=g[i][j-1]+j\times g[i-1][j]\)

原来的可以看成是一个网格从左上出发到右下,每次往下走就乘上\(ij\),往右走就乘上\(j^2\),求所有路径权值和,变成\(g[i][j]\)之后就是只有往下走会乘上\(j\)

那么我们枚举在第\(i\)列走了几步,发现第\(i\)列的生成函数就是\({1\over 1-ix}\),答案就是所有列的生成函数之积,直接分治\(NTT+\)多项式求逆即可,复杂度\(O(n\log^2 n)\)

因为比较懒所以只写了\(O(n^2)\)

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int P=998244353;
inline void upd(R int &x,R int y){(x+=y)>=P?x-=P:0;}
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int dec(R int x,R int y){return x-y<0?x-y+P:x-y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
int ksm(R int x,R int y){
    R int res=1;
    for(;y;y>>=1,x=mul(x,x))(y&1)?res=mul(res,x):0;
    return res;
}
const int N=10005;
char s[N],t[N];int fac[N],ifac[N],f[N][N],n,cnta,cntb;
inline int C(R int n,R int m){return m>n?0:1ll*fac[n]*ifac[m]%P*ifac[n-m]%P;}
inline void init(int n=10000){
    fac[0]=ifac[0]=1;fp(i,1,n)fac[i]=mul(fac[i-1],i);
    ifac[n]=ksm(fac[n],P-2);fd(i,n-1,1)ifac[i]=mul(ifac[i+1],i+1);
}
int main(){
    init();
    scanf("%s%s",s+1,t+1),n=strlen(s+1);
    fp(i,1,n){
        cnta+=(s[i]=='1'&&t[i]=='1');
        cntb+=(s[i]=='1'&&t[i]=='0');
    }
    f[0][0]=1;
    fp(j,1,cntb)f[0][j]=mul(fac[j],fac[j]);
    fp(i,1,cnta)fp(j,1,cntb)
        f[i][j]=(1ll*i*j%P*f[i-1][j]+1ll*j*j%P*f[i][j-1])%P;
    R int res=0;
    fp(i,0,cnta){
        R int j=cnta-i;
        upd(res,1ll*f[i][cntb]*fac[j]%P*fac[j]%P*C(cnta,j)%P*C(cnta+cntb,j)%P);
    }
    printf("%d\n",res);
    return 0;
}

\(F\)

好秒……

首先假设现在还剩\(n\)\(Yes\)\(m\)\(No\),那么肯定是选择较多的那个回答,如果一样的话随便蒙一个

然后我们把这个东西放到一个坐标系里,那么一种回答的方案相当于是从\((n,m)\)出发到\((0,0)\),如果以\(y=x\)把坐标系分成两部分,则在这条线下面往左走会有贡献,在上面往下走会有贡献

先假设\(n=m\),且走的过程中没有碰到过对角线,那么不管在上面走还是在下面走贡献都是\(n\)

否则先假设\(n\geq m\),那么在走到对角线之前,一定会先往左走\(n-m\)步,如果到了对角线,我们以每次碰到对角线为界把路径分成若干部分,那么每一部分的贡献都是之前说的没有碰到过对角线的贡献了,所以总的贡献必定是\(n\),同理当\(m\geq n\)的时候贡献一定是\(m\)

然后是对角线上的点,不管往哪边走贡献都是\({1\over 2}\),那么我们对于每个点判断一下经过它的概率是多少即可

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int P=998244353;
inline void upd(R int &x,R int y){(x+=y)>=P?x-=P:0;}
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int dec(R int x,R int y){return x-y<0?x-y+P:x-y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
int ksm(R int x,R int y){
    R int res=1;
    for(;y;y>>=1,x=mul(x,x))(y&1)?res=mul(res,x):0;
    return res;
}
const int N=1e6+5;
int fac[N],ifac[N],n,m,res;
inline int C(R int n,R int m){return m>n?0:1ll*fac[n]*ifac[m]%P*ifac[n-m]%P;}
inline int calc(R int n,R int m){return C(n+m,n);}
inline void init(int n=1e6){
    fac[0]=ifac[0]=1;fp(i,1,n)fac[i]=mul(fac[i-1],i);
    ifac[n]=ksm(fac[n],P-2);fd(i,n-1,1)ifac[i]=mul(ifac[i+1],i+1);
}
int main(){
    init();
    scanf("%d%d",&n,&m);
    fp(i,1,min(n,m))upd(res,mul(calc(i,i),calc(n-i,m-i)));
    res=mul(res,ksm(mul(calc(n,m),2),P-2));
    upd(res,max(n,m));
    printf("%d\n",res);
    return 0;
}

AtCoder Grand Contest 019 题解

标签:fine   test   多项式   区间   cpp   line   n+1   amp   网格   

原文地址:https://www.cnblogs.com/yuanquming/p/11600267.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!