2019牛客多校第九场题解(D/E/J)

D

给你最多36个数和 一个定值s,找一个子集使这些数的和为s

做法

将集合分成两个部分,先对右边部分用二进制数枚举选取情况,然后求和之后用map存下来,键值为值,值为对应的二进制数

然后对左边部分用二进制数枚举选举情况,求和后用s-sum,看map中是否存在这个键值,如果存在则找到了答案,则将相应的二进制数分左右部分分别输出出来。

复杂度O(2^{ \frac{n}{2} }+ \frac{n}{2}*2^{\frac{n}{2}})  ( map查询有一个log(n)的复杂度

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
map<ull,int>mp;
ull a[40];
int ans[100005];
int main()
{
    int n;
    ull s;
    scanf("%d%llu",&n,&s);
    for(int i=0;i<n;i++)
    {
        scanf("%llu",&a[i]);
    }
    int m=n-(n/2);
    for(int i=0;i<(1<<m);i++)
    {
        ull sum=0;
        for(int j=0;j<m;j++)
        {
            if(i&(1<<j))sum+=a[n/2+j];
        }
        mp[sum]=i;
    }
    for(int i=0;i<(1<<(n/2));i++)
    {
        ull sum=0;
        for(int j=0;j<(n/2);j++)
        {
            if(i&(1<<j))sum+=a[j];
        }
        if(mp[s-sum])
        {
            for(int j=0;j<n/2;j++)
            {
                if(i&(1<<j))ans[j]=1;
                else ans[j]=0;
            }
            for(int j=0;j<m;j++)
            {
                if(mp[s-sum]&(1<<j))ans[n/2+j]=1;
                else ans[n/2+j]=0;
            }
            for(int i=0;i<n;i++)
            {
                printf("%d",ans[i]);
            }
            printf("\n");
        }
    }
    return 0;
}

E

给出n个不同的数,m次操作,每次操作在两个数之间建立一个联系,问没操作之前和每次操作后,从所有数中选出四个毫无关系的数的方案数。

首先初始化ans=(__int128)n*(n-1)*(n-2)*(n-3)/24;   乘的过程中会超过long long 所以用__int128

然后每次合并两个数的集合,用并查集维护每个集合,每次合并操作使答案减少的方案数为 分别从两个合并的集合中选出一个数的方案数相乘然后乘以除了这两个集合以外的数中选出两个不属于同一集合的方案数。

而从所有数中选出两个不属于同一集合的方案数等于从所有数中任意选出两个的方案数减去属于同一集合的两个数的方案数。

每次更新答案的时候。由于我们统计的是所有数中选出两个属于同一集合的方案数。所以也会包含在我们正在合并的集合中选取两个的方案数 ,而正在合并的集合很明显是不在统计范围内的。所以-sum后要加上合并的集合中选取两个答案的方案数。

ans-=num[x]*num[y]*( (n-num[x]-num[y])*(n-num[x]-num[y]-1)/2-sum+(num[x]-1)*num[x]/2+(num[y]-1)*num[y]/2);

每次更新sum的时候,由于sum 是合并之前选取同一集合中两个数的方案数。所以合并之后我们sum先减去分别在两个集合中选取两个数的方案数,然后加上在合并完的集合中选取两个数的方案数。

sum-=(num[x]-1)*num[x]/2;
sum-=(num[y]-1)*num[y]/2;

sum+=(num[y]-1)*num[y]/2;

然后合并集合,合并集合大小,输出ans。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100000+10;
int n,m;
int fa[maxn];
ll num[maxn];
int get(int x)
{
    if(fa[x]==x)return x;
    else return fa[x]=get(fa[x]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){fa[i]=i;num[i]=1;}
    ll ans=( __int128 )(n)*(n-1)*(n-2)*(n-3)/24;
    ll sum=0;
    int x,y;
    printf("%lld\n",ans);
    while(m--)
    {
        scanf("%d%d",&x,&y);
        x=get(x);
        y=get(y);
        if(x!=y)
        {
            ans-=num[x]*num[y]*( (n-num[x]-num[y])*(n-num[x]-num[y]-1)/2-sum+(num[x]-1)*num[x]/2+(num[y]-1)*num[y]/2);
            sum-=(num[x]-1)*num[x]/2;
            sum-=(num[y]-1)*num[y]/2;
            fa[x]=y;
            num[y]+=num[x];
            sum+=(num[y]-1)*num[y]/2;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

J

给你n个宽为1 高度为r-l 的矩形,现在让你删掉一些矩形的面积,使剩下的矩形有一条横向的对称轴,并求出所有矩形的最大面积和。

解法 两个点。

1.最大的矩形面积一定是当对称轴在某个矩形的边界或者中心线的时候取得。

2.对单个矩形来看从下往上来看 我们可以看做从l~(l+r)/2  斜率为2的单调递增函数。 从(l+r)/2~r 斜率为-2的单调递减函数。所以

我们可以在 l处为2,在(l+r)/2 处为-4,在r处为2,相当于

l                            (l+r)/2                          r      

+2+2+2+2+2+2+2          -2-2-2-2-2-2-2-2   
那么相当于区间修改 我们分别在l,r ,(l+r)/2 三个点处进行修改。然后求前缀和就可以得到区间修改。

我们每次到达一个需要累积答案的点。用之前的前缀和*(nod[i]-nod[i-1])得到当前的答案。然后累积答案取max。

最后答案除2输出。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;
struct node
{
    int x,y;
    friend bool operator <(const struct node & a, const struct node & b)
    {
        return a.x<b.x;
    }
}nod[1000000];
int main()
{
     scanf("%d",&n);
     ll l,r;
     int cont=0;
     for(int i=1;i<=n;i++)
     {
         scanf("%d%d",&l,&r);
         l<<=1;
         r<<=1;
         nod[++cont].x=l;nod[cont].y=2;
         nod[++cont].x=(l+r)>>1;nod[cont].y=-4;
         nod[++cont].x=r;nod[cont].y=2;
     }
     sort(nod+1,nod+1+cont);
     ll sum=nod[1].y;
     ll s=0;
     ll ans=0;
     for(int i=2;i<=cont;i++)
     {
         s+=sum*(nod[i].x-nod[i-1].x);
         ans=max(ans,s);
         sum+=nod[i].y;
     }
     printf("%lld\n",(ans>>1) );
     return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值