AtCoder Grand Contest 007题解

Posted 源曲明

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AtCoder Grand Contest 007题解相关的知识,希望对你有一定的参考价值。

传送门

\(A\)

咕咕咕

//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=15;
char mp[N][N];int vis[N][N],n,m;
bool dfs(int x,int y){
    vis[x][y]=1;
    if(x==n&&y==m)return true;
    R int c=0;
    if(x<n&&mp[x+1][y]=='#')++c;
    if(y<m&&mp[x][y+1]=='#')++c;
    if(c!=1)return false;
    if(x<n&&mp[x+1][y]=='#')return dfs(x+1,y);
    if(y<m&&mp[x][y+1]=='#')return dfs(x,y+1);
}
int main(){
    scanf("%d%d",&n,&m);
    fp(i,1,n)scanf("%s",mp[i]+1);
    if(mp[1][1]!='#'||mp[n][m]!='#')return puts("Impossible"),0;
    if(!dfs(1,1))return puts("Impossible"),0;
    fp(i,1,n)fp(j,1,m)if((mp[i][j]=='#')!=vis[i][j])return puts("Impossible"),0;
    return puts("Possible"),0;
}

\(B\)

先把\(a\)\(b\)分别设成\(1\)\(n\)来满足第一第二个条件

我们发现,如果把\(a_i\)\(a_n\)全部加上\(x\),且\(b_1\)\(b_i\)全部加上\(x\),可以使\(a_i+b_i\)相对增加\(x\)且仍满足第一第二个条件

那么我们令\(p_i\)相对增加\(i\)即可满足第三个条件,最大的数是\(O(n^2)\)级别的,不会超过\(10^9\)

const int N=1e5+5;
int a[N],b[N],p[N],n;
int main(){
    scanf("%d",&n);
    fp(i,1,n)scanf("%d",&p[i]),a[p[i]]+=i,b[p[i]]+=i;
    fp(i,1,n)a[i]+=a[i-1];fd(i,n,1)b[i]+=b[i+1];
    fp(i,1,n)printf("%d%c",a[i]+i," \n"[i==n]);
    fp(i,1,n)printf("%d%c",b[i]+n-i+1," \n"[i==n]);
    return 0;
}

\(C\)

完全想不到啊……

首先我们转化为有\(2n+1\)个物品,每次随机删去相邻的两个,求距离的期望和

我们发现一开始时距离是一个等差数列,并且如果维护每两个点间距离的期望,那么一次操作之后仍然是等差数列

具体的证明的话,如果删去的两个点在边界上显然操作之后还是等差,否则如果对于一个长度为\(d+kx\)的段,删去这段的两个顶点之后这一段长度变为\(3d+3kx\),所以令\(d_i\)表示第\(i\)个点和第\(i+1\)个点的期望距离,\(d_i'\)为操作之后的期望距离,那么总共有\(2n\)种删法,所以\(d_i'={i\over 2n}(d_i+2x)+{1\over 2n}(3d_i+3x)+{2n-i-1\over 2n}d_i={2n+2\over 2n}d_i+{2i+3\over 2n}x\),所以\(x'=d_i'-d_{i-1}'={2n+4\over 2x}x={n+2\over n}x\),是一个定值

然后再来考虑新的等差数列的首项,如果删了\(1,2\),首项变为\(d+2x\),如果删了\(2,3\),首项变为\(3x+3d\),否则不变,所以新的首项\(d_1^{'}=\frac{1}{2n}*(d_1+2x)+\frac{1}{2n}*(3d_1+3x)+\frac{2n-2}{2n}*d_1=\frac{(2n+2)d_1+5x}{2n}\)

然后每次计算即可

int n;double d,x,res;
int main(){
    scanf("%d%lf%lf",&n,&d,&x);
    for(;n;--n){
        res+=(d+((n<<1)-1)*x*0.5),
        d=(((n<<1)+2)*d+5*x)/(n<<1),
        x=(n+2)*x/n;
    }
    printf("%.12lf\n",res);
    return 0;
}

\(D\)

首先我们容易写出一个暴力,设\(f_i\)表示把前\(i\)个数全部处理完的最小时间,则\(f_i=\min_{j<i}(f_j+\max(t,(a_i-a_{j+1})))\)

因为\(\max(t,(a_i-a_{j+1})\)的分界点是随\(i\)递增而不降的,所以我们可以直接把分界点两边的答案分别用\(set\)记录下来然后直接查询就好了

//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;
typedef long long ll;
const int N=5e5+5;const ll inf=1e18;
int n,t,e,a[N];ll f[N];
inline ll min(R ll x,R ll y){return x<y?x:y;}
inline ll max(R ll x,R ll y){return x>y?x:y;}
struct Queue{
    priority_queue<ll,vector<ll>,greater<ll> >A,B;
    inline void push(R ll x){A.push(x);}
    inline void pop(R ll x){B.push(x);}
    inline ll top(){
        while(!B.empty()&&A.top()==B.top())A.pop(),B.pop();
        return A.empty()?inf:A.top();
    } 
}s1,s2;
int main(){
    scanf("%d%d%d",&n,&e,&t);
    fp(i,1,n)scanf("%d",&a[i]);
    s2.push(0);
    for(R int i=1,j=0;i<=n;++i){
        while(j<i&&((a[i]-a[j+1])<<1)>=t)s1.push(f[j]-(a[j+1]<<1)),s2.pop(f[j]),++j;
        f[i]=inf,cmin(f[i],s1.top()+(a[i]<<1)),cmin(f[i],s2.top()+t);
        s2.push(f[i]);
    }
    printf("%lld\n",f[n]+e);
    return 0;
}

\(E\)

首先肯定要二分答案,且因为每条边只能经过两次,所以进入一颗子树之后必须遍历完其中所有节点才能出来

对于每一个点,记录一个状态\((a,b)\)表示一条合法的路径是花费\(a\)的代价从当前节点走到某个叶子,花费\(b\)的代价从某个叶子走回到当前节点,那么对于左子树的\((a,b)\)和右子树的\((c,d)\),只有当\(b+w_i+c+w_j\leq mid\)的时候它们才能合并成\((a+w_i,d+w_j)\),同理也要判断一下是否能反过来合并成\((c+w_j,b+w_i)\)

每一次把左右儿子的状态进行合并,最后只要\(1\)号点存在合法的状态就说明\(mid\)可行

有一个优化,就是对于一个合法的\(a\)来说,它对应的\(b\)一定是越小越好,那么我们可以对于左子树按第一维排序,右子树按第二位排序,这样合并的复杂度就可以优化到\(O(n)\)

然而状态数可能会比较多,所以我们每一次用状态数小的去合并状态数大的,这样状态数\(S_u\leq 2\min(S_i,S_j)\),总的状态数是\(O(n\log n)\)的,于是总复杂度为\(O(n\log^2 n)\)

然而比较奇怪的是我如果用状态数大的去合并小的不是\(T\)而是\(WA\)……好迷啊……

//quming
#include<bits/stdc++.h>
#define R register
#define pb push_back
#define fi first
#define se second
#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;
typedef vector<pi> vc; 
const int N=5e5+5;
int ls[N],rs[N],w[N],mn[N],n,l,r,mid,ans;vc f[N];
inline int min(R int x,R int y){return x<y?x:y;}
inline void swap(R int &x,R int &y){R int t=x;x=y,y=t;}
inline bool cmpx(const pi &x,const pi &y){return x.fi<y.fi;}
inline bool cmpy(const pi &x,const pi &y){return x.se<y.se;}
void merge(vc &a,vc &ls,vc &rs){
    R int n=ls.size(),m=rs.size();
    if(!n||!m)return;
    if(n>m)ls.swap(rs),swap(n,m);
    sort(ls.begin(),ls.end(),cmpy);
    sort(rs.begin(),rs.end(),cmpx);
    mn[0]=rs[0].se;fp(i,1,m-1)mn[i]=min(mn[i-1],rs[i].se);
    for(R int i=0,j=m-1;i<n;++i){
        while(j>=0&&ls[i].se+rs[j].fi>mid)--j;
        if(j<0)break;a.pb(pi(ls[i].fi,mn[j]));
    }
    sort(ls.begin(),ls.end(),cmpx);
    sort(rs.begin(),rs.end(),cmpy);
    mn[0]=rs[0].fi;fp(i,1,m-1)mn[i]=min(mn[i-1],rs[i].fi);
    for(R int i=0,j=m-1;i<n;++i){
        while(j>=0&&ls[i].fi+rs[j].se>mid)--j;
        if(j<0)break;a.pb(pi(mn[j],ls[i].se));
    }
}
void dfs(int u){
    f[u].clear();if(!ls[u])return f[u].pb(pi(w[u],w[u])),void();
    dfs(ls[u]),dfs(rs[u]);
    merge(f[u],f[ls[u]],f[rs[u]]);
    for(auto &v:f[u])v.fi+=w[u],v.se+=w[u];
}
int main(){
    scanf("%d",&n);
    for(R int i=2,fa,x;i<=n;++i){
        scanf("%d%d",&fa,&x);
        (ls[fa]?rs[fa]:ls[fa])=i,w[i]=x;
    }
    l=0,r=1e9,ans=1e9;
    while(l<=r){
        mid=(l+r)>>1,dfs(1);
        f[1].empty()?l=mid+1:(ans=mid,r=mid-1);
    }
    printf("%d\n",ans);
    return 0;
}

\(F\)

好迷……

首先我们发现,一个字母往下的轨迹视为一条路径,那么这条路径会是一条折线,且这个折线越早拐弯越好

那么我们对于\(t\)从后往前贪心的匹配,找到\(s\)中最后面的能匹配的位置,然后判断是否会和之前的折线有交点

感觉我还是说不太清楚……建议看一下官方题解的图

//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=1e6+5;
char a[N],b[N];int q[N],h,t,n,res;
int main(){
    scanf("%d%s%s",&n,a+1,b+1);
    if(!strcmp(a+1,b+1))return puts("0"),0;
    h=1,t=0;
    for(R int i=n,p=n;i;--i)if(b[i]!=b[i-1]){
        cmin(p,i);while(p&&a[p]!=b[i])--p;
        if(!p)return puts("-1"),0;
        while(h<=t&&q[h]-(t-h+1)>=i)++h;
        q[++t]=p;
        if(i!=p)cmax(res,t-h+1);
    }
    printf("%d\n",res+1);
    return 0;
}

以上是关于AtCoder Grand Contest 007题解的主要内容,如果未能解决你的问题,请参考以下文章

AtCoder Grand Contest 007C

markdown AtCoder Grand Contest 016

AtCoder Grand Contest 005

AtCoder Grand Contest 006

AtCoder Grand Contest 008 题解

AtCoder Grand Contest 025 Problem D