2019牛客多校第一场

A Equivalent Prefixes

题意

给两个序列a和b,找出最大一个位置p,使得两个序列1-p的子序列中,任意区间的最小值位置相同。

分析

最小值的位置考虑用单调栈预处理出每个数作为最小值的最左和最右的位置,然后从1开始枚举,对于某个位置i,如果\(a_i\)\(b_i\)作为最小值覆盖区间的左端点不同,那么肯定不行,直接break,因为如果选择这个作为p,显然存在一个区间最小值位置不同。

如果左端点相同,考虑右端点,显然我们的p最大可以取到两个右端点的小值,因此我们的i也最多就枚举到p,而且p是不断往小的更新。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
int n,a[N],b[N];
stack<int> ss;
int ale[N],ari[N],ble[N],bri[N];
int main(void){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        for(int i=1;i<=n;i++){
            scanf("%d",&b[i]);
        }
        while(!ss.empty()){
            ss.pop();
        }
        for(int i=1;i<=n;i++){
            while(ss.size()>0 && a[i]<=a[ss.top()]){
                ss.pop();
            }
            if(ss.size()>0){
                ale[i]=ss.top()+1;
            }else{
                ale[i]=1;
            }
            ss.push(i);
        }
        while(!ss.empty()){
            ss.pop();
        }
        for(int i=n;i>=1;i--){
            while(ss.size()>0 && a[i]<=a[ss.top()]){
                ss.pop();
            }
            if(ss.size()>0){
                ari[i]=ss.top()-1;
            }else{
                ari[i]=n;
            }
            ss.push(i);
        }
        while(!ss.empty()){
            ss.pop();
        }
        for(int i=1;i<=n;i++){
            while(ss.size()>0 && b[i]<=b[ss.top()]){
                ss.pop();
            }
            if(ss.size()>0){
                ble[i]=ss.top()+1;
            }else{
                ble[i]=1;
            }
            ss.push(i);
        }
        while(!ss.empty()){
            ss.pop();
        }
        for(int i=n;i>=1;i--){
            while(ss.size()>0 && b[i]<=b[ss.top()]){
                ss.pop();
            }
            if(ss.size()>0){
                bri[i]=ss.top()-1;
            }else{
                bri[i]=n;
            }
            ss.push(i);
        }
        int p=0,R=n;
        bool flag=true;
        for(int i=1;i<=R;i++){
            if(ale[i]!=ble[i]){
                flag=false;
                break;
            }else{
                if(ari[i]!=bri[i])
                {
                    p=R=min(ari[i],bri[i]);
                    flag=false;
                }
            }
        }
        //特判所有区间左右端点都相等
        if(flag){
            p=n;
        }
        printf("%d\n",p);
    }
    return 0;
}

B Integration

题意

已知\(\int_{0}^{\infty}\frac{1}{1+x^2}=\frac{\pi}{2}\),现在给一个序列\(a_1,a_1...a_n\),求\(\frac{1}{\pi}\int_0^{\infty}\frac{1}{\prod_{i=1}^n(a_i^2+x^2)}dx\)

分析

由于原式\(\frac{1}{\prod_{i=1}^n(a_i^2+x^2)}\)分母是乘积的形式,考虑通过裂项化为相加的形式,并使用待定系数法,也就是\(\frac{C_1}{(a_1^2+x^2)}+\frac{C_2}{(a_2^2+x^2)}+...+\frac{C_n}{(a_n^2+x^2)}=\frac{1}{\prod_{i=1}^n(a_i^2+x^2)}\),现在为了求出\(C_1\),我们在方程左右两边同乘\(a_1^2+x^2\)并移项,得到$ C_1=\frac{a_12+x2}{\prod_{i=1}n(a_i2+x2)}-(\frac{C_2*a_12+x2}{(a_22+x2)}+...+\frac{C_n*a_12+x2}{(a_n2+x2)})$,那么如果我们取$x2=-a_12$,代入可以得到$C_1=\frac{1}{\prod_{i=1}n(a_i2-a_12)(i!=1)}$,同理我们可以O(n^2)算出这所有系数。

求出系数之后,原式即为\(\frac{1}{\pi}\int_0^{\infty}\frac{1}{\prod_{i=1}^n(a_i^2+x^2)}dx=\frac{1}{\pi}\int_0^{\infty}\sum \frac{c_i}{a_i^2+x^2}dx=\frac{1}{\pi}\sum\frac{1}{a_i^2}\int_0^{\infty}\frac{c_i}{1+(\frac{x}{a_i})^2}dx\),换元,由题意可得到结果为\(\frac{1}{\pi}\sum\frac{c_i}{2a_i}\pi=\sum\frac{c_i}{2a_i}\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+50;
typedef long long ll;
const ll mod=1e9+7;
int n;
ll a[N],c[N];
ll Pow(ll a,ll n){
    ll ans=1ll;
    while(n){
        if(n%2){
            ans=ans*a%mod;
        }
        a=a*a%mod;
        n/=2;
    }
    return ans;
}
ll inv(ll x){
    return Pow(x,mod-2);
}
int main(void){
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        for(int i=1;i<=n;i++){
            c[i]=1ll;
            for(int j=1;j<=n;j++){
                if(i==j){
                    continue;
                }
                c[i]=(c[i]*(a[j]*a[j]%mod-a[i]*a[i]%mod+mod))%mod;
            }
            c[i]=inv(c[i]);
        }
        ll ans=0;
        for(int i=1;i<=n;i++){
            ans=(ans+inv(2)*inv(a[i])%mod*c[i])%mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

E ABBA

题意

给定n个AB和m个BA,问这n+m的子序列能组成多少种方案的字符串。

分析

定义状态\(dp[i][j]\)表示i个A和j个B组成的前缀方案数,显然如果转移的方案都合法,那么是\(dp[i][j]=dp[i-1][j]+dp[i][j-1]\),但是并不是每次转移都是合法的。

例如对A来说,但\(i<=n\)时,再放一个A显然是合法的,后面肯定可以放B,当\(i>n\)时,这时候再放A肯定是前面有B才行了,也就是\(j>(i-n)\)

放B也是同理。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e3+50;
const ll mod=1e9+7;
int n,m;
//dp[i][j]表示前i+j个字符中放了i个A和j个B的方案数
ll dp[N][N];
int main(void){
    // freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        for(int i=0;i<=n+m;i++){
            for(int j=0;j<=n+m;j++){
                dp[i][j]=0;
            }
        }
        //初始化,只含A或者只含B只有一种方案
        for(int i=0;i<=n;i++){
            dp[i][0]=1;
        }
        for(int i=0;i<=m;i++){
            dp[0][i]=1;
        }
        //出现一个A就看成是AB的A,出现一个B就看成是BA的B
        //因为假设这个A是BA的A,那么只要这个A不超过总个数,就一定能在后面找到一个A来代替
        //因此状态转移的时候,i和j也就是A和B的个数只要保证不超过总个数即可
        for(int i=1;i<=n+m;i++){
            for(int j=1;j<=n+m;j++){
                //在前面合法的方案状态中加入一个A
                //i-j的数量就是至少的AB数量(假设前面都是BABA..),要<=n
                if(i<=n || min(j,m)>=(i-n)){
                    dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
                }
                //在前面合法的方案状态中加入一个B
                if(j<=m || min(i,n)>=(j-m)){
                    dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
                }
            }
        }
        printf("%lld\n",dp[n+m][n+m]%mod);
    }
    return 0;
}

F Random Point in Triangle

题意

给一个三角形,在三角形内任意取一个点,分成三个三角形,问最大值的期望。

分析

显然只会跟三角形面积有关,因为两个不同形状的三角形肯定能找到一个对应的点使得两者划分出的三角形面积相同,所以随机撒点(注意用面积大一点的三角形)得到答案是22倍三角形面积。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//次数
int n=100000;
double randf(){
    return (double)(rand()/(double)RAND_MAX);
}
ll area(ll x1,ll y1,ll x2,ll y2,ll x3,ll y3){
    return abs(((x1*y2-x2*y1)+(x2*y3-x3*y2)+(x3*y1-x1*y3))*11ll);
}
ll ax1,ay1,ax2,ay2,ax3,ay3;
int main(void){
    while(~scanf("%lld%lld%lld%lld%lld%lld",&ax1,&ay1,&ax2,&ay2,&ax3,&ay3)){
        ll ans=area(ax1,ay1,ax2,ay2,ax3,ay3);
        printf("%lld\n",ans);
    }
    return 0;
}

H XOR

题意

给n个数,求所有异或为0的子集大小之和。

分析

异或为0的子集想到线性基,求大小之和一般反过来考虑每个数的贡献,也就是这个数在多少个子集中可以异或得到0。

先将\(n\)个数放入线性基得到\(r\)个基底,对于不在线性基中的\(n-r\)个数,他们的任意组合都可以用线性基中的一个基底组合来线性表示,因此对于这\(n - r\)不在线性基中的数,贡献为\((n - r)*(2^{n-r-1})\),即固定一个数,其他任选。

根据线性基的性质,如果有两个不同的线性基,那么他们的大小是相同的,都是\(r\),因此,对于在线性基中的\(r\)个数,我们依次枚举,然后将剩下的\(n-1\)个数加入到另一个线性基中,这时候枚举的这个数\(x\)就不在线性基内了,相当于第一种情况,我们只要判断\(x\)是否能由线性基中的基底表示,若能,则贡献为\(2^{n-r-1}\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+50;
const ll mod=1e9+7;
struct LB{
    ll a[65],p[65];
    int cnt,num;
    void init(){
        memset(a,0,sizeof(a));
        memset(p,0,sizeof(p));
        cnt=0;
        num=0;
    }
    //用!ins判断是否能用基底表示
    bool ins(ll x){
        for(int i=63;i>=0;i--){
            if((x>>i)&1){
                //之前这一位没出现过1
                if(!a[i]){
                    a[i]=x;
                    num++;
                    break;
                }
                //之前出现过,把x异或掉
                x^=a[i];
            }
        }
        //全部位被异或掉,则插入失败
        return x>0;
    }
    bool exist(ll x){
        //同插入
        for(int i=60;i>=0;i--){
            if((x>>i)&1){
                x^=a[i];
                if(!x){
                    return true;
                }
            }
        }
        return false;
    }
}A,B,C;
int n;
ll a[N];
bool vis[N];
vector<ll> Ab;
ll Pow(ll a,ll n){
    ll ans=1ll;
    while(n){
        if(n%2){
            ans=ans*a%mod;
        }
        a=a*a%mod;
        n/=2;
    }
    return ans;
}
int main(void){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        Ab.clear();
        for(int i=1;i<=n;i++){
            vis[i]=false;
        }
        //先求第一个线性基A
        A.init();
        B.init();
        for(int i=1;i<=n;i++){
            vis[i]=A.ins(a[i]);
            if(vis[i]){
                Ab.push_back(a[i]);
            }else{
                B.ins(a[i]);
            }
        }
        int r=Ab.size();
        if(r==n){
            printf("0\n");
            continue;
        }
        //计算不在线性基外n-r个数的贡献 (n-r)*2^(n-r-1)
        ll ans=1ll*(n-r)*Pow(2ll,n-r-1)%mod;
        //枚举A线性基中的r个数,将其他n-1个数加入另一个线性基中
        //由线性基性质,如果枚举的x能由其他n-1个数的线性基表示,那么这个线性基的大小也是r
        //所以x的贡献也是2^(n-r-1)
        //先处理出n-r个数的线性基,每次枚举再加入r-1个原线性基中的数
        C.init();
        for(int i=0;i<r;i++){
            C=B;
            for(int j=0;j<r;j++){
                if(i==j){
                    continue;
                }
                C.ins(Ab[j]);
            }
            if(C.exist(Ab[i])){
                ans=(ans+(Pow(2ll,n-r-1)%mod))%mod;
            }
        }
        printf("%lld\n",ans%mod);
    }
    return 0;
}

I Points Division

题意

二维平面上有n个点,每个点有一个a和b值,将n个点分为AB两部分,使得不存在一个A的点在B的点的右下方,求\(max(\sum_{i\sub A}ai+\sum_{i\sub B}bi)\)

分析

  • 由于A的点不可能在B的右下方,所以划分AB两部分的一定是从左下到右上的一条折线。
  • 考虑dp,定义状态dp[i]表示枚举到当前点的x坐标处,y坐标为i的点在折线上的最大权值和(暂时状态)。或者表示当前枚举到点i,且点i在折线上的最大权值和(暂时状态)
  • 具体的状态转移,考虑x从小到大,y从大到小转移,对于枚举到的一个点,分两部更新dp状态,第一步是更新自身的dp状态,第二步是更新前面枚举过的点的dp状态。
  • 首先查询前面枚举过的点中y坐标在\((1,yi)\)范围内的dp状态最大值,why?因为dp状态其实就是对应点在折线上(B部分)的权值和,假设查询大于\(y_i\)的dp状态,也就是这个折线通过这个点是在当前点的左上边的,这条折线就是下降了,不满足条件,因此不行。
  • 查询到最大值tmp之后,就相当于把前面这个最大值的折线连到当前点上来,所以当前点的dp状态就要单点更新\(tmp+b_i\)
  • 当折线经过当前点时,也会对前面的其他dp状态造成影响,具体表现为,对前面\(y_j<y_i\)的点,当前点的贡献是\(a_i\),对前面\(y_j>y_i\)的点,贡献是\(b_i\)
  • 这里的贡献应该是站在前面某个点的角度来看,dp[j]满足\(y_j<y_i\),因此多了i这个点,显然就是多了一个A部分的点,所以dp[j]的值会多了\(a_i\)
  • 相同x坐标,y为什么要从大到小枚举?假设有三个同x坐标的点,我们从小到大,也就是从下到上枚举,分别为点1,2,3,枚举到点1没有任何特别的情况,当前dp状态就表示点1在折线上的情况,权值和为\(b_1\),枚举到点2,dp状态表示点2在折线上,权值和为\(b_1+b_2\),同时更新点1的dp状态,表示当点1在折线上时,点2带来的是\(a_2\)贡献,所以此时点1的dp状态变为\(b_1+a_2\),此时仍然没有问题,出现问题的是点3,点3在进行第一步查询最大值的时候有可能查询到点1的dp状态,如果直接加上,即点3的dp状态变为\(b_1+a_2+b_3\),显然是不对的,也就是查询\(y_i\)下面的最大值,应该保证所有dp状态都只含有\(b\)贡献,这样当点3放在折线上时,下面的最大值仍然可以直接累加上去。
  • 在第一个点之前我们还要再加一个y最小的虚拟点,为什么?假设没有这个点,那么我们枚举第一个点的时候,只能更新当前dp状态,也就是该点在折线上(B部分)的贡献,而无法更新前面的状态,也就是该点在折线上方(A部分)的贡献,所以会影响到后面其他dp状态。

代码

#include <bits/stdc++.h>
using namespace std;
#define ls i<<1
#define rs i<<1|1
#define mid (l+r)/2
typedef long long ll;
const int N=1e5+50;
int n,m;
vector<int> yy;
struct node{
    int x,y;
    ll a,b;
    bool operator <(const node& rhs)const{
        //x从小到大 y从大到小 具体原因见博客
        if(x==rhs.x){
            return y>rhs.y;
        }
        return x<rhs.x;
    }
}p[N];
ll mx[N*4],lz[N*4];
void pushup(int i){
    mx[i]=max(mx[ls],mx[rs]);
}
void pushdown(int i){
    if(lz[i]){
        lz[ls]+=lz[i];
        lz[rs]+=lz[i];
        mx[ls]+=lz[i];
        mx[rs]+=lz[i];
        lz[i]=0;
    }
}
void build(int i,int l,int r){
    lz[i]=0;
    if(l==r){
        mx[i]=0;
        return;
    }
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(i);
}
void update(int i,int l,int r,int p,ll v){
    if(l==r && p==l){
        mx[i]=max(mx[i],v);
        return;
    }
    //因为存在区间更新,单点更新也要pushdown
    pushdown(i);
    if(p<=mid){
        update(ls,l,mid,p,v);
    }else{
        update(rs,mid+1,r,p,v);
    }
    pushup(i);
}
void update(int i,int l,int r,int ql,int qr,ll v){
    if(ql<=l && qr>=r){
        lz[i]+=v;
        mx[i]+=v;
        return;
    }
    pushdown(i);
    if(ql<=mid){
        update(ls,l,mid,ql,qr,v);
    }
    if(qr>mid){
        update(rs,mid+1,r,ql,qr,v);
    }
    pushup(i);
}
ll query(int i,int l,int r,int ql,int qr){
    if(ql<=l && qr>=r){
        return mx[i];
    }
    ll ans=0;
    pushdown(i);
    if(ql<=mid){
        ans=max(ans,query(ls,l,mid,ql,qr));
    }
    if(qr>mid){
        ans=max(ans,query(rs,mid+1,r,ql,qr));
    }
    return ans;
}
int main(void){
    // freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        yy.clear();
        for(int i=1;i<=n;i++){
            scanf("%d%d%lld%lld",&p[i].x,&p[i].y,&p[i].a,&p[i].b);
            yy.push_back(p[i].y);
        }
        //y离散化
        sort(yy.begin(),yy.end());
        yy.erase(unique(yy.begin(),yy.end()),yy.end());
        m=yy.size();
        for(int i=1;i<=n;i++){
            p[i].y=lower_bound(yy.begin(),yy.end(),p[i].y)-yy.begin()+2;
        }
        //增加一个虚拟点,计算第一个点的a贡献
        m++;
        sort(p+1,p+1+n);
        build(1,1,m);
        for(int i=1;i<=n;i++){
            ll tmp=query(1,1,m,1,p[i].y);
            update(1,1,m,p[i].y,tmp+p[i].b);
            update(1,1,m,1,p[i].y-1,p[i].a);
            if(p[i].y+1<=m){
                update(1,1,m,p[i].y+1,m,p[i].b);
            }
        }
        printf("%lld\n",mx[1]);
    }
    return 0;
}

J Fraction Comparision

题意

判断两个分数大小,分子范围1e18

分析

Java或者python大数或者__int128可以直接水过。

不用大数的话可以先比较整数部分,整数部分相同再取模化成真分数,然后交叉乘比较。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll x,y,a,b;
int main(void){
    while(~scanf("%lld%lld%lld%lld",&x,&a,&y,&b)){
        ll xa=x/a;
        ll yb=y/b;
        if(xa>yb){
            printf(">\n");
        }else if(xa<yb){
            printf("<\n");
        }else{
            ll xm=(x%a)*b;
            ll ym=(y%b)*a;
            if(xm>ym){
                printf(">\n");
            }else if(xm<ym){
                printf("<\n");
            }else{
                printf("=\n");
            }
        }
    }
    return 0;
}
posted @ 2019-07-25 08:45  Keane1998  阅读(205)  评论(0编辑  收藏  举报