AGC007

我现在板刷 AGC 有两个目的。一个是为了多刷点题增加点练习量,多见点套路和题型。另一个是模拟赛搬AGC的时候不至于保龄。

然后今天模拟赛三个套路题切爽了(虽然挂了不少分)。所以板刷一套 AGC 来打击打击心态。

[AGC007A] Shik and Stone

普及。珍惜现在 A 题还是普及的时候吧。

char s[10][10];
int n,m,cnt;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(s[i][j]=='#')cnt++;
        }
    }
    if(cnt==n+m-1)puts("Possible");
    else puts("Impossible");
    return 0;
}

[AGC007B] Construct Sequences

一个显然的构造是先把 \(a\) 变成 \(1-n\)\(b\) 变成 \(n-1\)。然后顺序扫 \(p\),对应位置加上。

然后这样会不满足 \(a,b\) 的单调性。发现我们只需要加不超过 \(n\) 的数,那一开始随便乘个比 \(n\) 大的数就行了。

int n,p[20010],a[20010],b[20010];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&p[i]);
    for(int i=1;i<=n;i++)a[i]=(n+1)*i,b[i]=(n+1)*(n-i+1);
    for(int i=2;i<=n;i++){
        if(a[p[i-1]]+b[p[i-1]]>=a[p[i]]+b[p[i]])b[p[i]]+=i;
    }
    for(int i=1;i<=n;i++)printf("%d ",a[i]);printf("\n");
    for(int i=1;i<=n;i++)printf("%d ",b[i]);printf("\n");
    return 0;
}

[AGC007C] Pushing Balls

神秘腧穴题。终于到了 C 题就开始不会的时候了。

打表可以得到每次滚一个球之后的距离序列的期望仍然是等差数列。然后考虑计算。

\(2n\) 种滚球方式,对 \(d_i\) 产生影响的操作有两种:

  1. 滚完前 \(i\) 段,此时原来的 \(d_{i+1}\) 无了,\(d_{i+2}\) 跑到了 \(d_i\) 的位置。
  2. 滚了第 \(i+1\) 段,此时原来 \(d_i,d_{i+2},d_{i+3}\) 并起来变成了 \(d_i\)

那么简单推导可以得到新的 \(d_i\) 的期望是: \(d_i+\dfrac{i\times 2x+2d+5x}{2n}\) 。回带 \(x\) 可以得到新的 \(x\)\(x+\dfrac {2x}n\)。每次推球的期望距离是所有的平均值,在等差数列里也就是中位数。

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

[AGC007D] Shik and Game

水题。

一眼 dp。然后每次走肯定是往前走一段再走回来收钱。考虑设 \(dp_i\) 为收完前 \(i\) 个时来回走花的时间,答案就是 \(dp_n+E\)

那么转移考虑从前面一个小熊的位置走过来在走回去,是 \(dp_i=dp_j+\max(2(a_i-a_{j+1}),T)\)

分两种情况讨论。\(\max\) 的第一项可以发现是单调的,整个单调指针扫一遍就行了。然后指针最多扫到 \(T\) ,那么整个单调队列随便维护一下就行了。

最近写单调队列老是忘记进队。

#define int long long
int n,E,T,a[100010],q[100010],l,r,dp[100010];
signed main(){
    memset(dp,0x3f,sizeof(dp));
    scanf("%lld%lld%lld",&n,&E,&T);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    l=r=1;int mn=0x3f3f3f3f;
    dp[0]=0;
    for(int i=1;i<=n;i++){
        while(l<=r&&2*(a[i]-a[q[l]+1])>=T){
            mn=min(mn,dp[q[l]]-2*a[q[l]+1]);
            l++;
        }
        dp[i]=min(dp[i],dp[q[l]]+T);
        dp[i]=min(dp[i],mn+2*a[i]);
        q[++r]=i;
    }
    printf("%lld\n",dp[n]+E);
    return 0;
}

[AGC007E] Shik and Travel

51nod 模拟赛搬过的题,当时完全不会。现在也只会某个 \(O(n^2\log n)\) 的东西。

最大值最小,二分答案。然后考虑到这个 dp 应该从左右儿子里各选一条边进来出去。

\(dp_{x,i,j}\)\(x\) 为根的子树内,从根节点到叶子的距离为 \(i\) ,叶子到根的距离为 \(j\) ,所有叶子到叶子的路径都小于当前二分的值 \(mid\) 的方案是否存在。那么转移考虑枚举两个叶子拼接起来,复杂度是 \(O(n^2)\) 的。

发现当两个状态 \(dp_{x,i_1,j_1},dp_{x,i_2,j_2}\) 满足 \(i_1\le i_2\)\(j_1\le j_2\) 时,后者是完全没必要存在的。于是我们现在将状态压缩到了 \(2n\) 级别的。使用启发式合并类似的复杂度证明可以得到总状态数是 \(O(n\log n)\) 的。

实现其实了解了思路之后比较好写,每个节点开个 vector 存状态,枚举左右儿子一个进去一个出去,双指针扫一遍即可。

#define int long long
int n,t,son[150010][2],w[150010][2];
vector<pair<int,int> >v[150010];
void dfs(int x,int val){
    v[x].clear();
    if(!son[x][0]){
        v[x].push_back(make_pair(0,0));return;
    }
    dfs(son[x][0],val);dfs(son[x][1],val);
    vector<pair<int,int> >ret;
    for(int s=0;s<2;s++){
        int tmp=val-w[x][0]-w[x][1];
        for(int i=0,j=0;i<v[son[x][s]].size();i++){
            while(j+1<v[son[x][s^1]].size()&&v[son[x][s^1]][j+1].first<=tmp-v[son[x][s]][i].second)j++;
            if(j>=v[son[x][s^1]].size()||v[son[x][s^1]][j].first>tmp-v[son[x][s]][i].second)continue;
            ret.push_back(make_pair(v[son[x][s]][i].first+w[x][s],v[son[x][s^1]][j].second+w[x][s^1]));
        }
    }
    sort(ret.begin(),ret.end());
    for(pair<int,int>p:ret){
        if(!v[x].empty()&&v[x].back().second<=p.second)continue;
        v[x].push_back(p);
    }
}
signed main(){
    scanf("%lld",&n);
    int l=0,r=0;
    for(int i=2;i<=n;i++){
        int f,val;scanf("%lld%lld",&f,&val);
        r+=val;
        if(son[f][0])son[f][1]=i,w[f][1]=val;
        else son[f][0]=i,w[f][0]=val;
    }
    while(l<r){
        int mid=(l+r)>>1;
        dfs(1,mid);
        if(!v[1].empty())r=mid;
        else l=mid+1;
    }
    printf("%lld\n",l);
    return 0;
}

[AGC007F] Shik and Copying String

神必贪心。——cmd

首先每个连续段显然由最右边能转移的转移过来。于是我们可以将所有变化看成折线,折线最靠右就行。

考虑怎么维护这个东西。划拉半天终于看懂了。

如图。

开一个队列记录向右拐的拐点的位置。然后每次的操作相当于把整条折线向左下移动一格(可以手模一下)。我们找到折线的起点和终点,然后把所有在终点后面的拐点扔掉,最后如果折线不是从上到下一条直线的话就把拐点加入进来,统计答案(就是拐点数量的最大值)。

记得特判相等。

int n,l,r,ans,q[1000010];
char s[1000010],t[1000010];
int main(){
    scanf("%d%s%s",&n,s+1,t+1);l=1;
    if(!strcmp(s+1,t+1)){
        puts("0");return 0;
    }
    for(int i=n,pos=n;i>=1;i--){//i是终点 pos是起点
        if(t[i]!=t[i-1]){
            pos=min(pos,i);
            while(pos&&s[pos]!=t[i])pos--;
            if(!pos){
                puts("-1");return 0;
            }
            while(l<=r&&q[l]-(r-l)>i)l++;//把在i后面的扔掉
            if(i!=pos)q[++r]=pos;
            ans=max(ans,r-l+1);
        }
    }
    printf("%d\n",ans+1);
    return 0;
}

完了。

posted @ 2022-11-16 20:28  gtm1514  阅读(49)  评论(0编辑  收藏  举报