Codeforces Round #456 (Div. 2) 题解

本文提供Codeforces Round #456 (Div.2) 中三道题目(C/D/E)的详细解析与AC代码。涵盖扫描线算法处理MOBA游戏问题、BFS与贪心算法求解期望值问题、以及利用双指针与二分法求解特定数值问题。

Codeforces Round #456 (Div. 2)

Codeforces 912C

题意

本题题意较为复杂,是以MOBA游戏作为背景的:直接放链接吧

解题思路

其实本题的Note就是对解决本题的绝佳提示,我们可以用扫描线来处理该区域能击杀多少个英雄,该区域的最佳使用大招的时间就是该区域结束的时间。

需要注意的是判断最优价值无限大的条件:

1.价值增长速率不为\(0\)

2.有英雄最大生命值小于等于大招的伤害 或 有英雄生命恢复速率为0,并且初始生命值小于大招的伤害

实现上,用set或者优先队列管理时间线,用两个map分别管理某个时间点能击杀的英雄增加了多少,和减少了多少

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
typedef pair<int,int> pii;
set <long long> timeline;
map <long long,int> erased,added;
vector <pii> enemy[maxn];
int regen[maxn],maxhealth[maxn],n,m,bounty,increase,damage; 
int main(int argc, char const *argv[])
{
    scanf("%d%d",&n,&m);
    scanf("%d%d%d",&bounty,&increase,&damage);
    for (int i=1;i<=n;i++)
    {
        int sh;
        scanf("%d%d%d",&maxhealth[i],&sh,&regen[i]);
        enemy[i].push_back(make_pair(0,sh));
    }
    for (int i=1;i<=m;i++)
    {
        int t,e,h;
        scanf("%d%d%d",&t,&e,&h);
        enemy[e].push_back(make_pair(t,h));
    }

    for (int i=1;i<=n;i++)
    {
        vector<pii> &v=enemy[i];
        sort(v.begin(),v.end());

        if(increase&&(damage>=maxhealth[i]||(!regen[i]&&v[0].first<=damage))){
            //cout<<(int)(damage>=maxhealth[i])<<(int)(!regen[i]&&v[0].first<=damage)<<endl;
            puts("-1");
            return 0;
        }

        for (int j=0;j<v.size();j++)
        {
            if(v[j].second>damage)  continue;

            long long nxtpos=2e9;
            if(regen[i])    nxtpos=min(nxtpos,1ll*(damage-v[j].second)/regen[i]);
            if(j!=v.size()-1)   nxtpos=min(nxtpos,1ll*(v[j+1].first-v[j].first-1));

            added[v[j].first]++;
            erased[v[j].first+nxtpos]++;


            timeline.insert(v[j].first);
            timeline.insert(v[j].first+nxtpos);

        }

    }

    long long ans=0,cnt=0;
    for (auto i: timeline){
        cnt+=added[i];
        ans=max(ans,cnt*(bounty+1ll*increase*i));
        cnt-=erased[i];
    }

    cout<<ans<<endl;    
    return 0;
} 

Codeforces 912D

题意

给定\(n \times m\) 的鱼塘和\(r \times r\) 的渔网,投放\(k\)条鱼,求能捕到鱼数最大的期望

解题思路

首先不难想到转化为投放在某个位置的期望:$ \frac{\text{这个位置能被多少渔网覆盖}}{r\times r}$

进而贪心地选择期望最大的\(k\)个位置就行了,但如果要求出所有\(n \times m\)个值进而求出前\(k\)个值是过不了的,不过可以发现更“靠近”中心的位置更优(显然的),所以我们从中心位置出发做\(BFS\),在已扩展的点中选择最优值就可以了。

这题的教训:map的去重不是看数据内容是否相同的,也不是要重载== 运算符,而是一般要对象及对象中的属性对象的<操作符进行重载,重载的<操作应满足“严格弱排序”。map并不是用==操作符来判断两个对象是否相等的,而是两个对象相互都不“小于”时认为两个对象相等。

复杂度\(O(k \log k)\)

AC代码

#include <bits/stdc++.h>
using namespace std;
int n,m,r,k;
long long solve(int i,int j)
{
    int tx=min(n,i+r-1)-max(i-r+1,1)-r+2;
    int ty=min(m,j+r-1)-max(j-r+1,1)-r+2;
    return 1ll*tx*ty;
}
struct node
{
    int x,y,z;
    node(int _x=1,int _y=1):x(_x),y(_y){z=solve(x,y);};

    bool operator < (const node& rhs) const
    {
        return z<rhs.z;
    }
};
int dx[]={-1,1,0,0},dy[]={0,0,-1,1};
map < pair<int,int> , bool > vis;
priority_queue<node> Q;
long long bfs()
{
    Q.push(node((n+1)/2,(m+1)/2));
    vis[make_pair((n+1)/2,(m+1)/2)]=true;
    long long res=0;
    while (k--)
    {
        node u=Q.top();
        Q.pop();
        res+=u.z;
        for (int i=0;i<4;i++){
            node v=node(u.x+dx[i],u.y+dy[i]);
            if(vis[make_pair(v.x,v.y)]) continue;
            if(v.x<1||v.x>n||v.y<1||v.y>m)  continue;
            vis[make_pair(v.x,v.y)]=true;
            Q.push(v);
        }
    }
    return res;
}
int main(int argc, char const *argv[])
{
    cin>>n>>m>>r>>k;
    printf("%.10f\n",1.0*bfs()/(1.0*(n-r+1)*(m-r+1)));
}

Codeforces 912E

题意

给定素数集合\(p\)\(Card\ p \le 16\)\(p_i \le 100\),设集合\(S=\text{素因子只在}p\text{中的集合}\), 求\(S\)中第\(k\)大的数,保证这个数\(\le 1e18\)

解题思路

如果直接求\(S\),复杂度难以接受,这里我们将集合\(p\)拆成\(A\)\(B\),那么\(S\)中的每个元素都是\(A\)\(B\)中元素的积,可以通过双指针法来求\(S\)中小于某个数有多少个值,进而我们可以通过二分法求出答案。

时间复杂度\(O(\log\ 1e18\ (S_A+S_B) )\)

对于\(A,B\)的选择,不是简单地分成两份,因为素因子小的集合产生的数多,经测试,排序后将前6个数分出来最坏情况下两个集合大小都不超过\(1e6\),其他的数都不够优

AC代码

#include <bits/stdc++.h>
using namespace std;
int prime[20],n,k;
const long long maxnum=1e18;
vector <long long> A,B;
void dfs(long long value,int from,int to,vector<long long> &out)
{
    out.push_back(value);
    for (int i=from;i<=to;i++)
        if(maxnum/prime[i]>=value)
            dfs(value*prime[i],i,to,out);
}
long long solve(long long x)
{
    int j=0;
    long long res=0;
    for (int i=A.size()-1;i>=0;i--)
    {
        while(j<B.size()&&B[j]<=x/A[i])
            j++;
        res+=j;
    }
    return res;
}
int main(int argc, char const *argv[])
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",&prime[i]);
    sort(prime+1,prime+n+1);
    scanf("%d",&k);
    dfs(1,1,min(6,n/2),A);
    dfs(1,min(n/2,6)+1,n,B);
    sort(A.begin(),A.end());
    sort(B.begin(),B.end());
    //cout<<A.size()<<" "<<B.size()<<endl;
    long long l=1,r=maxnum,mid,ans;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(solve(mid)>=k)
        {
            ans=mid;
            r=mid-1;
        }
        else
            l=mid+1;
    }
    cout<<ans<<endl;
    return 0;
}

转载于:https://www.cnblogs.com/falseangel/p/8458520.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值