牛客多校2022第四场A,H,K,N

D队友补了不太想写了。之后可能会把C(ntt)和L(计算几何)补了

A题
背包,贪心,数学
题意:给定n个物品,每个物品有一个w值和p值,要你从中选出m个并且对这个m个物品排序使得如下公式的值最大。
请添加图片描述
对于这个公式,我们显然能看出m物品的先后顺序是对这个公式的答案有影响的。

我们考虑从中,前,后,三个方向插入第m个物品看对原m-1个物品的价值影响。
发现中,后方插入后公式推导困难,而前方插入公式推导简单
前方插入物品为(w,p)那么新的价值就是w+p原价值
显然可以按照插入价值拍个序,
对于a插入的价值大于b插入价值的条件就是:Wa+Pa(Wb+Pb
pre)>Wb+Pb(Wa+Papre)
化简后有
Wa+Pa
Wb>Wb+Pb*Wa

排序完后从前完后背包并且贪心的先选插入价值大的即可。
然而一维01背包是从后往前选择物品的,这样我们就让排序规则为Wa+PaWb<Wb+PbWa

#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace std;
struct node
{
    double p,w;
};
const int N =1e5+100;
int n,m;
node a[N];
double dp[N];
bool cmp(const node &a,const node &b)
{
    return a.w+a.p*b.w<b.w+b.p*a.w;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i].w;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i].p;
        a[i].p/=10000.0;
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
        for(int j=min(i,m);j>=1;j--)
            dp[j]=max(dp[j],a[i].w+a[i].p*dp[j-1]);
    cout<<fixed<<setprecision(12)<<dp[m]<<endl;
    return 0;
}

H
构造,数学
题意:给定一个整数n,则有长度为n砖1块,n-1的2块,一直到长度为1的砖n块,这些砖头宽度均为1
要求你用上所有砖垒一堵矩形的墙,输出这堵墙周长最小的方案数和每一块砖的左上角以及右下角。

思路:首先是周长,我们易知面积是一定的,又知area=n*m。所以可以枚举最相近的n和m值

然后让长度最小的情况下,一层一层的垒砖就可以了

#include <iostream>

using namespace std;
int l,h,num[200+100];
int main()
{
    int t;
    for(cin>>t;t;t--)
    {
        int n,area=0;
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            area+=(n-i+1)*i;
            num[i]=n-i+1;
        }
        for(int i=1;i*i<=area;i++)
        {
            if(area%i==0)
            {
                l=area/i;
                h=i;
            }
        }
        cout<<(l+h)*2<<endl;
        for(int i=0;i<h;i++)
        {
            int temp=0;
            while(temp<l)
            {
                int j=min(n,l-temp);
                while(!num[j])
                {
                    j--;
                }
                cout<<temp<<" "<<i<<" "<<temp+j<<" "<<i+1<<endl;
                temp+=j;
                num[j]--;
            }
        }
    }
    return 0;
}

K
SB
题意:变量a要顺序的遍历1到n,当且仅当a取模n同余于i(i代表1-n的第i个数)时算i被遍历了,并且可以继续遍历下一项。a的初始值为0,遍历前可以进行0-x次操作。求遍历整个数组需要的最小操作次数
操作定义:a=a*10+x(x是从0到9的任意一个数)

思路:首相易知n为1情况下答案是0.
遍历第i项时,a的数值取模于n肯定是i-1,也就是有效部分就是i-1,也就是a就可以当做i-1考虑。

a进行一次操作,能和a取模于n同余的数就多了。
第一次操作
[a10%n,a10%n+10)(这个最大值是取不到的)
第二次操作
[a1010%n,a1010%n+10*10)
以此类推

所以只要让i%n或者i%n+n在这个范围里就好了

#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
int calc(int x,int y)
{
    int cnt=1,len=10;
    x=10*x%n;
    while(1)
    {
        int l=x,r=x+len;
        if((y>=l&&y<r)||(y+n>=l&&y+n<r))
            return cnt;
        ++cnt;
        len*=10;
        x*=10;
        x%=n;
    }
}
int main()
{
    cin>>n;
    if(n==1)
    {
        cout<<0<<endl;
        return 0;
    }
    for(int i=1;i<=n;++i)
    {
        ans+=calc(i-1,i%n);
    }
    cout<<ans<<endl;
    return 0;
}

N
思维题
题意:给定n个数,这n个数可以选择任意两个数然后做and和or运算产生两个值,无限次操作之后数组趋于稳定,问这个稳定数组的方差。

很容易想到二进制状态的位运算,对齐两个数,只有这两个数的二进制位不同时才会交换对应二进制位的0和1.相同时没有变化。所以最终的稳定数组应当是在二进制视角下的两极分化。

比如:010 110 001最终会变成111 010 000.
然后就是求方差的过程。
发现根据数据范围最大到1e25.爆longlong当时int128可以接受。
另一种方法是优化公式。

#include <iostream>

using namespace std;
#define ll __int128
const ll N = 1e5+100;
ll a[N];
ll bit[20];
ll b[N];
__int128 read()
{
    __int128 f=1,w=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
        f=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        w=w*10+ch-'0';
        ch=getchar();
    }
    return f*w;
}
void print(__int128 x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)print(x/10);
    putchar(x%10+'0');
}
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
int main()
{
    ll n;
    n=read();
    for(ll i=1;i<=n;i++)
    {
        a[i]=read();
        for(ll j=0;j<=15;j++)
        {
            ll temp=(a[i]>>j);
            if(temp&1)
            {
                bit[j]++;
            }
        }
    }
    for(ll i=1;i<=n;i++)
    {
        for(ll j=15;j>=0;j--)
        {
            if(bit[j])
            {
                bit[j]--;
                b[i]+=1<<j;
            }
        }
    }
    ll sum=0;
    ll sum1=0;
    for(ll i=1;i<=n;i++)
    {
        sum+=b[i];
    }
    for(ll i=1;i<=n;i++)
    {
        sum1+=(n*b[i]-sum)*(n*b[i]-sum);
    }
    ll mm=n*n*n;
    print(sum1/gcd(sum1,mm));
    printf("/");
    print(mm/gcd(sum1,mm));
    return 0;
}

再做两个SA的题今天就下班了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值