Description
有三支队伍,分别是A,B,C。有n个游戏节目,玩第i个游戏,队伍A可以得到的分数是A[i],队伍B可以得到的分数是B[i],队伍C可以得到的分数是C[i]。由于时间有限,可能不是每个节目都能玩,于是节目主持人决定要从n个游戏节目里面挑选至少k个节目出来(被选中的节目不分次序),使得队伍A成为赢家。队伍A能成为赢家的条件是队伍A的总得分要比队伍B的总得分要高,同时也要比队伍C的总得分要高。节目主持人有多少种不同的选取方案?
Input
第一行,两个整数n和k。
第二行, n个整数,分别是A[1]、A[2]、A[3]…A[n]。
第三行, n个整数,分别是B[1]、B[2]、B[3]…B[n]。
第四行, n个整数,分别是C[1]、C[2]、C[3]…C[n]。
Output
一个整数,表示不同的选取方案数量。
Sample Input
3 2
1 1 2
1 1 1
1 1 1
Sample Output
3
【样例解释】
方案一:选取节目1和节目3。
方案二:选取节目2和节目3。
方案三:选取节目1、节目2、节目3。
Data Constraint
对于40%数据,2 <= n <= 20。
对于100%数据,2 <= n <= 34, 1 <= k <= min(n,7), 1 <=A[i], B[i], C[i]<= 10^9。
Solution
分两步处理:
第一步:把问题简单化,假设没有k的限制,设求出来的方案总数是x。
第二步:考虑k的限制,由于k<7,可以穷举n个节目取0个,n个节目取1个,n个节目取2个,n个节目取3个,n个节目取3个,n个节目取4个,n个节目取5个,n个节目取6个,穷举完这几种情况就可以知道哪些方案是合法的。而且Combinations(34,0) + Combinations(34,1) + Combinations(34,2) + Combinations(34,3) + Combinations(34,4) + Combinations(34,5) + Combinations(34,6) = 1676116。
也就是这个穷举不超过1676116次。设第二步的方案总数是y。
那么,最后应该输出的答案就是x - y。
第二步的方案数y可以搜索计算结果,下面重点讲讲第一步的方案数x如何求。
由于n最大是34,直接搜索会超时。可以把n个节目平均分成两部分,即第1至第n/2个节目归为第1部分,第n/2+1个节目至第n个节目归为第2部分。
第1部分:显然最多只有17个节目,每个节目可以取或者不取,穷举这17个节目的所有情况,显然有2^17种取法,对于每一种取法,队伍A都有一个得分,设为scoreA, 队伍B也有一个得分scoreB,队伍C也有一个得分scoreC。不妨设difAB1 = scoreA - scoreB, difAC1 = scoreA - scoreC,即每一种取法,都可以计算出一对值(difAB1,difAC1),
第2部分:显然最多也只有17个节目。每个节目可以取或者不取,穷举这17个节目的所有情况,显然也是有2^17种取法。同理,对于每一种取法,设difAB1 = scoreA - scoreB, difAC1 = scoreA - scoreC,即每一种取法都可以计算出一对值(difAB2,difAC2),
显然,如果一个方案要成立,必须要同时满足:
difAB1 + difAB2 > 0 (即队伍A的总得分比队伍B的总得分高)
difAC1 + difAC2 > 0 (即队伍A的总得分比队伍C的总得分高)
于是,问题转化为,枚举一对值(difAB1,difAC1),在第2部分里面查询有多少对(difAB2,difAC2),使得同时满足
difAB1 + difAB2 > 0
difAC1 + difAC2 > 0
显然,对于第2部分,可以用树状数组或者线段树之类的数据结构进行保存,以备第1部分的查询所需。
这里主要讲一下权值线段树的实现方法:
首先我们暴力求出来了四AB个数组,分别是:difAB1[],difAB2[],difAC1[]和difAC2[]
我们将difAB1从大到小排序,同是difAC1也跟着变,再将difAB2从小到大排序,difAC2跟着。
接着我们枚举difAB1,然后用一个j指针在difAB2中不断往后指,直到difAB1[i]+difAB2[j]>0,(因为difAB2是从小到大有序的,所以j指针不用更新)这个时候我们就满足difAB1 + difAB2 > 0了,接下来只要在j~len(difAC2)中找到difAC1[i]+difAC2[j]>0的总数就可以了,那么怎么找呢?这个时候就用到了权值线段树了,首先我们将difAC2中所有元素加入一颗权值线段树中,然后j指针每往后移动一位,就将difAC2[j]在权值线段树中删除(因为你要查询的区间右端点是固定的,既len(difAC2)),做完这些以后,就可以直接查询在j~len(difAC2)中有多少difAC1[i]+difAC2[j]>0的了,直接用区间查询,这个会吧。
由于分两步求答案,于是时间复杂度 = x + y = 2^17 * Log(2^17) + 1676116
Code
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll long long
#define N 50
#define M 1000010
using namespace std;
ll MAX=4000000009LL;
ll ans=0;
int n,k;
int a[35],b[35],c[35],ss=0,ls[M*3],rs[M*3],tree[M*3],tot=0;
ll f1[131073],f2[131073],g1[131073],g2[131073];
void dg(int x,int t,ll sab,ll sac){
if(t>=k) return;
if(x>n){
if(sab>0&&sac>0) tot++;
return;
}
dg(x+1,t+1,sab+a[x]-b[x],sac+a[x]-c[x]);
dg(x+1,t,sab,sac);
}
void dfs1(int x,ll sab,ll sac){
if(x>n/2){
f1[++f1[0]]=sab;
f2[++f2[0]]=sac;
return;
}
dfs1(x+1,sab+a[x]-b[x],sac+a[x]-c[x]);
dfs1(x+1,sab,sac);
}
void dfs2(int x,ll sab,ll sac){
if(x>n){
g1[++g1[0]]=sab;
g2[++g2[0]]=sac;
return;
}
dfs2(x+1,sab+a[x]-b[x],sac+a[x]-c[x]);
dfs2(x+1,sab,sac);
}
void qs1(int l,int r){
int i=l,j=r;
ll m=f1[(l+r)/2];
while(i<=j){
while(f1[i]>m) i++;
while(f1[j]<m) j--;
if(i<=j){
swap(f1[i],f1[j]);
swap(f2[i],f2[j]);
i++,j--;
}
}
if(l<j) qs1(l,j);
if(i<r) qs1(i,r);
}
void qs2(int l,int r){
int i=l,j=r;
ll m=g1[(l+r)/2];
while(i<=j){
while(g1[i]<m) i++;
while(g1[j]>m) j--;
if(i<=j){
swap(g1[i],g1[j]);
swap(g2[i],g2[j]);
i++,j--;
}
}
if(l<j) qs2(l,j);
if(i<r) qs2(i,r);
}
void insert(int x,ll l,ll r,ll k,int p){
tree[x]+=p;
if(l>=r) return;
ll mid=(l+r)>>1;
if(k<=mid){
if(!ls[x]) ls[x]=++ss;
insert(ls[x],l,mid,k,p);
if(!tree[ls[x]]) ls[x]=0;
}
else{
if(!rs[x]) rs[x]=++ss;
insert(rs[x],mid+1,r,k,p);
if(!tree[rs[x]]) rs[x]=0;
}
}
int find(int x,ll l,ll r,ll k){
ll t=0;
if(l==r) return tree[x];
ll mid=(l+r)/2;
if(k<=mid){
if(ls[x]) t=find(ls[x],l,mid,k);
}
else{
if(ls[x]) t=tree[ls[x]];
if(rs[x]) t+=find(rs[x],mid+1,r,k);
}
return t;
}
int main(){
freopen("show.in","r",stdin);
freopen("show.out","w",stdout);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<=n;i++) scanf("%d",&c[i]);
dg(1,0,0,0);
dfs1(1,0,0);
dfs2(n/2+1,0,0);
qs1(1,f1[0]);
qs2(1,g1[0]);
for(int i=1;i<=g2[0];i++) insert(0,0,MAX*2,-g2[i]+MAX,1);
int j=1;
for(int i=1;i<=f1[0];i++){
while(f1[i]+g1[j]<=0&&j<g2[0]){
insert(0,0,MAX*2,-g2[j]+MAX,-1);
j++;
}
if(f1[i]+g1[j]>0) ans+=find(0,0,MAX*2,f2[i]+MAX-1);
}
printf("%lld",ans-tot);
return 0;
}
作者:zsjzliziyang
QQ:1634151125
转载及修改请注明
本文地址:https://blog.csdn.net/zsjzliziyang/article/details/81771765