2019牛客暑期多校训练营(第九场)

B Quadratic equation

题目链接
题意:模数p为,输入b和c,保证b、c都小于模数,求x和y满足以下式子

思路:对于这两个式子,我们可以转化成
对于这个式子,我们可以通过二次剩余得到x-y的结果,再通过化简即可

#include <bits/stdc++.h>

typedef long long ll;
using namespace std;
const ll p=1000000007;
ll b,c;

ll w;
struct node{
    ll x,y;
    node mul(node a,node b,ll m){
        node ans;
        ans.x=(a.x*b.x%m+a.y*b.y%m*w%m)%m;
        ans.y=(a.x*b.y%m+a.y*b.x%m)%m;
        return ans;
    }

    node qpow(node a,ll b,ll m){
        node ans;
        ans.x=1;ans.y=0;
        while(b){
            if(b&1) ans=mul(ans,a,m);
            a=mul(a,a,m);
            b>>=1;
        }
        return ans;
    }
};

ll qpow(ll a,ll b,ll m){
    ll ans=1;
    ll k=a;
    while(b){
        if(b&1)ans=ans*k%m;
        k=k*k%m;
        b>>=1;
    }
    return ans;
}

ll Cipolla(ll n,ll p){
    n%=p;
    if(!n)return 0;
    if(p==2)return n;
    if(qpow(n,(p-1)/2,p)==p-1)
        return -1;

    ll a,t;
    while(1){
        a=rand()%p;
        t=a*a-n;
        w=(t%p+p)%p;
        if(qpow(w,(p-1)/2,p)==p-1)break;
    }

    node ans;
    ans.x=a;ans.y=1;
    ans=ans.qpow(ans,(p+1)/2,p);
    return ans.x;
}

void input(){
    scanf("%lld%lld",&b,&c);
}

void solve(){
    ll ans=Cipolla(b*b-4*c+p,p);
    if(ans==-1)
        printf("-1 -1\n");
    else{
        ll x=(b+ans)%p*qpow(2,p-2,p);
        x%=p;
        ll y=(b-x+p)%p;
        if(x>y)swap(x,y);
        printf("%lld %lld\n",x,y);
    }
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        input();
        solve();
    }
}

D Knapsack Cryptosystem

题目链接
题意:对于给定n(小于等于36),要求在其中选择任意个数,其总和等于s。
思路:折半枚举,我们可以枚举一半个数物品的选择状态存入map。然后在后半段进行查询。

#include <bits/stdc++.h>

#define maxn 40
typedef long long ll;
using namespace std;

int n;
ll tot;
ll a[maxn];
void input(){
    int i;
    scanf("%d%lld",&n,&tot);
    for(i=0;i<n;i++)
        scanf("%lld",&a[i]);
}

void solve(){
    map<ll,string>m1;
    map<ll,string>m2;

    int i,j;    
    for(i=0;i<(1<<(n/2));i++){
        string vis;
        ll sum=0;

        for(j=0;j<n/2;j++){
            if((i>>j)&1){
                vis.push_back('1');
                sum+=a[j];
            }else
                vis.push_back('0');
        }

        m1[sum]=vis;
    }

    for(i=0;i<(1<<(n-n/2));i++){
        string vis;
        ll sum=0;

        for(j=0;j<n-n/2;j++){
            if((i>>j)&1){
                vis.push_back('1');
                sum+=a[j+n/2];
            }else
                vis.push_back('0');
        }

        m2[sum]=vis;
        if(m1.count(tot-sum)){
            cout<<m1[tot-sum]<<m2[sum]<<endl;
            break;
        }
    }


}

int main(){
    input();
    solve();
}

E All men are brothers

题目链接
题意:有n个人,有m轮会找到两个人成为朋友,朋友关系是相互且可传递的。在每轮前后都会进行询问,即在这n个人中寻找4个人,4个人两两不能存在友谊,询问有多少种选择方法。
思路:首先对于同一集合里的人数,可以通过并查集O(1)维护。
假设存在A、B、C、D、E五个集合,其中字母代表这个集合中的人数。

不合并之前的选择方法数
我们选择A和B进行合并,合并后的选择方法数
可发现缺少的为,提取公因子得到
即合并的两个集合人数相乘,再乘上其余集合人数两两相乘之和。
对于

通过以上公式,可维护O(1)得到结果

#include <bits/stdc++.h>

#define maxn 100005
typedef unsigned long long ull;
using namespace std;

int n,m;

struct Union{
    int fa[maxn];
    ull sz[maxn];
    void init(int n){
        for(int i=1;i<=n;i++)
        {
            fa[i]=i;
            sz[i]=1;
        }
    }

    int find(int x){
        return x==fa[x]?x:fa[x]=find(fa[x]);
    }

    void mix(int x,int y){
        int fx=find(x);
        int fy=find(y);
        fa[fx]=fy;
        sz[fy]+=sz[fx];
    }
}un;

void solve(){
    scanf("%d%d",&n,&m);
    un.init(n);

    ull ans=0;
    if(n>=4)
        ans=1llu*n*(n-1)/2*(n-2)/3*(n-3)/4;
    printf("%llu\n",ans);

    ull sum=n;
    int x,y;
    for(int i=0;i<m;i++){
        scanf("%d%d",&x,&y);
        if(un.find(x)!=un.find(y))
        {
            int fx=un.find(x);
            int fy=un.find(y);

            ull los=(n-un.sz[fx]-un.sz[fy])*(n-un.sz[fx]-un.sz[fy]);
            los=los-(sum-un.sz[fx]*un.sz[fx]-un.sz[fy]*un.sz[fy]);
            los=los/2;
            los=los*un.sz[fx]*un.sz[fy];
            sum=sum-un.sz[fx]*un.sz[fx]-un.sz[fy]*un.sz[fy]+(un.sz[fx]+un.sz[fy])*(un.sz[fx]+un.sz[fy]); 

            ans=ans-los;
            un.mix(x,y);
        }

        printf("%llu\n",ans);
    }
}

int main(){
    solve();
}

H Cutting Bamboos

题目链接
题意:有n个竹子,每个竹子的高度是hi,每次查询l,r,x,y代表从区间中寻找到高度H,使得H以上的高度总和占这个区间高度和的
思路:用主席树维护区间内高度H以下的竹子的高度和以及个数和,然后二分H判断。

#include <bits/stdc++.h>

#define MAXN 200005
#define eps 1e-10
typedef long long ll;
using namespace std;

int n,q;
int cnt;
int h[MAXN];
int T[MAXN];
int lson[MAXN*30],rson[MAXN*30];
ll c[MAXN*30],nu[MAXN*30];
ll sum[MAXN];

ll SUM,NUM;

int Build(int l,int r){//建立空树 
  int root=cnt++;
  c[root]=0;
  nu[root]=0;
  if(l!=r){
    int mid=(l+r)>>1;
    lson[root]=Build(l,mid);
    rson[root]=Build(mid+1,r);
  }
  return root;
}

int update(int root,int pos,int val){//根据插入值,logn的建立树链,即另一个版本主席树 
  int newroot=cnt++;
  int ans=newroot;
  c[newroot]=c[root]+val;
  nu[newroot]=nu[root]+1;
  int l=1,r=100000;
  while(l<r){
    int mid=(l+r)>>1;
    if(pos<=mid){
      lson[newroot]=cnt++;
      rson[newroot]=rson[root];
      newroot=lson[newroot];
      root=lson[root];
      r=mid;
    }else{
      rson[newroot]=cnt++;
      lson[newroot]=lson[root];
      newroot=rson[newroot];
      root=rson[root];
      l=mid+1;
    }
    c[newroot]=c[root]+val;
    nu[newroot]=nu[root]+1;
  }
  return ans;
}

void query(int left_root,int right_root,int l,int r,int k)//左端点版本号的树,右端点版本号的树 
{
    if(l>k) return;
    if(r<=k){
        SUM+=c[left_root]-c[right_root];
        NUM+=nu[left_root]-nu[right_root];
        return;
    }

    int mid=(l+r)>>1;
    query(lson[left_root],lson[right_root],l,mid,k);
    query(rson[left_root],rson[right_root],mid+1,r,k);
}

void solve(){
    scanf("%d%d",&n,&q);
    T[n+1]=Build(1,n);
    int i;
    for(i=1;i<=n;i++){
        scanf("%d",&h[i]);
        sum[i]=sum[i-1]+h[i];
    }

    for(i=n;i>=1;i--)
        T[i]=update(T[i+1],h[i],h[i]);

    int l,r,x,y;
    while(q--){
        scanf("%d%d%d%d",&l,&r,&x,&y);
        double li=0,ri=100000;
        while(abs(li-ri)>eps){
            double mid=(li+ri)/2;
            SUM=NUM=0;
            query(T[l],T[r+1],1,100000,(int)mid);
            NUM=r-l+1-NUM;

            double tot=mid*NUM+SUM;
            double ans=(y-x)*1.0*(sum[r]-sum[l-1])/y;
            if(ans<tot)
                ri=mid;
            else
                li=mid;
        }
        printf("%.15lf\n",li);
    }
}

int main(){
    solve();
}

J Symmetrical Painting

题目链接
题意:存在n个矩形,左上角为(i-1,l),右下角为(i,r),寻找一条平行x轴的线,将穿过矩形的对称面积相加,求最大值。
思路:水平线在L,(L+R)/2,R三种情况下才有可能得到最优情况。O(n)维护当前线穿过的矩阵个数和面积。也可以将矩阵看作区间,在区间维护斜率,解决分段函数

#include <bits/stdc++.h>

#define maxn 300005
typedef long long ll;
using namespace std;

int n;
ll l,r;
ll a[maxn*3];

void input(){
    scanf("%d",&n);
    int i;
    for(i=0;i<n;i++){
        scanf("%lld%lld",&l,&r);
        a[i*3]=4*l;
        a[i*3+1]=4*r;
        a[i*3+2]=2*l+2*r+1;
    }
}

void solve(){
    sort(a,a+3*n);
    int i;
    ll ans=0,s=0;
    ll last=0,num=0;
    for(i=0;i<3*n;i++){
        s+=(a[i]/2-last)*num;
        last=a[i]/2;

        if(s>ans)
            ans=s;

        if(a[i]%2==1)
            num-=2;
        else
            num++;

//        cout<<last<<' '<<s<<' '<<num<<' '<<ans<<endl;
    }

    printf("%lld\n",ans);
}

int main(){
    input();
    solve();
}