1008 I love exam
题目思路
先将每个学科分开,看作单个背包,算出每个背包各个容量下的最大价值,主要要保证分数不能超过100。
然后我们需要将各学科合并。令dp[i][t][z]表示前i门学课花费t时间挂科数目位z的最大价值总和,f[i][j]表示第i门学科花费j时间的最大价值。
因为存在挂不挂科的情况,所以我们合并时要判断f[i][j]是否大于等于60。
当大于60并且当前挂科的数目大于0时我们的递推式就是
d
p
[
i
]
[
t
]
[
z
]
=
m
a
x
(
d
p
[
i
−
1
]
[
t
−
j
]
[
z
−
1
]
+
f
[
i
]
[
j
]
)
,
z
>
0
dp[i][t][z]=max(dp[i-1][t-j][z-1]+f[i][j]),z>0
dp[i][t][z]=max(dp[i−1][t−j][z−1]+f[i][j]),z>0
小于60时递推式为
d
p
[
i
]
[
t
]
[
z
]
=
m
a
x
(
d
p
[
i
−
1
]
[
t
−
j
]
[
z
]
+
f
[
i
]
[
j
]
)
,
z
>
0
dp[i][t][z]=max(dp[i-1][t-j][z]+f[i][j]),z>0
dp[i][t][z]=max(dp[i−1][t−j][z]+f[i][j]),z>0
ac代码
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <math.h>
#include <string.h>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <utility>
#define pi 3.1415926535898
#define ll long long
#define lson rt<<1
#define rson rt<<1|1
#define eps 1e-6
#define ms(a,b) memset(a,b,sizeof(a))
#define legal(a,b) a&b
#define print1 printf("111\n")
#define pb(x) push_back(x)
#define pair4 pair<pair<int,int>,pair<int,int> >
#define fi first
#define se second
using namespace std;
const int maxn = 3e5+10;
const int inf = 1e9+10;
const ll llinf =1e18+10;
const ll mod = 1e9+7;
int n,m,cnt,s,p;
string a;
map<string,int>mp;
vector<vector<pair<int,int> > >vec(15000+10);
int f[100][15000+10];
int dp[100][15000+10][5];
int main()
{
int _;
scanf("%d",&_);
while(_--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cin>>a;
mp[a]=i;
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int u,v;
cin>>a>>u>>v;
vec[mp[a]].push_back(make_pair(u,v));
}
scanf("%d%d",&s,&p);
ms(f,-inf);
for(int i=1;i<=n;i++)
{
f[i][0]=0;
for(int j=0;j<vec[i].size();j++)
{
for(int z=s;z>=0;z--)
{
int w=vec[i][j].se;
int v=vec[i][j].fi;
if(z>=w)
f[i][z]=max(min(100,f[i][z-w]+v),f[i][z]);
}
}
}
ms(dp,-inf);
dp[0][0][0]=0;
for(int i=1;i<=n;i++)//第i门
{
for(int j=0;j<=s;j++)//花费j时间
{
for(int t=0;t<=s;t++)//背包容量
{
for(int z=0;z<=min(i,p);z++)//挂科数目
{
int x=0;
if(f[i][j]<60)
{
x=1;
}
if(t>=j&&x==0)
dp[i][t][z]=max(dp[i][t][z],dp[i-1][t-j][z]+f[i][j]);
else if(t>=j&&z>0)
dp[i][t][z]=max(dp[i][t][z],dp[i-1][t-j][z-x]+f[i][j]);
}
}
}
}
int ma=-1;
for(int i=0;i<=s;i++)
{
for(int j=0;j<=p;j++)
{
ma=max(ma,dp[n][i][j]);
}
}
printf("%d\n",ma);
mp.clear();
for(int i=1;i<=n;i++)vec[i].clear();
}
}
1004 I love counting
题目思路
我们如果将a[i]看作位置i的纵坐标,那么这道题的问题基本跟第一场1010zoto的问题基本一样了
我们用莫队处理每次询问,并将a[i]对应的纵坐标进行分块,用num数组记录每个a[i]的出现的个数,用sum表示每个块里面不为0的数的个数。
然后就是我们怎么求这个区间有多少个数c异或a后的值小于等于b了。
我们将三个数都看成二进制的形式,但看一个二进制位j。
当b的第j位为1,a的第j位为1,那么当第j位为1的数最后异或值一定都小于答案,为0时不一定小于。
当b的第j位为1,a的第j位为0,那么当第j位为0的数最后异或值一定都小于答案,为1时不一定小于。
当b的第j位为0,a的第j位为1,那么当第j位为0的数最后异或值一定都大于答案,为1时不一定小于。
当b的第j位为0,a的第j位为0,那么当第j位为1的数最后异或值一定都大于答案,为0时不一定小于。
根据上面的分析,对于每个j,求出小于他的所有值,最后不要忘记加上异或后等于b的情况
具体操作见代码。
ac代码
int n,q;
int siz,len;
int a[maxn];
int belong[maxn];//表示查询所属的块
int now;
int ans[maxn];//记录查询的答案
int num[maxn],sum[maxn];//num记录每个a[i]出现的个数,sum记录当前块中大于0的数的个数
int k;//a[i]分块后块的个数
struct node
{
int l,r,a,b,id;
}e[maxn];
bool cmp(node a,node b)
{
return (belong[a.l]^belong[b.l])?belong[a.l]<belong[b.l]:((belong[a.l]&1)?a.r<b.r:a.r>b.r);//处理查询的排序方法
}
void add(int x)
{
if(++num[x]==1)sum[x/k]++;
}
void del(int x)
{
if(--num[x]==0)sum[x/k]--;
}
int calc(int x)//处理区间操作
{
int ans=0;
for(int i=0;i<x/k;i++)
ans+=sum[i];
for(int i=(x/k)*k;i<=x;i++)
ans+=(num[i]!=0);
return ans;
}
int main()
{
scanf("%d",&n);
k=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
siz=sqrt(n);
len=ceil((double)n/siz);
for(int i=1;i<=siz;i++)
{
for(int j=1;j<=len;j++)
{
belong[j+(i-1)*len]=i;
}
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d%d%d%d",&e[i].l,&e[i].r,&e[i].a,&e[i].b);
e[i].id=i;
}
sort(e+1,e+1+q,cmp);
int l=1,r=0;
for(int i=1;i<=q;i++)
{
int s=0;
int p=0;
int ql=e[i].l;
int qr=e[i].r;
while(l<ql)del(a[l++]);
while(l>ql)add(a[--l]);
while(r>qr)del(a[r--]);
while(r<qr)add(a[++r]);
int a=e[i].a;
int b=e[i].b;
for(int j=19;j>=0;j--)
{
p=s;
if((b>>j)&1)
{
if((a>>j)&1)
{
p|=(1<<j);
}else
{
s|=(1<<j);
}
ans[e[i].id]+=calc(p+(1<<j)-1)-calc(p-1);//从p+(1<<j)-1开始是因为我们要找到最大的值,举个例子:当前k为10000,那么我们从10000一直到10111都是满足异或a后小于b的,所以我们应该从10111开始。
}else
{
if((a>>j)&1)
{
s|=(1<<j);
}
}
}
ans[e[i].id]+=(num[e[i].a^e[i].b]!=0);//记录相等时的情况
}
for(int i=1;i<=q;i++)
{
printf("%d\n",ans[i]);
}
}
还有一种字典树的解法,用树状数组处理前缀和,因为卡了空间所以不能用线段树。。。然后我又不会树状数组就不补这种写法了。大致看了下思路,先统计每个右端点r的对应的区间,从小到大枚举r。
剩下的跟上述解法思路差不多。