AtCoder Grand Contest 023

25 篇文章 0 订阅
17 篇文章 0 订阅

好久没有发布完整的博客了,最近忙着各种写题,包括cccc的系列博客一直堆到现在,还有概率dp,java等等,最近先补一波博客吧

从atcoder开始,好久都没打这个比赛了,其实也只是第二场了,题目难度感觉还行,水题不水,还是有一定价值的

Zero-Sum Ranges

思路:统计和为0的子串数量,2e5的数据范围宣判O(n^2)的暴力直接死刑,但其实这题考得是一个比较常见的技巧了,求前缀和,前缀和只差为0处比为0子串,即只需要统计相同的前缀和数量即可

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <map>
#include <cmath>
#include <string>
#include <queue>
#include <stack>

using namespace std;

const int maxn = 2e5+10;

typedef long long ll;

ll a[maxn];

map<ll,int> col;

int main()
{
    int n;
    scanf("%d",&n);
    a[0] = 0LL;
    col[0]++;
    ll temp;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&temp);
        a[i] = a[i-1] + temp;
        col[a[i]]++;
    }
    map<ll,int> :: iterator it;
    ll sum = 0LL;
    for(it = col.begin();it != col.end();it++)
    {
        ll now = it->second;
        sum += now * (now-1LL) / 2LL;
    }
    printf("%lld\n",sum);
    //cout <<col.size()<<"*"<< col[0] << "*" << col[1]  << "*" << col.size()<<endl;
    return 0;
}

B Find Symmetries

思路:一个方阵,经过一系列平移变换后可以变成其逆矩阵,问有多少种变换方式

毫无疑问,首先先考虑暴力,N=300,判断逆矩阵O(n^2)*平移矩阵O(n^2),凉了

然而,注意到题目中的方阵,突然发现,向下平移可以和向左平移等效,于是优化为O(n^3),成了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <map>
#include <cmath>
#include <string>
#include <queue>
#include <stack>

using namespace std;

const int maxn = 350;

char a[maxn][maxn];

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%s",a[i]);
        //printf("%s\n",a[i]);

    }
    int sum = 0;
    for(int k=0;k<n;k++)
    {
        bool yes = true;
        for(int i=0;yes&&i<n;i++)
        {
            for(int j=0;yes&&j<n;j++)
            {
                int ii = i+k;
                int jj = j+k;
                if(i+k>=n)
                {
                    ii -= n;
                }
                if(j+k>=n)
                {
                    jj -= n;
                }
                if(a[ii][j] != a[jj][i])
                {
                    yes = false;
                }
            }
        }
        if(yes)
        {
            sum += n;
        }
    }
    printf("%d\n",sum);
    return 0;
}

C Painting Machines

思路:n个连续区域,n-1台涂色机器,每个机器i只能涂i和i+1连续的两个区域,按顺序使用机器,每种涂色方案最少所需机器数为该方案得分,求所有方案总得分和

一开始是比较懵逼的,打表找规律,感觉是个组合数,似乎有点头绪,但还是比较迷茫,关键还是转化的寻求,寻找组合数的使用机会,只要思路对了,就没什么太大问题了

最后,从代码美化的角度,这题算是摆上个组合数表的模板

具体思路怕忘,在码完代码后记录在源码注释中了,这里就不再赘述了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <map>
#include <cmath>
#include <string>
#include <queue>
#include <stack>

using namespace std;

const int maxn = 1e6+10;

const long long mod = 1e9+7;

int Fac[maxn],inv[maxn],invFac[maxn],f[maxn],s[maxn];
inline long long C(int n,int m)
{
    return 1LL*Fac[n]*invFac[m]%mod*invFac[n-m]%mod;
}

int main()
{
    int n;
    cin>>n;
    long long ans=0LL,li=(n+1LL)/2LL;
    Fac[0]=inv[0]=inv[1]=invFac[0]=invFac[1]=1LL;
    for(int i=1;i<=n;i++)Fac[i]=1LL*Fac[i-1]*i%mod;//阶乘表
    for(int i=2;i<=n;i++)inv[i]=(mod-1LL*(mod/i)*inv[mod%i]%mod)%mod;//逆元表
    for(int i=2;i<=n;i++)invFac[i]=1LL*inv[i]*invFac[i-1]%mod;//逆元阶乘表
    for(int i=li;i<n;i++)s[i]=1LL*C(i-1,n-1-i)*Fac[i]%mod*Fac[n-1-i]%mod;//s表
    for(int i=n-1;i>=li;i--)f[i]=(s[i]-s[i-1]+mod)%mod;//f表
    for(int i=n-1;i>=li;i--)ans=(ans+1LL*i*f[i])%mod;//计算结果
    cout<<ans<<endl;
    return 0;
}

/*
逆元表
阶乘表
组合数计算表
假设需要k次完成操作的情况有s【k】种,则ans = Σ s(k) * k,(n+1)/2 <= k <= n-1
然而,如果但求s(k)的话,选择的顺序将会是一个很麻烦的事情
那么不如求f(k),用来表示最多k次完成操作的情况
于是s(k) = f(k) - f(k-1)
先排除最右边,使n-2 machine对应n-2 square, 需要k-1次操作
相当于接下来的k-1次操作每次可以涂满一个或者两个square,总需要n-1个square
先假设都涂了一个square,则还需n-2-(k-1) = n-k-1,即在k-1次操作中选择n-k-1次涂两个
C(k-1,n-1-k)
对于涂的顺序随意,于是f(k) = C(k-1,n-1-k) * (k-1)! * (n-1-k)!
接下来就都是表了
*/

D Go Home

思路:这题就是个典型的贪心了,至于贪心的原则,又是很麻烦的最智能人,就是可以预知未来而使自己的利益最大化,于是事情就变得异常复杂

对于复杂情况不好处理的话,于是想起逆向思考,考虑最好车上只剩最远处两处人的情况

若两处人在同侧,就十分简单了,直接把车开过去就行了

而对于两侧都有人的情况,设为a,b,显然车会先开向人多的那侧,再开向人少的那一侧,在这种情况下,无论如何,人多的那侧到达的时间都会是人少的那一侧的时间加上他俩距离行驶时间,即

当p(a)>p(b)时,t(b) = t(a) + dis(a,b);

于是,此时b的时间就和a的时间息息相关了,在最后一种情况到来之前,b将会完全加入a的阵营,使a的利益最大化,从而使自己的利益最大化

这就是一个递归贪心的策略了,从最远两边开始,从后往前贪心,从前往后递归出结果,最后扫描一遍,找出最长时间即为答案

注意一下递归终止条件,就是当某侧没人时车会径直开向另一侧,从而得出虽有剩余的时间,这就是递归的最末端

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <map>
#include <cmath>
#include <string>
#include <queue>
#include <stack>

using namespace std;

typedef long long ll;

const int maxn = 1e5+10;

typedef struct APARTMENT
{
    ll x,p,t;
}Apartment;

Apartment apartment[maxn];

bool cmp(const Apartment &a,const Apartment &b)
{
    if(a.x<b.x)
    {
        return true;
    }
    else
    {
        return false;
    }
}

void car(int st,int en,int s)
{
    if(apartment[st].x>s)
    {
        for(int i=st;i<=en;i++)
        {
            apartment[i].t = apartment[i].x - s;
        }
    }
    else if(apartment[en].x<s)
    {
        for(int i=st;i<=en;i++)
        {
            apartment[i].t = s - apartment[i].x;
        }
    }
    else
    {
        if(apartment[st].p>=apartment[en].p)
        {
            apartment[st].p += apartment[en].p;
            car(st,en-1,s);
            apartment[en].t = apartment[st].t + apartment[en].x - apartment[st].x;
        }
        else
        {
            apartment[en].p += apartment[st].p;
            car(st+1,en,s);
            apartment[st].t = apartment[en].t + apartment[en].x - apartment[st].x;
        }
    }
    return ;
}

int main()
{
    ll n,s;
    ios::sync_with_stdio(false);
    cin >> n >> s;
    for(int i=0;i<n;i++)
    {
        cin >> apartment[i].x >>apartment[i].p ;
    }
    sort(apartment,apartment+n,cmp);
    car(0,n-1,s);
    ll sum = 0;
    for(int i=0;i<n;i++)
    {
        if(apartment[i].t>sum)
        {
            sum = apartment[i].t;
        }
    }
    cout << sum << endl;
    return 0;
}

这次比赛感觉题目很巧妙,特别是c,d两题,寻求转化和贪心,耐人寻味


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值