2019牛客暑期多校训练营(第二场)
Solutions
A:Eddy Walker
题意:
给出$n$个点,编号为$0\sim n-1$,问走完所有点恰巧到达$m$点的概率。
思路:
如果,只要$m$不为$0$,到其他点概率都是一样的,为$\frac1{n-1}$,然后判$m=0$的情况。
1 //#define DEBUG 2 #include<bits/stdc++.h> 3 using namespace std; 4 const int N=100010; 5 const int inf=0X3f3f3f3f; 6 const long long INF = 0x3f3f3f3f3f3f3f3f; 7 const double eps = 1e-6; 8 const double pi = acos(-1.0); 9 const int mod = 1000000007; 10 typedef long long ll; 11 12 ll q_pow(ll a,ll b) { 13 ll ans=1; 14 ll tmp=a%mod; 15 while(b) { 16 if(b&1) ans=ans*tmp%mod; 17 tmp=tmp*tmp%mod; 18 b=b>>1; 19 } 20 return ans; 21 } 22 23 int main() { 24 #ifdef DEBUG 25 freopen("in.txt","r",stdin); 26 #endif 27 int _; 28 ll ans=1; 29 for(scanf("%d",&_);_;_--) { 30 ll n,m; 31 scanf("%lld%lld",&n,&m); 32 if(n==1&&m==0) ans*=1; 33 else if(m==0&&n>1) ans=0; 34 else ans=ans*q_pow(n-1,mod-2)%mod; 35 printf("%lld\n",ans); 36 } 37 }
B:Eddy Walker 2
题意:
某人开始在$0$点,给出目标点$k$,和每步能走的最大距离$k$,且走距离$1\sim k$概率都为$\frac1k$,求最后到达$n$点的概率,$n=-1$表示无穷远。
思路:
$dp[i]$表示到达$i$点的概率,那么
$dp[i]=\frac1k\sum_{j=i-k}^{i-1}dp[j]$,线性递推式,丢进BM里搞一搞
1 #include <cstdio> 2 #include <cstring> 3 #include <cmath> 4 #include <algorithm> 5 #include <vector> 6 #include <string> 7 #include <map> 8 #include <set> 9 #include <cassert> 10 #include<bits/stdc++.h> 11 using namespace std; 12 #define rep(i,a,n) for (int i=a;i<n;i++) 13 #define per(i,a,n) for (int i=n-1;i>=a;i--) 14 #define pb push_back 15 #define mp make_pair 16 #define all(x) (x).begin(),(x).end() 17 #define fi first 18 #define se second 19 #define SZ(x) ((int)(x).size()) 20 typedef vector<int> VI; 21 typedef long long ll; 22 typedef pair<int,int> PII; 23 const ll mod=1000000007; 24 ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;} 25 // head 26 27 namespace linear_seq { 28 const int N=10010; 29 ll res[N],base[N],_c[N],_md[N]; 30 31 vector<int> Md; 32 void mul(ll *a,ll *b,int k) { 33 rep(i,0,k+k) _c[i]=0; 34 rep(i,0,k) if (a[i]) rep(j,0,k) _c[i+j]=(_c[i+j]+a[i]*b[j])%mod; 35 for (int i=k+k-1;i>=k;i--) if (_c[i]) 36 rep(j,0,SZ(Md)) _c[i-k+Md[j]]=(_c[i-k+Md[j]]-_c[i]*_md[Md[j]])%mod; 37 rep(i,0,k) a[i]=_c[i]; 38 } 39 int solve(ll n,VI a,VI b) { // a 系数 b 初值 b[n+1]=a[0]*b[n]+... 40 ll ans=0,pnt=0; 41 int k=SZ(a); 42 assert(SZ(a)==SZ(b)); 43 rep(i,0,k) _md[k-1-i]=-a[i];_md[k]=1; 44 Md.clear(); 45 rep(i,0,k) if (_md[i]!=0) Md.push_back(i); 46 rep(i,0,k) res[i]=base[i]=0; 47 res[0]=1; 48 while ((1ll<<pnt)<=n) pnt++; 49 for (int p=pnt;p>=0;p--) { 50 mul(res,res,k); 51 if ((n>>p)&1) { 52 for (int i=k-1;i>=0;i--) res[i+1]=res[i];res[0]=0; 53 rep(j,0,SZ(Md)) res[Md[j]]=(res[Md[j]]-res[k]*_md[Md[j]])%mod; 54 } 55 } 56 rep(i,0,k) ans=(ans+res[i]*b[i])%mod; 57 if (ans<0) ans+=mod; 58 return ans; 59 } 60 VI BM(VI s) { 61 VI C(1,1),B(1,1); 62 int L=0,m=1,b=1; 63 rep(n,0,SZ(s)) { 64 ll d=0; 65 rep(i,0,L+1) d=(d+(ll)C[i]*s[n-i])%mod; 66 if (d==0) ++m; 67 else if (2*L<=n) { 68 VI T=C; 69 ll c=mod-d*powmod(b,mod-2)%mod; 70 while (SZ(C)<SZ(B)+m) C.pb(0); 71 rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod; 72 L=n+1-L; B=T; b=d; m=1; 73 } else { 74 ll c=mod-d*powmod(b,mod-2)%mod; 75 while (SZ(C)<SZ(B)+m) C.pb(0); 76 rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod; 77 ++m; 78 } 79 } 80 return C; 81 } 82 int gao(VI a,ll n) { 83 VI c=BM(a); 84 c.erase(c.begin()); 85 rep(i,0,SZ(c)) c[i]=(mod-c[i])%mod; 86 return solve(n,c,VI(a.begin(),a.begin()+SZ(c))); 87 } 88 }; 89 90 ll dp[3000]; 91 int main() { 92 int _; 93 for(scanf("%d",&_);_;_--) { 94 int k; 95 ll n; 96 scanf("%d%lld",&k,&n); 97 if(n==-1) { 98 printf("%lld\n",2*powmod(k+1,mod-2)%mod); 99 } else { 100 VI t; 101 dp[0]=1; 102 t.push_back(dp[0]); 103 for(int i=1;i<=2*k;i++) { 104 dp[i]=0; 105 for(int j=max(0,i-k);j<i;j++) { 106 dp[i]=(dp[i]+dp[j])%mod; 107 } 108 dp[i]=dp[i]*powmod(k,mod-2)%mod; 109 t.push_back(dp[i]); 110 } 111 printf("%lld\n",linear_seq::gao(t,n)); 112 } 113 } 114 }
D:Kth Minimum Clique
题意:
给出$n$个点以及点权,求第$k$小团。空团是第一小。
思路:
可以拿着空的,往里面放。每次取出最小的,往里放点,放的时候判断一下是否和团里面的都有边,判断可以用$bitset$优化
放的时候,编号递增的放,防止放入重复点。
1 //#define DEBUG 2 #include<bits/stdc++.h> 3 using namespace std; 4 const int N=100010; 5 const int inf=0X3f3f3f3f; 6 const long long INF = 0x3f3f3f3f3f3f3f3f; 7 const double eps = 1e-6; 8 const double pi = acos(-1.0); 9 const int mod = 1000000007; 10 typedef long long ll; 11 12 struct Node{ 13 ll w; 14 bitset<110> S; 15 bool operator < (const Node& b) const { 16 return w>b.w; 17 } 18 }; 19 20 char buf[110]; 21 bitset<110> bs[110]; 22 ll a[110]; 23 24 int main() { 25 #ifdef DEBUG 26 freopen("in.txt","r",stdin); 27 #endif 28 int n,k; 29 scanf("%d%d",&n,&k); 30 for(int i=0;i<n;i++) scanf("%lld",&a[i]); 31 for(int i=0;i<n;i++) { 32 scanf("%s",buf); 33 for(int j=0;buf[j];j++) { 34 if(buf[j]=='1') bs[i][j]=1; 35 } 36 } 37 priority_queue<Node> pq; 38 bitset<110> t; 39 pq.push((Node){0,t}); 40 while(!pq.empty()) { 41 Node f=pq.top();pq.pop(); 42 bitset<110> r=f.S; 43 k--; 44 if(k==0) { 45 printf("%lld\n",f.w); 46 return 0; 47 } 48 int pos=0; 49 for(int i=0;i<n;i++) { 50 if(r[i]) { 51 pos=i+1; 52 } 53 } 54 for(int i=pos;i<n;i++) { 55 if(!r[i]&&(bs[i]&r)==r) { 56 r[i]=1; 57 pq.push((Node){f.w+a[i],r}); 58 r[i]=0; 59 } 60 } 61 } 62 puts("-1"); 63 }
E:MAZE
题意:
有一个$n\times\ m$的$01$矩阵,$1$表示不可行,$0$表示可行
每次可以从$\left(i,j\right)$走到$\left(i,j-1\right),\left(i,j+1\right),\left(i+1,j\right)$,且不能回到走过的格子
有$q$个以下两种欧冠操作:
- 将某个格子的状态反转
- 询问从$\left(1,x\right)走到\left(n,y\right)$的方案数
- $1\leq\ n$,$q\leq\ 5e4$,$1\leq\ m\leq10$
思路:
先不考虑修改,设$a[i][j]$表示第$i$行第$j$个元素
$dp[i][j]$表示能从第$i-1$行经过$\left(i-1,j\right)$走到$\left(i,j\right)$的方案数,状态转移方程如下:
$dp[i][j]=\sum_{k=1}^{j-1}{dp[i-1][k]}_{for\ a[i-1][k]=0} +\sum_{k=j}^m\ {dp[i-1][k]}_{for\ a[i-1][k]=0}$
比如,$n=2,m=6$时,
$dp[2][2]=dp[1][1]+dp[1][2]+dp[1][3]$
$dp[2][6]=dp[1][5]+dp[1][6]$
我们可以用“列可达”矩阵表示转移关系,然后乘起来就是最后的结果。
“列可达”比如第一行的列可达,第一列为$0$,所以$\left(0,0\right)$位置为$1$,它能到达$2,3$列所以$\left(2,1\right),\left(3,1\right)$为$1$,遇到$1$结束。(见下图)
依次类推。
也就是这样:
发现两个矩阵相乘就可以查询答案了。可以用线段树维护矩阵相乘,每个节点维护一个矩阵(一行)。
修改的话,就修改维护那一行的节点。
1 //#define DEBUG 2 #include<bits/stdc++.h> 3 using namespace std; 4 const int N=50010; 5 const int inf=0X3f3f3f3f; 6 const long long INF = 0x3f3f3f3f3f3f3f3f; 7 const double eps = 1e-6; 8 const double pi = acos(-1.0); 9 const int mod = 1000000007; 10 typedef long long ll; 11 12 int n,m,a[N][15]; 13 14 struct Mat{ 15 ll a[15][15]; 16 void init() {memset(a,0,sizeof(a));} 17 Mat operator * (const Mat &b)const { 18 Mat ans; 19 ans.init(); 20 for(int i=1;i<=m;i++) { 21 for(int j=1;j<=m;j++) { 22 for(int k=1;k<=m;k++) { 23 ans.a[i][j]=(ans.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod; 24 } 25 } 26 } 27 return ans; 28 } 29 }mat[N<<2]; 30 31 void mul(int rt,int x) { 32 mat[rt].init(); 33 for(int i=1;i<=m;i++) { 34 if(a[x][i]) continue; 35 mat[rt].a[i][i]=1; 36 for(int k=i+1;k<=m&&(!a[x][k]);k++) 37 mat[rt].a[k][i]=1; 38 for(int k=i-1;k>=1&&(!a[x][k]);k--) 39 mat[rt].a[k][i]=1; 40 } 41 } 42 43 void build(int l,int r,int rt) { 44 if(l==r) { 45 mul(rt,l); 46 return ; 47 } 48 int mid=(l+r)>>1; 49 build(l,mid,rt<<1); 50 build(mid+1,r,rt<<1|1); 51 mat[rt]=mat[rt<<1]*mat[rt<<1|1]; 52 } 53 54 void updata(int v,int l,int r,int rt) { 55 if(l==r) { 56 mul(rt,l); 57 return ; 58 } 59 int mid=(l+r)>>1; 60 if(v<=mid) updata(v,l,mid,rt<<1); 61 else updata(v,mid+1,r,rt<<1|1); 62 mat[rt]=mat[rt<<1]*mat[rt<<1|1]; 63 } 64 65 int main() { 66 #ifdef DEBUG 67 freopen("in.txt","r",stdin); 68 #endif 69 int q; 70 scanf("%d%d%d",&n,&m,&q); 71 for(int i=1;i<=n;i++) { 72 for(int j=1;j<=m;j++) 73 scanf("%1d",&a[i][j]); 74 } 75 build(1,n,1); 76 while(q--) { 77 int op,x,y; 78 scanf("%d%d%d",&op,&x,&y); 79 if(op==1) { 80 a[x][y]^=1; 81 updata(x,1,n,1); 82 } else printf("%lld\n",mat[1].a[x][y]); 83 } 84 return 0; 85 }
F:Partition problem
题意:
$2n$个人,每个人都与其他人有对抗值,分成两个集合且集合大小相等,求分配后的最大对抗值
思路:
啥都不说,就是不会$DFS$,感觉还做过。给出链接
1 //#define DEBUG 2 #include<bits/stdc++.h> 3 using namespace std; 4 const int N=100010; 5 const int inf=0X3f3f3f3f; 6 const long long INF = 0x3f3f3f3f3f3f3f3f; 7 const double eps = 1e-6; 8 const double pi = acos(-1.0); 9 const int mod = 1000000007; 10 typedef long long ll; 11 12 ll a[30][30],ans; 13 int sa[30],sb[30]; 14 int n; 15 16 void dfs(int id,int num1,int num2,ll sum) { 17 if(num1==n&&num2==n) { 18 for(int i=0;i<num1;i++) printf("%d ",sa[num1]); 19 puts(""); 20 for(int i=0;i<num2;i++) printf("%d ",sb[num2]); 21 puts(""); 22 ans=max(ans,sum); 23 return; 24 } 25 if(num1<n) { 26 ll tmp=0; 27 for(int i=0;i<num2;i++) tmp+=a[id][sb[i]]; 28 sa[num1]=id; 29 dfs(id+1,num1+1,num2,sum+tmp); 30 } 31 if(num2<n) { 32 ll tmp=0; 33 for(int i=0;i<num1;i++) tmp+=a[id][sa[i]]; 34 sb[num2]=id; 35 dfs(id+1,num1,num2+1,sum+tmp); 36 } 37 } 38 int main() { 39 #ifdef DEBUG 40 freopen("in.txt","r",stdin); 41 #endif 42 scanf("%d",&n); 43 for(int i=1;i<=2*n;i++) { 44 for(int j=1;j<=2*n;j++) 45 scanf("%lld",&a[i][j]); 46 } 47 dfs(1,0,0,0); 48 printf("%lld\n",ans); 49 }
H:Second Large Rectangle
题意:
求次大全$1$矩阵的面积。
思路:
首先最大全$1$矩阵可以通过单调栈求出,用同样的方法,把二维的压下来,每一行存当前高度,然后每一行都用单调栈维护,求得的矩阵去更新答案,注意!!!$x\ast(y-1),y\ast(x-1)$也要更新答案。。。当时没理解,加入就AC了。。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int maxn=100010; 5 const int inf=0x3f3f3f3f; 6 7 int a[1010][1010]; 8 int n,m; 9 stack<pair<int,int> > s; 10 vector<int> ansans; 11 int main() { 12 while(~scanf("%d%d",&n,&m)) { 13 for(int i=1;i<=n;i++) { 14 for(int j=1;j<=m;j++) { 15 scanf("%1d",&a[i][j]); 16 if(a[i][j]) a[i][j]+=a[i-1][j]; 17 } 18 a[i][m+1]=0; 19 } 20 pair<int,int> v; 21 for(int i=1;i<=n;i++) { 22 for(int j=1;j<=m+1;j++) { 23 if(s.empty()||s.top().second<=a[i][j]) { 24 s.push({1,a[i][j]}); 25 } 26 else { 27 int sum=0; 28 while(!s.empty()&&s.top().second>a[i][j]) { 29 v=s.top(); 30 sum+=v.first; 31 int area1=sum*v.second; 32 int area2=(sum-1)*v.second; 33 int area3=sum*(v.second-1); 34 ansans.push_back(area1); 35 ansans.push_back(area2); 36 ansans.push_back(area3); 37 s.pop(); 38 } 39 s.push({sum+1,a[i][j]}); 40 } 41 } 42 while(!s.empty()) s.pop(); 43 } 44 sort(ansans.begin(),ansans.end()); 45 int len=ansans.size(); 46 if(len<2) printf("0\n"); 47 else { 48 printf("%d\n",ansans[len-2]); 49 } 50 while(!s.empty()) s.pop(); 51 ansans.clear(); 52 } 53 }