本场总结:

题目类型:
A.概率问题
D.bfs第K小团
E.线性dp,线段树维护矩阵加速
F.dfs剪搜
H.单调栈维护第二大子矩阵
J.区间合并问题

其他题目暂时鸽了


小结:
--概率问题脑子不够用
--k小团问题bit<int.> & 极好用
--线段树还能维护矩阵加速
震惊
--单调栈找第二大子矩阵小细节--注意
--区间合并问题一直是zero
下次还是不会

A. Eddy Walker

大致题意:一个由0--n-1组成的环,你的起点为0,你在环上行走,你会独立地随机选择前进或后退。现给你T个场景,场景先后关联产生。场景给定n,m,n表示行走的是0--n-1的环,m表示你从起点0开始行走,环上每个点都走了一次,最后落脚点为m.
分析:

  • 我们先考虑一个场景,起点为0,终点不定,其实走的顺序组合就是(n-1)个点的排列---(n-1)!.那么场景要求终点是m,那么组合就是(n-2)个点的排列---(n-2)!.那么该场景发生的概率就是 (n-2)! / (n-1)! = 1/(n-1). ( n>1 , m!=0 )
  • 那么如果n>1,m=0时,显然该场景的发生概率为0,因为起始点为0.
  • n=1,m=0时,因为环只有一个点,那么该场景发生概率为1.
    最后有个坑点,就是前后场景关联,发生第n个场景的概率要乘以前缀场景发生概率乘积即为答案。
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mod=1e9+7;
int n,m,t;

ll poww( ll a,ll b )
{
    ll ans=1;
    while( b )
    {
        if( b&1 )  ans=ans*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return ans;
}

int main()
{
    ll ans=1;
    scanf("%d",&t);
    while( t-- )
    {
        scanf("%d%d",&n,&m);
        if( n==1 && m==0 ) ans=ans*1;
        else if( m==0 ) ans=0;
        else ans=ans*poww(n-1,mod-2)%mod;
        printf("%lld\n",ans);
    }
}

D. Kth Minimum Clique

大致题意:给定n个点和每个点的点权,再给定各点的边关系.求一个第k小加权团.
加权团定义:一个点集合的点权总和,集合中各点满足任意两点在图中可达. 注:空集是最小的加权团
分析:显然用优先队列bfs直接找就行,对于当前点集合,一个新的点要加入进去必须满足到集合各点都是可达的,那么我们可以用bitset来维护更新集合,具体实现看代码.

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=1e5+10;

bitset<105> bit1[105];
int n,k;
ll w[105];


struct node{
    ll sum;
    bitset<105> bit;
    int idx;
    bool operator < ( node t ) const{
        return sum>t.sum; 
    }
};

priority_queue<node> que;

int main()
{
    scanf("%d%d",&n,&k);k--;
    for( int i=1;i<=n;i++ ) scanf("%d",&w[i]);
    for( int i=1;i<=n;i++ )
    {
        for( int j=1;j<=n;j++ )
        {
            int x;scanf("%1d",&x);
            bit1[i][j]=x;
        }
    }
    if( !k ) 
    {
        puts("0");
        return 0;
    }
    for( int i=1;i<=n;i++ ) que.push( {w[i],bit1[i],i} );
    while( !que.empty() )
    {
        node p=que.top();que.pop();
        if( k==1 )
        {
            printf("%lld\n",p.sum);
            return 0;
        }
        k--;
        for( int i=p.idx+1;i<=n;i++ )
        {
            if( p.bit[i] )
            {
                que.push( {p.sum+w[i],p.bit&bit1[i],i} );
            }
        }
    }
    puts("-1");
    return 0;
} 

E. MAZE

题目大意:走迷宫,n*m大小的01迷宫,1的位置有一堵墙不可访问,每次可以向左或者向右或者向上走,到达位置标记必须为0,并且不可以走回头路。q个询问,问到第n层某个位置有多少种走法。( n<=5e4 , m<=10 )
分析:

https://blog.csdn.net/weixin_43702895/article/details/99197116

#include<bits/stdc++.h>
#define ls ( cur<<1 )
#define rs ( cur<<1|1 )
#define ms(x)  memset(x,0,sizeof(x))
using namespace std;

typedef long long ll;
const int maxn=5e4+10;
const int maxm=12;
const int mod=1e9+7;
int n,M,q;

int mp[maxn][maxm];

struct matrix{
    ll m[maxm][maxm];

    matrix operator * ( matrix &a ) // 乘法重载 
    {
        matrix x;
        ms(x.m);
        for( int i=1;i<=M;i++ )
        {
            for( int j=1;j<=M;j++ )
            {
                for( int k=1;k<=M;k++ )
                x.m[i][j]=(x.m[i][j]+this->m[i][k]*a.m[k][j])%mod;
            }
        }
        return x;
    }
}; 

struct node{
    int l,r;
    matrix mat;
}t[maxn<<2];


void pushup( int cur )
{
    t[cur].mat=t[ls].mat*t[rs].mat;
} 

void build( int cur,int l,int r )
{
    t[cur].l=l;
    t[cur].r=r;
    if( l==r )
    {
        for( int i=1;i<=M;i++ )
        {
            if( mp[l][i]==1 ) continue;
            else t[cur].mat.m[i][i]=1;
            for( int j=i+1;j<=M;j++ )
            {
                if( mp[l][j]==0 ) t[cur].mat.m[j][i]=1;
                else break;
            }
            for( int j=i-1;j>=1;j-- )
            {
                if( mp[l][j]==0 ) t[cur].mat.m[j][i]=1;
                else break;
            }
        }
        return;
    }

    int mid=(l+r)/2;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(cur);
} 

void update( int cur,int x )
{
    int l=t[cur].l,r=t[cur].r;
    if( l==r )
    {
        ms(t[cur].mat.m);
        for( int i=1;i<=M;i++ )
        {
            if( mp[x][i]==1 ) continue;
            else t[cur].mat.m[i][i]=1;
            for( int j=i+1;j<=M;j++ )
            {
                if( mp[l][j]==0 ) t[cur].mat.m[j][i]=1;
                else break;
            }
            for( int j=i-1;j>=1;j-- )
            {
                if( mp[l][j]==0 ) t[cur].mat.m[j][i]=1;
                else break;
            }
        }
        return;
    }
    int mid=(l+r)/2;
    if( x<=mid ) update(ls,x);
    else update(rs,x);
    pushup(cur);
}

int main()
{
    scanf("%d%d%d",&n,&M,&q);
    for( int i=1;i<=n;i++ ) 
    {
        for( int j=1;j<=M;j++ ) scanf("%1d",&mp[i][j]);
    }
    build(1,1,n);
    while( q-- )
    {
        int opt,a,b;
        scanf("%d%d%d",&opt,&a,&b);
        if( opt==1 )
        {
             mp[a][b]^=1;
             update(1,a);
        }
        else
        {
            printf("%lld\n",t[1].mat.m[a][b]);
        }
    }
    return 0;
}

F. Partition problem

大致题意:2*n个人有不同的竞争关系权值(两个人所属不同队伍就会产生对应的竞争关系权值),请给他们分成两支队伍,使得竞争值最大。
分析:dfs边搜 边更新答案,当前队伍用一个vector来存,一个新人加入队伍更新贡献.

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=2e5+10;

int v[30][30];
ll sum[30];
int n;
ll ans=0;
vector<int> q;

void dfs( int x,ll p )
{
    if( x>2*n ) return;
    if( q.size()==n )
    {
        ans=max(ans,p);
        return;
    }
    dfs(x+1,p);
    q.push_back(x);
    p+=sum[x];
    for( int i=0;i<q.size();i++ ) p-=2*v[x][q[i]];
    dfs(x+1,p);
    q.pop_back();
}


int main()
{    
    scanf("%d",&n);
    for( int i=1;i<=2*n;i++ )
    {
        sum[i]=0;
        for( int j=1;j<=2*n;j++ )
        {
            scanf("%d",&v[i][j]);
            sum[i]+=v[i][j];
        }
    }
    dfs(1,0ll);
    printf("%lld\n",ans);
}

H. Second Large Rectangle

大致题意:给定n*m的01矩阵,找第二大的全1子矩阵.
分析:枚举下边界,单调队列处理出 以a(ij)为高的左右边界,然后更新答案即可.判段重复矩阵那就用个set维护一下.

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e3+5;
int a[maxn][maxn];
int L[maxn],R[maxn];
int s[maxn];

set< pair<pair<int,int>,int> > p;

priority_queue<int, vector<int>, greater<int> > q;

void add(int x)
{
    q.push(x);
    while (q.size() > 2) q.pop();
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for( int i=1;i<=n;i++ )
    {
        for( int j=1;j<=m;j++ )
        {
            scanf("%1d",&a[i][j]);
            a[i][j]+=a[i-1][j]*a[i][j];
        }
    }
    int ans1=0,ans2=0;
    q.push(0);q.push(0);
    for( int i=1;i<=n;i++ )
    {
        int top=0;
        p.clear();
        for( int j=1;j<=m;j++ )  // find l
        {
            while( top && a[i][s[top]]>=a[i][j] ) --top;
            L[j]=( top==0 ? 1 : s[top]+1 );
            s[++top]=j;
            R[j]=j;
        }
        top=0;
        for( int j=m;j>=1;j-- ) //  find r
        {
            while( top && a[i][s[top]]>=a[i][j] ) --top;
            R[j]=( top==0 ? m : s[top]-1 );
            s[++top]=j;
        }

        for( int j=1;j<=m;j++ ) 
        {
            p.insert( make_pair( make_pair(L[j],R[j]),a[i][j] ) );
        }
        for( auto v:p )
        {
            int l1=v.first.first,r1=v.first.second,tmp=v.second;
            int tmp1=(r1-l1+1)*tmp;
            int tmp2=(r1-l1+1)*(tmp-1);
            int tmp3=(r1-l1)*tmp;
            add(tmp1);add(tmp2);add(tmp3);
        }
    }
    printf("%d\n",q.top());
}

J.Subarray

题目大意:一个长度为1e9的序列,给定n组全为1的区间,求有多少区间对(l,r)满足区间和大于0. 序列中1的个数不超过1e7( n<1e6 )
分析:

  • 首先我们讨论如果序列长度很短的话如何求解.显然我们会用前缀和。对于一个区间对,我们固定右边界r,要找有效的左边界,我们只需要的前缀中小于sum[r]的个数即可.
  • 那么回到这道题.虽然序列的长度是1e9,但是1的个数不超过1e7.那么我们考虑一段区间(3,5)全为1,与其关联的产生贡献区间只有(1,2) 和 (6,7).那么对于整个1e9序列,我们只用考虑区间长度最多其实就是1e7的量级.
  • 对于刚才的举例如何求得(1,2) 和(6,7)呢.我们可以用两个数组来维护back[]和front[],back 表示一个区间 以 第 i 段的左边界为左边界 向右延长的最大区间和,front 表示一个区间 以 第 i段的右边界为右边界 向左延长的最大区间和.这样就可以求得关联区间了.
  • 并且为了优化计算,我们还可以合并有效区间,对于两个区间交,并为一个区间计算。这样就处理出所有区间,那么我们按照前缀和的方法计算即可.
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=1e7+10;
const int maxm=1e6+10;
int n;
int l[maxm],r[maxm]; // l[i],r[i]表示第i段全1区间的左右边界 
int back[maxm],front[maxm]; // back  表示一个区间 以 第 i段的左边界为左边界 的最大区间和
                            // front 表示一个区间 以 第 i段的右边界为右边界 的最大区间和  
int cnt[maxn<<2];  // 计数相同前缀和个数 
int num2;
int sum[maxn<<2];  // 维护有效合并区间前缀和 
ll ans=0;

int main()
{
    scanf("%d",&n);
    r[n+1]=l[n+1]=1e9+10;
    for( int i=1;i<=n;i++ )
    {
        int a,b;scanf("%d%d",&a,&b);
        l[i]=a+1;r[i]=b+1;
    }
    // 更新 back  front 
    for( int i=1;i<=n;i++ )  front[i]=r[i]-l[i]+1+max( front[i-1]-(l[i]-r[i-1]-1),0 );
    for( int i=n;i>=1;i-- )  back[i]=r[i]-l[i]+1+max( back[i+1]-(l[i+1]-r[i]-1),0 );

    int i=1;
    int base=1e7+10;
    while( i<=n )
    {
        int pos=i,t=i;
        while( pos<=n && front[pos]+back[pos+1]>=l[pos+1]-r[pos]-1 ) pos++;  // 合并区间 
        int first=max(l[i]-back[i],1),last=min((int)1e9,r[pos]+front[pos]); // 确定合并区间的有效边界
        int len=last-first+1; // 合并区间de长度 
        for( int k=first;k<=last;k++ )
        {
            int id=k-first+1;
            if( k>=l[t] && k<=r[t] ) sum[id]=sum[id-1]+1; // 当前位置是 1 
            else sum[id]=sum[id-1]-1;                      // 当前位置是 -1 
            if( k==r[t] ) t++;                            //  t就是 区间迭代序号
            cnt[sum[id]+base]=0;                           //  初始化 
        } 
        cnt[base+0]=1;
        for( int k=1;k<=len;k++ )                   
        {
            if( sum[k]>sum[k-1] ) num2=num2+cnt[sum[k-1]+base]; // num2表示在sum[i]前面比sum[i]小的数的个数
            else num2=num2-cnt[sum[k]+base];                     
            cnt[sum[k]+base]++;
            ans+=num2;                                             //区间对 右边界为 last+k-1 的贡献 
        }
        num2=0;
        i=pos+1;
    }
    printf("%lld\n",ans);
    return 0; 
}