2019牛客多校第一場

A Equivalent Prefixes

題意

給兩個序列a和b,找出最大一個位置p,使得兩個序列1-p的子序列中,任意區間的最小值位置相同。node

分析

最小值的位置考慮用單調棧預處理出每一個數做爲最小值的最左和最右的位置,而後從1開始枚舉,對於某個位置i,若是\(a_i\)\(b_i\)做爲最小值覆蓋區間的左端點不一樣,那麼確定不行,直接break,由於若是選擇這個做爲p,顯然存在一個區間最小值位置不一樣。python

若是左端點相同,考慮右端點,顯然咱們的p最大能夠取到兩個右端點的小值,所以咱們的i也最多就枚舉到p,並且p是不斷往小的更新。c++

代碼

#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\)dom

分析

因爲原式\(\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_1^2+x^2}{\prod_{i=1}^n(a_i^2+x^2)}-(\frac{C_2*a_1^2+x^2}{(a_2^2+x^2)}+...+\frac{C_n*a_1^2+x^2}{(a_n^2+x^2)})\(,那麼若是咱們取\)x^2=-a_1^2\(,代入能夠獲得\)C_1=\frac{1}{\prod_{i=1}^n(a_i^2-a_1^2)(i!=1)}$,同理咱們能夠O(n^2)算出這全部係數。ui

求出係數以後,原式即爲\(\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}\)spa

代碼

#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的子序列能組成多少種方案的字符串。code

分析

定義狀態\(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也是同理。it

代碼

#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;
}
相關文章
相關標籤/搜索