2019牛客暑期多校训练营(第七场)E F H I

E Find the median

题意:每次往序列中增加连续的[l,r]的数,每加入一次就询问当前序列的中位数。

解法:此题没有要求在线,那么直接离线+线段树+二分就可以了。求出每个端点之后排序得到数组b,线段树每个叶子结点i存储的是区间[ b[i-1]+1,b[i] ]的系数(即当前序列有多少个[ b[i-1]+1,b[i] ])。修改时顺便维护当前总的数个数sum,然后处理询问就是直接在线段树上二分就可以了。

#include<bits/stdc++.h>
#define lc o*2
#define rc o*2+1
#define LL long long
using namespace std;
const int maxn = 4e5+5;
LL sumv[16*maxn],addv[16*maxn];
int a[maxn],b[maxn];
int l[4*maxn],r[4*maxn];
int ll[16*maxn],rr[16*maxn];
int n;
int X1,X2,A1,B1,C1,M1;
int Y1,Y2,A2,B2,C2,M2;
int tot;
void init() {
    int X3,Y3;
    a[1]=min(X1,Y1)+1,a[2]=min(X2,Y2)+1;
    b[1]=max(X1,Y1)+1,b[2]=max(X2,Y2)+1;
    for(int i=3; i<=n; i++) {
        X3 = ((LL)A1*X2+(LL)B1*X1+C1)%M1;
        Y3 = ((LL)A2*Y2+(LL)B2*Y1+C2)%M2;
        a[i]=min(X3,Y3)+1;
        b[i]=max(X3,Y3)+1;
        X1 = X2;
        X2 = X3;
        Y1 = Y2;
        Y2 = Y3;
    }
}
int c[4*maxn];
int cnt;
void pushup(int o) {
    sumv[o]=sumv[lc]+sumv[rc];
    ll[o]=ll[lc];
    rr[o]=rr[rc];
}
void pushdown(int o) {
    if(addv[o]) {
        sumv[lc]+=(rr[lc]-ll[lc]+1)*addv[o];
        sumv[rc]+=(rr[rc]-ll[rc]+1)*addv[o];
        addv[lc]+=addv[o];
        addv[rc]+=addv[o];
        addv[o]=0;
    }
}
void build(int o,int L,int R){
    if(L==R){
        ll[o]=l[L];
        rr[o]=r[L];
        return;
    }
    int M = (L+R)>>1;
    build(lc,L,M);
    build(rc,M+1,R);
    pushup(o);
}
void update(int o,int L,int R,int ql,int qr,int v) {
    if(ql<=L&&R<=qr) {
        sumv[o]+=(LL)(rr[o]-ll[o]+1)*v;
        addv[o]+=v;
        return;
    }
    pushdown(o);
    int M =(L+R)>>1;
    if(ql<=M)update(lc,L,M,ql,qr,v);
    if(qr>M)update(rc,M+1,R,ql,qr,v);
    pushup(o);
}
int query(int o,int L,int R,LL k){
    if(L==R){
        LL f = sumv[o]/(rr[o]-ll[o]+1);
        LL c = (k-1)/f;
        return (ll[o]+c);
    }
    pushdown(o);
    int M = (L+R)>>1;
    if(k>sumv[lc])return query(rc,M+1,R,k-sumv[lc]);
    else return query(lc,L,M,k);
}
int main() {
    cin>>n;
    cin>>X1>>X2>>A1>>B1>>C1>>M1;
    cin>>Y1>>Y2>>A2>>B2>>C2>>M2;
    init();
//  for(int i=1; i<=n; i++)cin>>a[i]>>b[i];
    for(int i=1; i<=n; i++) {
        c[++cnt]=a[i];
        c[++cnt]=b[i];
    }
    sort(c+1,c+1+cnt);
    cnt = unique(c+1,c+1+cnt)-(c+1);
    for(int i=1; i<=cnt; i++) {
        l[++tot]=c[i];
        r[tot]=c[i];
        if(i!=cnt&&c[i+1]-c[i]>1) {
            l[++tot]=c[i]+1;
            r[tot]=c[i+1]-1;
        }
    }
    build(1,1,tot);
    LL now = 0;
    for(int i=1; i<=n; i++) {
        int L = lower_bound(l+1,l+1+tot,a[i])-l;
        int R = lower_bound(r+1,r+1+tot,b[i])-r;
        update(1,1,tot,L,R,1);
        now += b[i]-a[i]+1;
        printf("%d
",query(1,1,tot,(now+1)/2));
    }
    return 0;
}
View Code

F Energy stones

 题意:有n个石头,每个石头有初始能量ei,每秒增长能量li,能量上限ci。有m次收割操作(t,si,ti)代表第t秒收割下标[si,ti]的石头能量,石头的能量被收割之后会变成0然后重新增长。问收割总能量。

解法:这题解法偏思维+数据结构。首先是收割一段下标的石头能量此只有一次询问,那么容易想到差分打标记的办法,对于一个收割(t,si,ti),我们在石头si打t的标记代表收割开始,在ti+1打-t的标记代表收割结束。然后我们石头从左往右扫,就容易得到每个石头的收割开始与结束标记。再进一步观察其实对于每个石头这些开始与结束标记就组成了一段段时间段,我们我们的问题就是怎么维护这些时间段以及计算这些时间段对答案的贡献。

方法是用Set+BIT的方式,用Set来保存时间点,用Bit来保存时间段长度(注意这句话非常重要)。那么每当扫到i点有插入或者删除标记的话,我们先在Set里查找这个时间点的位置,因为Set保存了所有的时间点,所以找到时间点的位置之后我们就能知道插入/删除这个时间点对时间段长度的影响,那么根据这些影响就更新Bit。那么时间段长度就得到实时更新,根据这些时间段我们就能算出该石头对答案的贡献。

一些具体细节:①在set插入/删除时间点有一番细节的操作,以插入为例,要分三种情况:插到set头,插到set尾,插到set中。这三种情况有不同操作要自己细细想。②Bit其实有两个Bit,bit1[i]记录时间段i的数值和,bit2[i]记录时间段i的个数。③怎么计算石头对答案贡献?分两种三种贡献,第一是初始能量,第二是不会增长到上限ci的贡献(就是时间段长度<=ci/li的时间段)这个贡献用Bit1算,第三是增长到了ci的贡献,这个贡献用Bit2算。

有一些东西比较难说情况,建议看代码理解:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+10;
int n,m,e[N],l[N],c[N];
vector<int> h[N];
set<int> s;

LL bit1[N],bit2[N];  //bit1[i]记录时间段i的数值和,bit2[i]记录时间段i的个数 
void update(int x,int v) {
    if (x==0) return;
    for (;x<N;x+=x&-x) {
        bit1[x]+=v;
        if (v>0) bit2[x]++; else bit2[x]--;
    }
}
LL query1(int x) {
    LL ret=0;
    for (;x;x-=x&-x) ret+=bit1[x];
    return ret;
}
LL query2(int x) {
    LL ret=0;
    for (;x;x-=x&-x) ret+=bit2[x];
    return ret;
}

void addtag(int x) {  //在set添加时间点并更新bit的时间段信息 
    if (s.empty()) { s.insert(x); return; }
    auto t=s.lower_bound(x);
    if (t==s.begin()) {
        int tmp=*(s.begin())-x; update(tmp,tmp);
    }else if (t==s.end()) {
        int tmp=x-*prev(s.end()); update(tmp,tmp); 
    } else {
        int tmp=*t-*prev(t); update(tmp,-tmp);
        tmp=*t-x; update(tmp,tmp);
        tmp=x-*prev(t); update(tmp,tmp);
    }
    s.insert(x);
}

void deltag(int x) {  //在set删除时间点并更新bit的时间段信息 
    auto t=s.lower_bound(x);
    if (s.size()==1) { s.erase(t); return; }
    if (t==s.begin()) {
        int tmp=*next(t)-x; update(tmp,-tmp);
    } else if (next(t)==s.end()) {
        int tmp=x-*prev(t); update(tmp,-tmp);
    } else {
        int tmp=x-*prev(t); update(tmp,-tmp);
        tmp=*next(t)-x; update(tmp,-tmp);
        tmp=*next(t)-*prev(t); update(tmp,tmp);
    }
    s.erase(t);
}

int main()
{
    int T,cas=0; cin>>T;
    while (T--) {
        cin>>n;
        for (int i=1;i<=n;i++) scanf("%d%d%d",&e[i],&l[i],&c[i]);
        cin>>m;
        for (int i=1;i<=n+1;i++) h[i].clear();
        for (int i=1;i<=m;i++) {
            int t,si,ti; scanf("%d%d%d",&t,&si,&ti);
            h[si].push_back(t); h[ti+1].push_back(-t);  //打标记 
        }
        
        LL ans=0;
        for (int i=1;i<=n+1;i++) {
            for (int j=0;j<h[i].size();j++) {
                int t=h[i][j];  //取出标记 
                if (t>0) addtag(t); else deltag(-t);
            }
            if (s.empty()) continue;
            ans+=min((LL)c[i],e[i]+(LL)*(s.begin())*l[i]);  //特殊处理初始能量 
            if (l[i]==0) continue;
            int d=c[i]/l[i];  //最多充能天数 
            ans+=(LL)query1(d)*l[i]+(LL)(query2(N-1)-query2(d))*c[i];  //用BIT计算贡献 
        }
        printf("Case #%d: %lld
",++cas,ans);
    }
    return 0;    
} 
View Code

H Pair

题意:给出A,B,C;问存在多少数字对<x,y> 满足 x€[1,A],y€[1,B],x&y>C||x^y<C 。

解法:这题是真的不会做,比赛的时候一直在想FFT什么的完全想偏了qwq。解法参考https://blog.csdn.net/qq_41117236/article/details/98884028这位大佬的。

首先要想到是一道数位dp的题目,然后设计出正确的dp状态:dp[i][c1][c2][lim1][lim2]代表当前填到第i位(从高位开始填),c1为前i位满足条件一情况,c2为前i位满足条件二情况,且A是否limit,B是否limit。特别要提到的是c1/c2只要3中状态(-1,0,1),-1是代表不满足条件(高位不满足已经凉了低位不用看了),1是已经满足条件(高位满足了低位也无所谓随便填),0是未确定(未确定其实就是还没满足但不是肯定不满足,相当于凉了一般但是还得看后面发挥)。

然后就是数位dp的常规套路,枚举每一位填什么顺便计算方案数。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL dp[32][3][3][2][2],pw[63],n1[32],n2[32];
 num1[32],num2[32],num3[32];
LL dfs(int len,int c1,int c2,bool lim1,bool lim2)
{
    if(c1==-1&&c2==-1) return 0; //两个条件都不满足
    if(len==0) return c1==1||c2==1; //所有位都取到了
    if(c1==1||c2==1){ //任一满足
        LL a=pw[len],b=pw[len]; //当前位的大小即最多可以取的不同数字
        if(lim1) a=n1[len]+1; //若有限制,只能取A取得到的
        if(lim2) b=n2[len]+1;
        return a*b;
    }
    if(dp[len][c1][c2][lim1][lim2]!=-1)
        return dp[len][c1][c2][lim1][lim2];
    int up1=lim1?num1[len]:1,up2=lim2?num2[len]:1; //如果有限制就按限制,没有限制就到1
    LL res=0;
    for(int i=0;i<=up1;i++) //枚举确定x和y当前位的取值是0还是1
        for(int j=0;j<=up2;j++){
            int x=i&j,y=i^j,nc1=c1,nc2=c2;
            if((!c1&&x<num3[len]||c1==-1)&&(!c2&&y>num3[len]||c2==-1))
                continue;
            if(!c1){
                if(x<num3[len]) nc1=-1;
                else if(x>num3[len]) nc1=1;
                else nc1=0;
            }
            if(!c2){
                if(y>num3[len]) nc2=-1;
                else if(y<num3[len]) nc2=1;
                else nc2=0;
            }
            res+=dfs(len-1,nc1,nc2,lim1&&i==up1,lim2&&j==up2); //确定下一位
        }
    dp[len][c1][c2][lim1][lim2]=res;
    return res;
}
LL cal(LL A,LL B,LL C)
{
    memset(num1,0,sizeof(num1));
    memset(num2,0,sizeof(num2));
    memset(num3,0,sizeof(num3));
    //取出ABC二进制形式的每一位
    int t1=0,t2=0,t3=0;
    while(A) num1[++t1]=A&1,A>>=1;
    while(B) num2[++t2]=B&1,B>>=1;
    while(C) num3[++t3]=C&1,C>>=1;
    int t=max(t1,max(t2,t3)); //最高位
    for(int i=t;i>0;i--){
        n1[i]=n2[i]=0;
        for(int j=i;j>0;j--){
            n1[i]=n1[i]<<1|num1[j]; //n1[i]*2+num1[j],表示A取到第i位的大小
            n2[i]=n2[i]<<1|num2[j]; //表示B取到第i位的大小即不同数字个数
        }
    }
    return dfs(t,0,0,1,1);
}
void init()
{
    pw[0]=1;
    for(int i=1;i<31;i++)
        pw[i]=pw[i-1]*2; //二进制的第i位表示的大小
}
int main()
{
    init();
    int t; scanf("%d",&t);
    while(t--){
        memset(dp,-1,sizeof(dp)); //初始化
        LL A,B,C; scanf("%lld%lld%lld",&A,&B,&C);
        printf("%lld
",cal(A,B,C)-min(A,C-1)-min(B,C-1)-1);
        //还需要减去x=0且y!=0,y=0且x!=0,x=0且y=0的部分
    }
    return 0;
}
View Code

I Chessboard

 题意:给定n,m。求k*k的矩阵,1<=k<=n,矩阵内的每个元素都不小于m,且矩阵内不同行不同列的元素相加都为一个定值T,且T<=n。问这样的矩阵有多少种,MOD998244353

解法:比赛的时候想不到,只能看题解了:https://blog.csdn.net/qq_43383246/article/details/99240320

要加强组合数学的推理能力(qwq)。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int p=998244353;
LL fac[500005],inv[500005];
LL fsp(LL a,int b){
    LL res=1;
    while(b){
        if(b&1)res*=a,res%=p;
        a*=a,a%=p;
        b>>=1;
    }
    return res;
}
LL C(int n,int m){
    if(m>n||m<0)return 0;
    return fac[n]*inv[n-m]%p*inv[m]%p;
}
int main(){
    int T,n,m;scanf("%d",&T);
    fac[0]=fac[1]=1LL;
    for(int i=2;i<=10000;i++)fac[i]=fac[i-1]*i%p;
    inv[0]=inv[1]=1LL;inv[10000]=fsp(fac[10000],p-2);
    for(int i=10000;i>=3;i--)inv[i-1]=inv[i]*i%p;
    while(T--){
        scanf("%d%d",&n,&m);
        LL ans=0;
        for(int k=1;k<=n;k++){
            for(int t=0;t<=n-k*m;t++){
                ans+=C(t+2*k-1,2*k-1);
                ans%=p;
                ans-=C(t+k-1,2*k-1);
                ans=(ans+p)%p;
            }
        }
        printf("%lld
",ans);
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/clno1/p/11455720.html