jzoj3512-游戏节目【树状数组,双向dfs】

正题


大意

有n个节目,每个节目对3个东西贡献不同,要求选择至少k个让第一个东西的值最大。求方案数


解题思路

至少k个我们可以计算选择任何个数的结果减去选择k个的结果。由于k比较小,我们考虑直接暴搜
数据不是很大,我们可以将节目分成两段进行搜索所有结果。
然后第一部分计算第1个东西的值减去第2个东西的值 ab1i a b 1 i ,和减去第3个东西的值 ac1i a c 1 i
第二部分一样计算 ab2i a b 2 i , ac2i a c 2 i
问题就变成了选择两个数 i i ,j使得

ab1iab2j>0 a b 1 i − a b 2 j > 0

and a n d

ac1iac2j>0 a c 1 i − a c 2 j > 0

然后我们将两个中的 ab a b 合在一起进行离散化,之后用一个树状数组或权值线段数进行第二部分查询每个区间内数的个数,这样就可以 n  log n n     l o g   n 查询了。
总共时间复杂度
O(217×log  217+1676116) O ( 2 17 × l o g     2 17 + 1676116 )


代码

#include<cstdio>
#include<algorithm>
#define ll long long
#define N 131080*4
#define lobit(x) x&(-x)
using namespace std;
struct ansnode{
  ll c,b;
  bool f;
}ans[N];
ll n,k,a[51],b[51],c[51],t,nz;
long long sum,tr[N],ans1;
void dfs(ll dep,ll w,ll sum1,ll sum2,ll sum3)//暴力处理k以内的结果
{
    if (w>=k) return;
    if (w<k) {
      if (sum1>sum2&&sum1>sum3)
        ans1++;
    }
    for (ll i=dep+1;i<=n;i++) dfs(i,w+1,sum1+a[i],sum2+b[i],sum3+c[i]);
}
void dfs1(ll dep,ll sum1,ll sum2,ll sum3)//第一部分搜索
{
    ans[++t].b=sum1-sum2;
    ans[t].c=sum1-sum3;
    ans[t].f=0;
    for (ll i=dep+1;i<=nz;i++) dfs1(i,sum1+a[i],sum2+b[i],sum3+c[i]);
}
void dfs2(ll dep,ll sum1,ll sum2,ll sum3)//第二部分搜索
{
    ans[++t].b-=sum1-sum2;
    ans[t].c-=sum1-sum3;
    ans[t].f=1;
    for (ll i=dep+1;i<=n;i++) dfs2(i,sum1+a[i],sum2+b[i],sum3+c[i]);
}
bool cmp1(ansnode x,ansnode y)//排序
{return x.c<y.c;}
bool cmp2(ansnode x,ansnode y)
{return x.b<y.b||x.b==y.b;}
void change(ll x,ll up)//树状数组——修改
{
    while (x<=up)
    {
        tr[x]++;
        x+=lobit(x);
    }
}
long long find(ll x)//树状数组——查询
{
    long long ans=0;
    while (x>0)
    {
        ans+=tr[x];
        x-=lobit(x);
    }
    return ans;
}
int main()
{
    //freopen("show.in","r",stdin);
    //freopen("show.out","w",stdout);
    scanf("%lld%lld",&n,&k);
    nz=n/2;
    for (ll i=1;i<=n;i++)
      scanf("%lld",&a[i]);
    for (ll i=1;i<=n;i++)
      scanf("%lld",&b[i]);
    for (ll i=1;i<=n;i++)
      scanf("%lld",&c[i]);
    dfs(0,0,0,0,0);//搜索
    dfs1(0,0,0,0);//第一部分搜索
    dfs2(nz,0,0,0);//第二部分搜索
    sort(ans+1,ans+1+t,cmp1);//离散化——排序
    ans[t+1].c=-2147483647;
    ll e=1,last=1;
    for (ll i=2;i<=t+1;i++)
    {
        if (ans[i].c!=ans[~-i].c)//离散化——去重,标号
        {
            for (ll j=last;j<i;j++)
              ans[j].c=e;
            e++;last=i;
        }
    }
    e--;
    sort(ans+1,ans+1+t,cmp2);
    ll o=0,g;
    while (o<=t)
    {
        g=ans[o].b;last=o;
        while (ans[o].b==g&&o<=t) o++;
        for (ll i=last;i<o;i++) if (!ans[i].f) sum+=find(~-ans[i].c);//查询
        for (ll i=last;i<o;i++) if (ans[i].f) change(ans[i].c,e);//修改
    }
    printf("%lld",sum-ans1);//输出
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值