2021杭电多校第二场补题

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[i1][tj][z1]+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[i1][tj][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。
剩下的跟上述解法思路差不多。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值