Codeforces Round #433(Div.2) C,D,E题目详解

C题题意:有n台飞机,起飞时间依次从1到n,但是现在会延期k分钟,也就是现在允许飞行的时间为k+1到k+n,每台飞机延期1分钟会有一个代价ci,并且不允许提前飞行,问如何安排飞行顺序使得代价最小。
思路:对于每台飞机来说,延期代价有大有小,那么延期对于代价大的来说,会影响更大,所以应该让代价大的飞机先选飞行时间。
用数学方法也同样可以得到该结论。读者有兴趣的话可以私聊博主,在这就不赘述了,毕竟比赛只有2个小时,不需要那么严谨。
按照上面的思路,先按照代价排序,然后让每个飞机都从自己原先起飞的时间往后遍历,看哪个时间没有被选,然后选它。。这样近似暴力的遍历会果断的TLE(哭),那么我们可以换个角度看看,我们也可以看成时刻选择飞机(精髓)。每个时刻可以选择原先在这个时刻之前起飞的代价最大的那个飞机。
代码如下:

#include<iostream>
#include<queue>
using namespace std;
typedef pair<long long, long long>P;
priority_queue<P>Q;
int theans[300005];
int main()
{
    int n, k;
    cin >> n >> k;
    int temp;
    for (int i = 1;i <=k;i++)
    {
        scanf("%d", &temp);
        Q.push(P(temp, i));
    }
    long long ans = 0;

    for (int i = k + 1;i <= n + k;i++)
    {
        if (i <= n)
        {
            scanf("%d", &temp);
            Q.push(P(temp, i));
        }
        P a = Q.top();Q.pop();
        ans +=1LL* (i - a.second)*a.first;
        theans[a.second] = i;
    }
    cout << ans << endl;
    for (int i = 1;i <= n;i++)
    {
        if (i != 1)
            printf(" ");
        printf("%d", theans[i]);
    }
    return 0;
}

D题题意:又是飞机。。有很多大使,原先在不同的位置(非0地点),都要去0地点,给出飞机起飞的时间,起点,终点(起点或终点中必有一个0)和代价,我们要在保证所有大使能够共同在0地点呆大于等于k天,问如何选飞机飞行可以满足题意并且花费最低。
思路:最直接的思路,求每个点的前缀最小值和后缀最小值,但求后缀最小值得往后推k天,也就是代表在第i天前和第i+k天后的最小花费。
代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int maxm = 200005;
struct node
{
    int d, f, t;
    long long c;
    bool operator<(const node &b)const
    {
        return d < b.d;
    }
}nodes[maxm];
const long long INF = 1e12;
long long L[1000005], R[1000005];
long long  mincost[maxm];
int main()
{
    long long n;
    int m, k;
    cin >> n >> m >> k;
    for (int i = 1;i <= m;i++)
    {
        scanf("%d%d%d%lld", &nodes[i].d, &nodes[i].f, &nodes[i].t, &nodes[i].c);
    }

    sort(nodes + 1, nodes + 1 + m);
    for (int i = 0;i <= 1000001;i++)
        L[i] = R[i] = n*INF;
    for (int i = 1;i <= n;i++)mincost[i] = INF;
    for (int i = 1, j = 1;i <= 1000000 - k + 1;i++)
    {
        L[i] = L[i - 1];
        while (nodes[j].d<i&&j <= m)
        {
            if (!nodes[j].t&&nodes[j].c < mincost[nodes[j].f])
            {
                L[i] = L[i] - mincost[nodes[j].f] + nodes[j].c;
                mincost[nodes[j].f] = nodes[j].c;
            }
            j++;
        }

    }
    for (int i = 1;i <= n;i++)mincost[i] = INF;
    for (int i = 1000000 - k + 1, j = m;i;i--)
    {
        R[i] = R[i + 1];
        while (nodes[j].d >= i + k&&j)
        {
            if (!nodes[j].f&&nodes[j].c < mincost[nodes[j].t])
            {
                R[i] = R[i] - mincost[nodes[j].t] + nodes[j].c;
                mincost[nodes[j].t] = nodes[j].c;
            }
            j--;
        }

    }
    long long ans = 1e18;
    for (int i = 1;i <= 1000000;i++)
        ans = min(ans, L[i] + R[i]);
    printf("%lld", ans > INF ? -1 : ans);
    return 0;
}

巧妙的写法,就算求出了一个最小值,如何判断所有人都到达了?试想飞机飞一次的代价为1e6,而最多有1e5个人,所有都取最大,那么来回最大花费也是2e11,所以我们将总代价初始成n*1e12,如果都可以飞到,那么ans就会是一个小于1e12的数,如果不能飞到,那么肯定是大于1e12的。

E题题意:好难讲啊这个题意。。给你一个n*n的矩形,每行每列都会有一个点,所以有n个点。一个区域被称为beautiful,如果它的任意一个对角都被点覆盖。所以一个矩形有n*(n-1)/2个beautiful区域。
现在给你一个区域,问你至少包含这个区域一块的beautiful区域有多少个。
思路:如果理解了题意,在思考下,就会容易的想到求所有不包括该区域的beautiful区域个数,然后总个数减去这个就行了。
那么根据容斥原理,我们先减掉在给定区域下部,上部,左部,右部的所有beautiful区域个数,然后加上左下角,右下角,左上角,右上角的beautiful区域个数。
现在问题就转化成了如何快速求出某区域被标记点的个数。
博主这里提供两种解法,读者可以自行体会其中的利弊。
第一种,这种题一看就和线段树有关,那么如何写呢?定义到第j列的线段树的区间[a,b]维护从第1列到第j列的第a行到第b行的标记点个数。
那么在计算第j列时,第j列的所有区间和应在j-1的前提下更新,如果该区间和j-1的区间相同,那么仍然用该该点做儿子,如果不同则新开一个点。专业名词可持久化线段树。
代码如下:

#include<iostream>
using namespace std;

const int maxn = 200005;
int Left[maxn*25];
int Right[maxn * 25];
int tot;
int rt[maxn];
int Sum[maxn * 25];
void insert(int L, int l, int r, int bef, int &idx)
{
    idx = ++tot;
    Left[idx] = Left[bef];
    Right[idx] = Right[bef];
    Sum[idx] = Sum[bef] + 1;
    if (l == r)return;
    int mid = l + r >> 1;
    if (L <= mid)
        insert(L, l, mid, Left[bef], Left[idx]);
    else insert(L, mid + 1, r, Right[bef], Right[idx]);
}

typedef long long ll;
ll work(ll a)
{
    return a*(a - 1) / 2;
}

int  query(ll L, ll R, ll l, ll r, ll idx)
{
    if (L <= l&&r <= R)return Sum[idx];
    if (L > R)return 0;
    ll mid = l + r >> 1;
    if (R <= mid)return query(L, R, l, mid, Left[idx]);
    else if (L >= mid + 1)return query(L, R, mid + 1, r, Right[idx]);
    else return query(L, mid, l, mid, Left[idx]) + query(mid + 1, R, mid + 1, r, Right[idx]);
}

int main()
{
    int n, q;
    cin >> n >> q;
    int temp;
    for (int i = 1;i <= n;i++)
    {
        scanf("%d", &temp);
        insert(temp, 1, n, rt[i - 1], rt[i]);
    }
    ll l, d, r, u;
    while (q--)
    {
        scanf("%lld%lld%lld%lld", &l, &d, &r, &u);
        ll ans = work(n);
        ans -= work(l - 1);
        ans -= work(n - r);
        ans -= work(d-1);
        ans -= work(n - u);
        temp = query(1, d - 1, 1, n, rt[l - 1]);
        ans += work(1LL*temp);
        temp = query(u + 1, n, 1, n, rt[l - 1]);
        ans += work(1LL*temp);
        temp = query(1, d - 1, 1, n, rt[n]) - query(1, d - 1, 1, n, rt[r]);
        ans += work(1LL*temp);
        temp = query(u + 1, n, 1, n, rt[n]) - query(u + 1, n, 1, n, rt[r]);
        ans += work(1LL*temp);
        cout << ans << endl;
    }
    return 0;
}

这份代码差点见不到各位了,跑了1600+ms,我们来分析一下复杂度。
建树每一列就要建一次,o(nlogn),每次查询询问4次和,o(4*q*logn)(不知道有没有分析对。。),总之,博主感觉跑得慢的主要原因就是建树的时间长了。
既然建树时间长,那么就不建这么多树了吧。
第二种做法,首先将在线操作变成离线操作,然后计算第i列左上角和左下角的时候就从1到i-1依次在树上加点,然后查询。计算右上角和右下角就从后往前加点。
这样就变成了简单的维护区间sum的线段树啦。
博主就简化用树状数组操作了。
代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define lowbit(x) ((x)&(-x))
typedef long long ll;
const int maxn = 200005;
int sum[maxn];
int p[maxn];
ll work(int a)
{
    return 1LL*a*(a - 1) / 2;
}
struct node
{
    int l, r, u, d,idx;
    ll lu, ld, ru, rd;
}nodes[maxn];
void add(int x,int num)
{
    while (x<maxn)
    {
        sum[x] += num;
        x += lowbit(x);
    }
}
int find(int x)
{
    int ans = 0;
    while (x)
    {
        ans += sum[x];
        x -= lowbit(x);
    }
    return ans;
}
bool cmp1(node a, node b)
{
    return a.l < b.l;
}
bool cmp2(node a, node b)
{
    return a.r < b.r;
}
ll ans[maxn];
int main()
{
    int n, q;
    cin >> n >> q;
    for (int i = 1;i <= n;i++)
        scanf("%d", &p[i]);
    for (int i = 1;i <= q;i++)
        scanf("%d%d%d%d", &nodes[i].l, &nodes[i].d, &nodes[i].r, &nodes[i].u), nodes[i].idx = i;
    sort(nodes + 1, nodes + 1 + q, cmp1);
    int now = 1;
    while (nodes[now].l==1)
    {
        now++;
    }
    for (int i = 1;i < n;i++)
    {
        add(p[i],1);
        while (nodes[now].l-1==i&&now<=q)
        {
            nodes[now].lu = find(n) - find(nodes[now].u);
            nodes[now].ld = find(nodes[now].d - 1);
            now++;
        }
    }
    memset(sum, 0, sizeof(sum));
    sort(nodes + 1, nodes + 1 + q, cmp2);
    now = q;
    while (nodes[now].r == n)now--;
    for (int i = n;i > 1;i--)
    {
        add(p[i],1);
        while (nodes[now].r + 1 == i&&now)
        {
            nodes[now].ru = find(n) - find(nodes[now].u);
            nodes[now].rd = find(nodes[now].d - 1);
            now--;
        }
    }
    ll num = work(n);
    ll temp;
    for (int i = 1;i <= q;i++)
    {
        temp = num;
        temp = temp - work(nodes[i].l - 1) - work(nodes[i].d - 1) - work(n - nodes[i].u) - work(n - nodes[i].r);
        temp += work(nodes[i].lu) + work(nodes[i].ld) + work(nodes[i].ru) + work(nodes[i].rd);
        ans[nodes[i].idx] = temp;
    }
    for (int i = 1;i <= q;i++)
        printf("%lld\n", ans[i]);
    return 0;
}

代码快了许多,483ms跑过,学会了再也不怕TLE啦。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值