2019中国大学生程序设计竞赛HDU6703 6705 6709

Array:HDU 6703

http://acm.hdu.edu.cn/showproblem.php?pid=6703

题意:给出有n个数的序列,每个值都不同,取值范围在1-n之间。

          有m个操作:(1,pos)序列a[pos]+10000000;(2,r,k)询问最小的值满足 不等于下标1—r之间的任何值 并且不小于k。

思路:因为序列是包含1—n的每个值,修改询问也是与值的大小有关,所以想到建权值线段树。

          询问中需要满足下标大于r,因此要记录下标值。大于r,尽量取大的,所以维护下标的最大值。

          询问中不少于k,答案大于等于k,r的取值1—n,当r等于n,序列中存的是n个数,答案为n+1,并且k的最大取值也为n,所以询问结果      在k—n+1之间,需要建一颗1—n+1的权值线段树。

          对于操作一:某个值加上很大的数,操作二询问的是最小值,那么修改完的值一定不会作为答案。但修改之前的值可以作为答案出现,它不同于现序列中的任何值。每次询问都可能取该值,所以将该值下标改为无穷大,保证每次能被询问到。

           对于操作二:要找到一个值大于等于k,下标大于r。所以询问k—n+1范围内第一个下标大于r的是多少。尽量往左子树找,如果找不到就往右子树找,进入左子树的时候判断值及下标最大值是否满足要求。

因为每次pos,r,k由上次结果异或得到,所以必须在线,复杂度为O( m * log n )

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int n,m,ans,x,y,z,a[maxn],b[maxn];
struct node
{
    int l,r,w;
}t[maxn<<2];
void build(int k,int l,int r)
{
    t[k].l=l;t[k].r=r;
    if(l==r){
        t[k].w=b[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    t[k].w=max(t[k<<1].w,t[k<<1|1].w);
}
void add(int k,int x)
{
    if(t[k].l==t[k].r){
        t[k].w+=10000000;
        return ;
    }
    int mid=(t[k].l+t[k].r)>>1;
    if(x<=mid) add(k<<1,x);
    else add(k<<1|1,x);
    t[k].w=max(t[k<<1].w,t[k<<1|1].w);
}
int ask(int k,int r,int v)
{
    if(t[k].l==t[k].r){
        if(t[k].l>=v&&t[k].w>r){
            return t[k].l;
        }
        else return -1;
    }
    int mid=(t[k].l+t[k].r)>>1;
    int ans=-1;
    if(v<=mid&&t[k<<1].w>r){
        ans=ask(k<<1,r,v);
    }
    if(ans==-1) ans=ask(k<<1|1,r,v);
    return ans;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            b[a[i]]=i;
        }
        b[n+1]=n+1;
        build(1,1,n+1);
        ans=0;
        for(int i=1;i<=m;i++){
            scanf("%d",&x);
            if(x==1){
                scanf("%d",&y);
                y^=ans;
                add(1,a[y]);
            }
            else{
                scanf("%d%d",&y,&z);
                y^=ans;z^=ans;
                ans=ask(1,y,z);
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}

PATH:HDU 6705

http://acm.hdu.edu.cn/showproblem.php?pid=6705

题意:给出n个点,m条边,多次询问第k大的路径值。

思路:直接跑bfs,直到找到第k大的路径。但一个点可能有多条出边,菊花图,会有很多填到队列里面。所以,就要选更小的,想到了扩展每个点时,按照边的权值从小到大选择下一点。用vector记录相连的点和边权,并按边权从小到大排序。

然后呢,扩展到什么时候停啊,如何判断是否够询问的最大值mx了?

维护两个优先队列,一个q用于扩展下一个点,再一个qq记录当前得到的路径值。当qq的大小<询问最大值mx时,直接往q里面添加新路径,qq里面直接记录这种情况下的路径大小。当qq的大小==询问最大值mx时,表示已经够了询问最大值的数量,但还没有完全得到mx个最小的路径值。所以判断当前qq中的最大值与当前值大小,如果最大值>当前,把最大值删去,换成当前值,进入q;最大值<当前,舍弃当前值,不进入q,qq,退出循环。这样可以控制加入q的元素个数,后面进入的都是尽量小的路径值。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> p;
const ll maxn=5e4+5;
vector<p> v[maxn];
ll n,m,qr,a,b,c,k[maxn],ans[maxn];
struct cmp{
    bool operator()(const p a,const p b)const{
        return a.second>b.second;
    }
};
bool cc(p a,p b)
{
    return a.second<b.second;
}
priority_queue<ll> qq;
priority_queue<p,vector<p>,cmp> q;
void bfs(ll mx)
{
    ll cnt=0;
    for(ll i=0;i<mx;i++){
        p f=q.top();
        q.pop();
        ll x=f.first,w=f.second;
        ans[++cnt]=w;
        if(cnt==mx) break;
        ll len=v[x].size();
        for(ll i=0;i<len;i++){
            p ff=v[x][i];
            ll xx=ff.first,ww=w+ff.second;
            if(qq.size()==mx){
                if(ww>qq.top()) break;
                else{
                    qq.pop();
                    qq.push(ww);
                    q.push(p(xx,ww));
                }
            }
            else{
                qq.push(ww);
                q.push(p(xx,ww));
            }
        }
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        while(!q.empty()) q.pop();
        while(!qq.empty()) qq.pop();
        scanf("%lld%lld%lld",&n,&m,&qr);
        for(ll i=0;i<=n;i++) v[i].clear();
        for(ll i=0;i<m;i++){
            scanf("%lld%lld%lld",&a,&b,&c);
            v[a].push_back(p(b,c));
            q.push(p(b,c));
            qq.push(c);
        }
        for(ll i=1;i<=n;i++){
            sort(v[i].begin(),v[i].end(),cc);
        }
        ll kk=0;
        for(ll i=0;i<qr;i++){
            scanf("%lld",&k[i]);
            kk=max(kk,k[i]);
        }
        bfs(kk);
        for(ll i=0;i<qr;i++){
            printf("%lld\n",ans[k[i]]);
        }
    }
    return 0;
}

Fishing Master:HDU 6709

http://acm.hdu.edu.cn/showproblem.php?pid=6709

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e5+5;
ll a[maxn],n,k;
priority_queue<ll> q;
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        while(!q.empty()) q.pop();
        scanf("%lld%lld",&n,&k);
        ll ans=k,sum=0;
        for(ll i=1;i<=n;i++){
            scanf("%lld",&a[i]);
            ans+=a[i];
            sum+=a[i]/k;
            q.push(a[i]%k);
        }
        if(sum>=n-1){
            printf("%lld\n",ans);
            continue;
        }
        else{
            ll m=n-1-sum;
            for(ll i=0;i<m;i++){
                ll x=q.top();
                q.pop();
                ans+=(k-x);
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值