本场总结:
题目类型:
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; }