2017-3-24校内训练

hzwer出题,因为有两道我们之前做过所以很快都A完了。

A.dispatching

题目大意:给定一棵n个点的数和费用上限m,每个点有费用ci和权值li,要求选出若干个费用和不超过m的点和这些点的一个公共祖先,求选出的点数乘上这个公共祖先的权值的最大值。(n<=100,000)

思路:对每个子树统计答案,求出子树内费用和不超过m的费用前k小,用k*li统计答案。随便打个启发式合并平衡树就可以了,复杂度O(nlogn^2)(或者主席树O(nlogn))。

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
inline int read()
{
    int x;char c;
    while((c=getchar())<'0'||c>'9');
    for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0';
    return x;
}
#define MN 100000
int fa[MN+5],w[MN+5],l[MN+5];
namespace treap
{
    int fa[MN+5],c[MN+5][2],p[MN+5],s[MN+5],rt[MN+5],a[MN+5],an;
    ll sm[MN+5];
    inline int ran()
    {
        static int x=23333;
        return x^=x<<13,x^=x>>17,x^=x<<5;
    }
    inline void up(int x)
    {
        s[x]=s[c[x][0]]+s[c[x][1]]+1;
        sm[x]=sm[c[x][0]]+sm[c[x][1]]+w[x];
    }
    int query(int x,ll k)
    {
        if(!x)return 0;
        if(sm[c[x][0]]+w[x]<=k)return s[c[x][0]]+1+query(c[x][1],k-sm[c[x][0]]-w[x]);
        return query(c[x][0],k);
    }
    void rotate(int x)
    {
        int f=fa[x],ff=fa[f],l=c[f][1]==x,r=l^1;
        fa[f]=x;fa[x]=ff;fa[c[x][r]]=f;
        c[ff][c[ff][1]==f]=x;c[f][l]=c[x][r];c[x][r]=f;
        up(f);up(x);
    }
    void ins(int x,int k)
    {
        int *i=rt+x,f=0;
        while(*i)f=*i,i=c[*i]+(w[k]>w[*i]);
        fa[*i=k]=f;c[k][0]=c[k][1]=0;p[k]=ran();
        for(f=k;f;f=fa[f])up(f);
        while(fa[k]&&p[k]>p[fa[k]])rotate(k);
        if(!fa[k])rt[x]=k;
    }
    void dfs(int x)
    {
        if(!x)return;
        dfs(c[x][0]);a[++an]=x;dfs(c[x][1]);
    }
    void merge(int x,int y)
    {
        int rv=an=0,i;
        if(s[rt[x]]<s[rt[y]])swap(x,y),rv=1;
        dfs(rt[y]);
        for(i=1;i<=an;++i)ins(x,a[i]);
        if(rv)rt[y]=rt[x];
    }
};
int main()
{
    int n,m,i;ll ans=0;
    n=read();m=read();
    for(i=1;i<=n;++i)fa[i]=read(),w[i]=read(),l[i]=read(),treap::up(treap::rt[i]=i);
    for(i=n;i;--i)
    {
        ans=max(ans,1LL*treap::query(treap::rt[i],m)*l[i]);
        if(fa[i])treap::merge(fa[i],i);
    }
    printf("%lld",ans);
}

D.超级钢琴

题目大意:给定一个长度为n的序列,求前k大的长度在[l,r]之间的子串和的和。(n,k<=500,000,1<=l<=r<=n)

思路:前缀和后对每个右端点用可持久化堆建出可用的左端点的小根堆,开个优先队列存下选各右端点时当前的最优答案,每次取出最优的那个右端点计入答案后把对应的堆顶弹掉。总复杂度O((n+k)logn)。

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
inline int read()
{
    int x,f=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')f=0;
    for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0';
    return f?x:-x;
}
#define MN 500000
#define ND 30000000
#define INF 0x3FFFFFFF
struct node{int l,r,mn;}t[ND+5];
int rt[MN+5],a[MN+5],tn;
class cmp{public:bool operator()(int x,int y){return a[x]-t[rt[x]].mn<a[y]-t[rt[y]].mn;}};
priority_queue<int,vector<int>,cmp> pq;
int build(int l,int r)
{
    int p=++tn,mid=l+r>>1;
    if(l<r)t[p].l=build(l,mid),t[p].r=build(mid+1,r);
    return t[p].mn=INF,p;
}
int change(int x,int l,int r,int k,int z)
{
    int p=++tn,mid=l+r>>1;
    if(l==r)return t[p].mn=z,p;
    if(k<=mid)t[p].l=change(t[x].l,l,mid,k,z),t[p].r=t[x].r;
    else t[p].l=t[x].l,t[p].r=change(t[x].r,mid+1,r,k,z);
    return t[p].mn=min(t[t[p].l].mn,t[t[p].r].mn),p;
}
int del(int x,int l,int r)
{
    int p=++tn,mid=l+r>>1;
    if(l==r)return t[p].mn=INF,p;
    if(t[t[x].l].mn<t[t[x].r].mn)t[p].l=del(t[x].l,l,mid),t[p].r=t[x].r;
    else t[p].l=t[x].l,t[p].r=del(t[x].r,mid+1,r);
    return t[p].mn=min(t[t[p].l].mn,t[t[p].r].mn),p;
}
int main()
{
    int n,k,l,r,i;long long ans=0;
    n=read();k=read();l=read();r=read();
    for(i=1;i<=n;++i)a[i]=a[i-1]+read();
    rt[0]=build(0,n);
    for(i=1;i<=n;++i)
    {
        rt[i]=rt[i-1];
        if(i>r)rt[i]=change(rt[i],0,n,i-r-1,INF);
        if(i>=l)rt[i]=change(rt[i],0,n,i-l,a[i-l]);
        pq.push(i);
    }
    while(k--)
    {
        i=pq.top();pq.pop();
        ans+=a[i]-t[rt[i]].mn;
        rt[i]=del(rt[i],0,n);
        pq.push(i);
    }
    printf("%lld",ans);
}

E.元素

题目大意:给定n个数,每个数有一个权值,要求选出若干个数,使其中不存在异或和为0的非空子集,求出选出的数的最大权值和。(n<=1000,数字<=10^18)

思路:线性基入门题。我比较菜没学过,现场学的,易知线性基满足不存在异或和为0的非空子集,而n个相同的数不管加入顺序,选出的线性基个数是相同的,我们把数字按权值从大到小排序后每个考虑能否加入当前线性基中即可,复杂度O(nlog10^18)。

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
inline ll read()
{
    ll x;char c;
    while((c=getchar())<'0'||c>'9');
    for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0';
    return x;
}
#define MN 1000
#define MK 60
struct node{ll x;int w;}a[MN+5];
bool cmp(node a,node b){return a.w>b.w;}
ll z[MK+5];
int main()
{
    int n=read(),i,j,ans=0;
    for(i=1;i<=n;++i)a[i].x=read(),a[i].w=read();
    sort(a+1,a+n+1,cmp);
    for(i=1;i<=n;++i)for(j=MK;j--;)if((a[i].x>>j)&1)
    {
        if(!z[j])z[j]=a[i].x,ans+=a[i].w;
        a[i].x^=z[j];
    }
    printf("%d",ans);
}
原文地址:https://www.cnblogs.com/ditoly/p/20170324C.html