hdu 5127/2014亚洲(广州)现场赛A - Dogs' Candies(XJB暴力/CDQ分治+动态凸包)

Dogs' Candies

Time Limit: 30000/30000 MS (Java/Others)    Memory Limit: 512000/512000 K (Java/Others)
Total Submission(s): 2193    Accepted Submission(s): 542


Problem Description
Far far away, there live a lot of dogs in the forest. Unlike other dogs, those dogs love candies much more than bones.

Every candy has two attributes: the sweetness degree p and the sourness degree q. Different dogs like different candies. A dog also has two attributes: the fondness degree for sweetness x and the fondness degree for sourness y. So the deliciousness degree of a candy for a dog is defined as p×x + q×y.

The dog king has a huge candy box. At first, the box is empty. The king can add candies to the box or take some candies from the box and eat them. There are always some dogs who want to know which candies in the box are the most delicious for them. Please help the king to answer their questions.
 

Input
The input consists of at most 10 test cases. For each test case, the first line contains an integer n indicating that there are n candy box operations(1 <= n <= 50000).

The following n lines describe the n operations.

Each operation contains three integers t, x and y( 0 <= |x|, |y| <= 109). The first integer t may be -1, 0, or 1.

If t equals -1, it means that a candy in the box with sweetness degree x and sourness degree y is eaten by the dog king.

If t equals 1, it means that a candy with sweetness degree x and sourness degree y is added to the candy box.

If t equals 0, it means that a dog with sweetness fondness degree x and sourness fondness degree y wants to know the maximal deliciousness degree of the candies in the box for him. 

It is guaranteed that every candy is unique in the box. 

The input ends by n = 0.
 

Output
For each operation in which t equals to 0, you should print the maximal deliciousness degree of the best candy for the dog.
 

Sample Input
  
  
6 1 2 1 1 1 2 1 1 1 0 2 1 -1 2 1 0 2 1 0
 

Sample Output
  
  
5 4
 

Source

题意:

一个狗国家的狗国王有一个装糖的盒子,每颗糖有两个属性p,q,分别代表甜度和咸度,每只狗对于甜度和咸度的偏爱度不一样,所以每条狗有两个参数x, y,每颗糖对于特定的狗的美味度等于p*x+q*y。现在有50000个操作,分为三种:

  • 将新的糖(p,q)放入盒子中
  • 将盒子中存在的糖(p,q)吃掉
  • 给出一条狗的参数(x,y),询问当前存在的糖的最大美味度。
xjb暴力代码

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <string.h>
#include <map>
#include <set>
#include <queue>
#include <deque>
#include <list>
#include <bitset>
#include <stack>
#include <stdlib.h>
#define lowbit(x) (x&-x)
#define e exp(1.0)
//ios::sync_with_stdio(false);
//    auto start = clock();
//    cout << (clock() - start) / (double)CLOCKS_PER_SEC;
typedef long long ll;
typedef long long LL;
using namespace std;
const int maxn=50005;
struct node
{
    int x,y,flag;
}a[maxn];
int main()
{
    int n;
    while(cin>>n && n)
    {
        for(int i=0;i<maxn;i++)
            a[i].flag=1;
        ll op,p,q;
        int pos=-1;
        while(n--){
        cin>>op>>p>>q;
        if(op==1)//add
        {
            a[++pos].x=p;
            a[pos].y=q;
        }
        else if(op==-1)//eat
        {
            for(int i=0;i<=pos;i++)
            {
                if(a[i].x==p && a[i].y==q)
                {
                    a[i].flag=0;
                    //break;
                }
            }
        }
        else//query
        {
            int f=1;
            ll ans=-1e18-1;
            ans=ans*2;
            for(int i=0;i<=pos;i++)
            {
                if(a[i].flag)
                {
                    f=0;
                    ll tmp=p*a[i].x+q*a[i].y;
                    ans=max(ans,tmp);
                }
            }
            //f?
            //cout<<0<<endl:cout<<ans<<endl;
            cout<<ans<<endl;
        }
        }
    }
    return 0;
}

转的正解:

解法:

这个题是2014年广州现场赛的A题,模拟赛做的时候虽然推出了公式,但是并没有发现最优解为凸包的性质,于是一直到比赛结束都没有做出这个题,但是当时最诡异的是,有一个排名比较靠后的队伍在16分钟就将此题一血拿走,整场比赛解出此题的不过10支左右队伍。当比赛结束的时候,看了一下别人的代码,发现我能在hust上面看到的代码居然都是暴力水过去的!…我觉得好醉啊…,继续在网上买了搜索此题题解,发现都是直接  n2 暴力来一遍的…,仔细想了一下, n2 暴力的话,复杂度在最坏的情况下为 2500025000 ,大概 6108 左右,这种复杂度我是无论如何都不敢敲的…,但是如果数据是随机生成的话,询问,插入,删除操作只占 500001/3 ,那么这个复杂度以当时的30s的时限来说,还是可以跑出结果的…于是这么一个现场10支队伍过的难题,就被这样水过去了。

此题虽然能够暴力水过去,但是无疑思考更一般的通解是很有必要的。 
后来听说翁教他们当时是分块暴力重建凸包过去的,我就去请教了一下翁教,终于由之前的公式发现了最优解在凸包上的这一重大性质。 
此题正解应该是cdq分治+维护动态凸包,或者分块暴力重建凸包。而我的做法是CDQ维护凸包。具体是这样的:

首先得先分析出最优解一定在凸包上这一性质:

考虑这样的两颗糖果 A(p1,q1),B(p2,q2) ,假设A比B优,那么存在这样的表达式: p1x+q1yp2x+q2y ,变形一下式子之后得到这样的表达式: p2p1q2q1yx(x>0q2>q1) ,也就是说,只要A,B满足上述式子就一定存在A比B更优。我们可以将q看成横坐标,p看成纵坐标,将每一颗糖看成一个点。假设 K=yx ,考虑这样的三个点 A(q1,p1),B(q2,p2),C(q3,p3) 。 设AB的斜率为 k1 , BC的斜率为 k2 , 则A比B优需要满足 k1<K ,B比C更优需要满足 k2<K ,那么B比A,C都优的话,就需要满足 k2<K<k1 ,也就是说需要满足 k2<k1 ,这种情况只有在三个点构成的图为上凸壳的时候才满足,如果图形是下凸壳的话,中间的B点一定不可能比AC都优。 
于是这里可以知道的是对于特定的 K 来说,最优解一定在可选点构成的上凸包上面,由于之前限定了 (x>0q2>q1) 当这个条件改变的时候,最优解也可能在下凸包上。于是我们只需要维护一个动态的凸包,对于每一个询问,在相应的上凸壳或下凸壳上二分斜率就能找到最优解。

至于这里如何维护凸包的话,如果做过维护动态凸包的类似题的话,此时肯定就迎刃而解。

我的方法是用CDQ分治做这个凸包。 
对于一个区间[l,r],我首先将存在于[l, mid]中的点集取出来做出一个凸包,对于[l, mid]之间的‘-1’操作,如果删除的是[l, mid]之间的点,那么就正常的删点;如果删的点是l左边的点,那么这个删除操作就忽略。然后利用构建好的这个凸包去更新[mid+1, r]中的所有询问操作。

看似好像没问题,但是实际上这是错的… 
因为在区间[mid+1, r]中可能存在删除操作,删掉了之前构建好的凸包上面的点,导致更新后面的询问出现错误,那么我们还需要做的就是在删除点之后,继续维护这个凸包。对于[mid+1, r]中的加点操作,我们是可以忽略的,因为后面更新右区间的时候也会更新的。 
删除点的维护操作是这样的,首先现在凸包上面找到这个点,如果没找到说明不会改变凸包,但是也要将这个点标记为已删除的点。如果这个点在凸包上面,那么找到这个点的在凸包上的前驱和后继,将该点删除,然后在最开始构筑凸包的那个数组中,重新构造前驱到后继之间的凸包,最后在加上后面的凸包即可。 
做完这一步的话,就可以正常的更新所有询问咯~ 
复杂度 O(nlog2nlog2n)

#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <map>
//#include <unordered_map>
#define N 50010
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const LL INF=0x3f3f3f3f3f3f3f3fLL;
void Open()
{
    #ifndef ONLINE_JUDGE
        freopen("D:/in.txt","r",stdin);
        //freopen("D:/my.txt","w",stdout);
    #endif // ONLINE_JUDGE
}
struct Point
{
    int x, y;
    int ty, opid;
    bool operator<(const Point& o)const{
        return x < o.x || (x == o.x && y < o.y);
    }
}can[N], p1[N], p2[N], pt[N], tmp[N];
int n;
int m1, m2;
map<PII, int> mp;
LL ans[N];
bool vis[N];
int cnt;

LL Cross(Point A, Point B){return (LL)A.x * B.y - (LL)A.y * B.x;}
Point operator-(Point A, Point B) {return (Point){A.x - B.x, A.y - B.y};}
void getConvex(int n)
{
    m1 = m2 = 0;
    sort(pt, pt+n);
    for(int i = 0; i < n; i++)
    {
        while(m1 > 1 && Cross(pt[i] - p1[m1-2], p1[m1-1] - p1[m1-2]) <= 0) m1--;
        p1[m1++] = pt[i];
    }
    for(int i = n-1; i >= 0; i--)
    {
        while(m2 > 1 && Cross(pt[i] - p2[m2-2], p2[m2-1] - p2[m2-2]) <= 0) m2--;
        p2[m2++] = pt[i];
    }
}

void updateConvex(Point deltP)
{
    int idx = -1;
    for(int i = 0; i < m1; i++)
    {
        if(p1[i].x == deltP.x && p1[i].y == deltP.y){idx = i;break;}
    }
    if(idx != -1)
    {
        int l = idx - 1, r = idx + 1;
        int tail = 0;
        for(int i = r+1; i < m1; i++)
            tmp[tail++] = p1[i];
        int lpt, rpt;
        if(l == -1) lpt = -1;
        if(r == m1) rpt = cnt - 1;
        for(int i = 0; i < cnt; i++){
            if(l != -1 && pt[i].x == p1[l].x && pt[i].y == p1[l].y && vis[pt[i].opid]) lpt = i;
            if(r != m1 && pt[i].x == p1[r].x && pt[i].y == p1[r].y && vis[pt[i].opid]) rpt = i;
        }
        ///
        m1 = l+1;
        for(int i = lpt + 1; i <= rpt; i++)
        {
            if(vis[pt[i].opid] == 0) continue;
            while(m1 > 1 && Cross(pt[i] - p1[m1-2], p1[m1-1] - p1[m1-2]) <= 0) m1--;
            p1[m1++] = pt[i];
        }
        for(int i = 0; i < tail; i++)
            p1[m1++] = tmp[i];
    }

    idx = -1;
    for(int i = 0; i < m2; i++)
    {
        if(p2[i].x == deltP.x && p2[i].y == deltP.y) {idx = i;break;}
    }
    if(idx != -1)
    {
        int l = idx - 1, r = idx + 1;
        int tail = 0;
        for(int i = r+1; i < m2; i++)
            tmp[tail++] = p2[i];
        int lpt, rpt;
        if(l == -1) lpt = cnt;
        if(r == m2) rpt = 0;
        for(int i = 0; i < cnt; i++){
            if(l != -1 && pt[i].x == p2[l].x && pt[i].y == p2[l].y && vis[pt[i].opid]) lpt = i;
            if(r != m2 && pt[i].x == p2[r].x && pt[i].y == p2[r].y && vis[pt[i].opid]) rpt = i;
        }
        ///
        m2 = l+1;
        for(int i = lpt - 1; i >= rpt; i--)
        {
            if(vis[pt[i].opid] == 0) continue;
            while(m2 > 1 && Cross(pt[i] - p2[m2-2], p2[m2-1] - p2[m2-2]) <= 0) m2--;
            p2[m2++] = pt[i];
        }
        for(int i = 0; i < tail; i++)
            p2[m2++] = tmp[i];
    }
}

void updateans(int x, int y, int idx)
{
    Point o =(Point){x, -y};
    if(m1 <= 2){
        for(int i = 0; i < m1; i++)
            ans[idx] = max(ans[idx], (LL)p1[i].x * y + (LL)p1[i].y * x);
    }else{
        int lb = -1, ub = m1-1;
        while(lb + 1 < ub)
        {
            int mid = lb + ub >> 1;
            if(Cross(p1[mid+1] - p1[mid], o) <= 0) lb = mid;
            else ub = mid;
        }
        lb = max(0, lb);
        ans[idx] = max(ans[idx], (LL)p1[lb].x * y + (LL)p1[lb].y * x);
        if(ub < m1) ans[idx] = max(ans[idx], (LL)p1[ub].x * y + (LL)p1[ub].y * x);
    }
    if(m2 <= 2) {
        for(int i = 0; i < m2; i++)
            ans[idx] = max(ans[idx], (LL)p2[i].x * y + (LL)p2[i].y * x);
    }else{
        int lb = -1, ub = m2 - 1;
        while(lb + 1 < ub){
            int mid = lb + ub >> 1;
            if(Cross(p2[mid+1] - p2[mid], o) <= 0) lb = mid;
            else ub = mid;
        }
        lb = max(0, lb);
        ans[idx] = max(ans[idx],(LL)p2[lb].x * y + (LL)p2[lb].y * x);
        if(ub < m2) ans[idx] = max(ans[idx], (LL)p2[ub].x * y + (LL)p2[ub].y * x);
    }
}
void divide(int l, int r)
{
    if(l >= r) return ;
    int mid = l + r >> 1;

    for(int i = l; i <= mid; i++) vis[i] = 0;
    for(int i = l; i <= mid; i++)
    {
        if(can[i].ty == 1) vis[i] = 1;
        if(can[i].ty == -1 && can[i].opid >= l && can[i].opid <= mid) vis[can[i].opid] = 0;
    }
    cnt = 0;
    for(int i = l; i <= mid; i++)
        if(vis[i]) pt[cnt++] = can[i];

    getConvex(cnt);

    for(int i = mid+1; i <= r; i++){
        if(can[i].ty == 0) updateans(can[i].x, can[i].y, i);
        if(can[i].ty == -1 && can[i].opid >= l && can[i].opid <= mid && vis[can[i].opid]){
            vis[can[i].opid] = 0;
            updateConvex(can[can[i].opid]);
        }
    }

    divide(l, mid);
    divide(mid+1, r);
}
int main()
{
    Open();
    while(~scanf("%d", &n) && n)
    {
        mp.clear();
        for(int i = 0; i < n; i++)
        {
            int ty, x, y;
            scanf("%d%d%d", &ty, &x, &y);
            can[i] = (Point){y, x, ty, i};
            if(ty == 0) swap(can[i].x, can[i].y), ans[i] = -INF;
            if(ty == 1) mp[PII(y, x)] = i;
            if(ty == -1) can[i].opid = mp[PII(y, x)];
        }
        divide(0, n-1);
        for(int i = 0; i < n; i++)
        {
            if(can[i].ty == 0)
                printf("%I64d\n", ans[i]);
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值