NOIP 鸡王争霸赛

竞赛时间: 2018 年 8 月 23 日
By –老王
最终测试均打开 O2 优化



bag vest helmet
bag.in vest.in helmet.in
bag,out vest.out helmet.out
2s 2s 2s
512MB 512MB 512MB
忽略行末空格和
回车
忽略行末空格和
回车
忽略行末空格和
回车
传统 传统 传统




Problem A:三级包(bag.c/cpp/pas)

Input file: bag.in
Output file: bag.out
Time limit: 2 seconds
Memory limit: 512 megabytes


老王被同学拉去吃鸡,一落地就捡到了三级包,同时地上还有很多其他老王
想要带走的东西, M416、 5.56 子弹、垂直握把、 8 倍镜等等,但是显然老王不
能带走所有的东西。老王的三级包比较奇特,不仅有重量限制,而且有物品个数
限制。地上的每一种装备个数都记为 1 并有自己的重量,老王想要在不超过限制
的情况下带走尽可能重的东西,为了帮助老王吃鸡,请你来完成这个问题。
Input
第一行两个正数 n、 m 分别表示三级包的个数限制和容量限制
第二行一个整数 k 表示地上的装备数量
接下来一行 k 个整数, 第 i 个整数

Output
输出一共一行一个整数, 表示老王能带走的最大重量

Example

Sample Input1

5 100
8
8 64 17 23 91 32 17 12

Sample Output1

99

Sample Input2

5 10
3
99 99 99

Sample Output2

0

Notes
30%的数据,满足k ≤ 15
另有20%的数据,满足k ≤ 20
另有20%的数据,满足n ≤ 6
对于100%的数据, k ≤ 40n ≤ 35, 未提及的数据均是110^9之间的整数。

30 分解法: 2 ^n爆搜即可
50 分解法: 30 分做法随便剪枝优化一下
70 分解法: 因为n ≤ 6,可以从1~n枚举选取物品个数,然后
爆搜, C[k][6]也没多大,所以跑过毫无压力(+50 分做法)

100 分解法:
先预处理前一半物品, 枚举这部分物品的所有选取集合, 并
用 set 存下来 S[i]里面存放选取物品数量不超过 i 的所有权值
集合。同样,枚举后一半物品的所有选取集合,并到对应的
set 里面二分查找满足要求的最大权值即可,时间复杂度
O(tlogt)

感谢黄学长(神犇)原代码

//
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int w[50],n,m,k;
int cnt1[1<<21],cnt2[1<<21];
ll sum1[1<<21],sum2[1<<21],ans;
vector<ll> v[50];
int lowbit(int x)
{
    return x&-x;
}
int main()
{
     cin>>n>>m>>k;
    for(int i=1;i<=k;i++)cin>>w[i];
    if(k==1)
    {
        if(w[1]<=m)cout<<w[1];
        else cout<<0;
        return 0;
    }
    int mid=k/2;
    int tot=(1<<mid)-1;
    for(int i=1;i<=mid;i++)sum1[1<<i-1]=w[i];//压缩1集合 
    for(int s=1;s<=tot;s++)
    {
        sum1[s]=sum1[s^lowbit(s)]+sum1[lowbit(s)];//s集合为sum1[s^lowbit(s)]的集合(s去掉lowbits位)情况加上lowbit位的情况 (lowbits位为一) 
        cnt1[s]=cnt1[s^lowbit(s)]+1;//个数限制 
        if(sum1[s]<=m&&cnt1[s]<=n)//满足条件 
        {
            ans=max(ans,sum1[s]);
            v[cnt1[s]].push_back(sum1[s]);//在选择cnt1个物品的情况下的一个代价 
        }
    }
    for(int i=1;i<=mid;i++)sort(v[i].begin(),v[i].end());
    for(int i=1;i+mid<=k;i++)sum2[1<<i-1]=w[i+mid];//压缩2集合 
    mid=k-mid;
    tot=(1<<mid)-1;
    vector<ll>::iterator it;
    for(int s=1;s<=tot;s++)
    {
        sum2[s]=sum2[s^lowbit(s)]+sum2[lowbit(s)];
        cnt2[s]=cnt2[s^lowbit(s)]+1;
        if(cnt2[s]<=n&&sum2[s]<=m)
        {
            ans=max(ans,sum2[s]);
            for(int j=1;j<=n-cnt2[s];j++)//和一集合匹配 
            if(!v[j].empty())
            {
                it=upper_bound(v[j].begin(),v[j].end(),m-sum2[s]);//二分查找满足要求的最大权值 
                if(it!=v[j].begin())
                {
                    it--;
                    ans=max(ans,(*it)+sum2[s]);
                }
            }
        }
    }
    cout<<ans;
    return 0;
}

Problem B:三级甲(vest.c/cpp/pas)

Input file: vest.in
Output file: vest.out
Time limit: 2 seconds
Memory limit: 512 megabytes

在皮卡多捡到了一件三级甲的老王非常兴奋, 想马上测试一下三级甲的性能,
于是老王走到了加油站旁边的空地上跳俄舞,不一会儿身上就被打了一排子弹,
老王躲起来后脱下三级甲,发现这一排弹孔深度不一,聪明的老王想到了一个量
化计算性能的方法,给定一个 k(k ≤ n),计算出所有长度为 k 的区间的弹孔深度
的最小值的和,当然 k 值不同,得到的性能值也不同,所以老王会取不同的 k 进
行计算,为了帮助老王吃鸡,这个问题就交给你来解决。
Input
第一行两个正数 n、 m 分别表示弹孔个数和询问次数
第二行共 n 个整数,表示这一排每个弹孔的深度

Output
输出共 m 行,每行输出一个整数表示对应询问的答案

Example

Sample Input1

2 2
1 1
1

2

Sample Output1

2

1

Sample Input2

5 3
10 3 20 30 11

2

4

5

Sample Output2

37

6

3

Sample Input3

9 5
11 33 78 55 26 100 200 89 98
2

3

4

5

6

Sample Output3

429

300

204

115

89

30 分解法:随便怎么做
50 分解法:分析每一个元素对于不同询问区间长度的贡献,
l 为它到左边第一个比他小元素的距离, r 为它到右边第一个
比他小元素的距离,那么可以发现,贡献呈这种趋势“
于是可以预处理每个元素的 l, r, O(1)即可得到每个元素对
所询问区间长度的贡献,于是O(  n^2)

60 分解法: 50 分+送的 10 分
100 分解法: 注意到,所询问就是如上图中多个梯形,在某
一个 x 处的权值和,而上述梯形又显然可分成三段,两段公
差为 1 的等差数列和一段常数数列,于是三段分开前缀后缀
之类的O(n)预处理一下,每个询问O(1),于是最终复杂度
O(n)

//
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
inline char nc()
{
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int rd()
{
    char ch=nc();
    int sum=0;
    while(!(ch>='0'&&ch<='9'))ch=nc();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
    return sum;
}
typedef long long ll;
ll ans1[1000005],ans2[1000005];
int n,m;
int a[1000005],l[1000005],r[1000005];
stack<int> s;
int main()
{
    n=rd(),m=rd();
    for(int i=1; i<=n; i++)
    {
        a[i]=rd();
    }
    s.push(0);
    for(int i=1; i<=n; i++)
    {
        while(!s.empty()&&a[s.top()]>a[i])//如果a[i]小于栈顶元素
        {
            r[s.top()]=i-1; //栈顶最右不能超过a[i]
            s.pop();
        }
        if(!s.empty()&&a[s.top()]==a[i])//特判相等
        {
            l[i]=l[s.top()];//左取小于等于 
            r[s.top()]=i-1;//右不取
            s.pop();
        }
        else l[i]=s.top()+1;//栈顶元素小于a[i],a[i]最左不能超过顶元素
        s.push(i);//i号入栈
    }
    while(!s.empty())r[s.top()]=n,s.pop();// 栈内还有元素则说明没有a[i]小于等于栈顶元素,则栈顶元素最小,最右影响到n号
    int L,R;
    for(int i=1; i<=n; i++)//讨论i号点的贡献
    {
        L=i-l[i]+1;//L到R区间分情况讨论
        R=r[i]-i+1;
        if(L<=R)
            //if(k<=L)ans+=k*a[i];梯形左边
            //if(L<k<R)ans+=L*a[i];梯形上边
            //if(R<=k<=R+L)ans+=(R+L-k)*a[i];梯形右边
            //分有无k进行差分
        {
            if(L>1)
            {
                ans2[1]+=a[i];//差分ans2:a[i] 
                ans2[L]-=a[i];
            }
            ans1[L]+=1ll*L*a[i];
            ans1[R+1]-=1ll*L*a[i];
            if(R+1<=L+R-1)
            {
                ans1[R+1]+=1ll*(R+L)*a[i];
                ans1[L+R]-=1ll*(R+L)*a[i];
                ans2[R+1]-=a[i];
                ans2[L+R]+=a[i];
            }
        }
        else
        {
            if(R>1)
            {
                ans2[1]+=a[i];
                ans2[R]-=a[i];
            }
            ans1[R]+=1ll*R*a[i];
            ans1[L+1]-=1ll*R*a[i];
            if(L+1<=L+R-1)
            {
                ans1[L+1]+=1ll*(L+R)*a[i];
                ans1[L+R]-=1ll*(L+R)*a[i];
                ans2[L+1]-=a[i];
                ans2[L+R]+=a[i];
            }
        }
    }
    int x;
    for(int i=1; i<=n; i++)ans1[i]+=ans1[i-1],ans2[i]+=ans2[i-1];
    for(int i=1; i<=n; i++)ans1[i]+=ans2[i]*i;//补回 k*a[i]
    while(m--)
    {
        x=rd();
        printf("%lld\n",ans1[x]);
    }
}

 



01-06 10:05