2019牛客暑期多校训练营(第二场)


Solutions


A:Eddy Walker

题意:

给出$n$个点,编号为$0\sim n-1$,问走完所有点恰巧到达$m$点的概率。

思路:

如果,只要$m$不为$0$,到其他点概率都是一样的,为$\frac1{n-1}$,然后判$m=0$的情况。

 1 //#define DEBUG
 2 #include
 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 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include <string>
  7 #include 
  8 #include <set>
  9 #include 
 10 #include
 11 using namespace std;
 12 #define rep(i,a,n) for (int i=a;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<;
 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)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)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) {
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
 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"%lld",&a[i]);
31     for(int i=0;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 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) {
50             if(r[i]) {
51                 pos=i+1;
52             }
53         }
54         for(int i=pos;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$个以下两种欧冠操作:

  1. 将某个格子的状态反转
  2. 询问从$\left(1,x\right)走到\left(n,y\right)$的方案数
  • $1\leq\ n$,$q\leq\ 5e4$,$1\leq\ m\leq10$

思路:

参考1、、3Bolgs

先不考虑修改,设$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
 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
 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"%d ",sa[num1]);
19         puts("");
20         for(int i=0;i"%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;ia[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;ia[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
 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 stackint,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 }