2020牛客寒假算法基础集训营3(全解)
题目链接:https://ac.nowcoder.com/acm/contest/3004
这里出题人的题解很详细,有多种解法:https://ac.nowcoder.com/discuss/365306?type=101&order=0&pos=1&page=1
题目说明:
A.牛牛的DRB迷宫I B.牛牛的DRB迷宫II C.牛牛的数组越位 D. 牛牛与二叉树的数组存储
(DP|记忆化搜索) (二进制) (模拟+STL) (模拟)
E.牛牛的随机数 F.牛牛的Link Power I G.牛牛的Link Power II H.牛牛的k合因子数
(数位DP) (前缀和) (线段树) (素数筛)
I.牛牛的汉诺塔 J.牛牛的宝可梦Go
(记忆化搜索) (DP+最短路)
A.牛牛的DRB迷宫I
题目大意:给你n*m的图(n,m<=50),问你从(1,1)到(n,m)有多少种走法,其中地图包含B,R,D(R只能向右走,D只能向下走,B都可),答案对1e9+7取模
示例
输入
5 5 RBBBR BBBBB BBBDB BDBBB RBBBB
输出
25
。。。一道格子DP,我们应该都做过只能向下和向右走的地图,$dp[i][j]+=dp[i][j-1]+dp[i-1][j]$,那么这题其实是一样的,不过我们也可以搜索,只不过为了防止TLE,我们可以记忆化一下。
DP的AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mac=55; const int mod=1e9+7; int n,m; char s[mac][mac]; ll dp[mac][mac]; int main(int argc, char const *argv[]) { int n,m; scanf ("%d%d",&n,&m); for (int i=1; i<=n; i++) scanf ("%s",s[i]+1); dp[n][m]=1; for (int i=n; i>=1; i--) for (int j=m; j>=1; j--){ if (s[i][j]=='R') dp[i][j]=(dp[i][j]+dp[i][j+1])%mod; else if (s[i][j]=='D') dp[i][j]=(dp[i][j]+dp[i+1][j])%mod; else dp[i][j]=(dp[i][j]+dp[i][j+1]+dp[i+1][j])%mod; } printf("%lld\n",dp[1][1]); return 0; }
记忆化搜索的AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mac=55; const int mod=1e9+7; #define ok(x,y,n,m) ((x>=1) && (x<=n) && (y>=1) && (y<=m)) char s[mac][mac]; ll dp[mac][mac],mk[mac][mac]; int vis[mac][mac],n,m; int dx[]={0,1},dy[]={1,0};//向右,向下 ll dfs(int x,int y,int id) { if (dp[x][y]) return dp[x][y]; if (x==n && y==m) {dp[x][y]++; return dp[x][y];} int xx,yy; if (id==0 || id==1){ xx=x+dx[id];yy=y+dy[id]; if (vis[xx][yy] || !ok(xx,yy,n,m)) return 0; vis[xx][yy]=1; dp[x][y]=(dp[x][y]+dfs(xx,yy,mk[xx][yy]))%mod; vis[xx][yy]=0; } else { for (int i=0; i<2; i++){ xx=x+dx[i];yy=y+dy[i]; if (vis[xx][yy] || !ok(xx,yy,n,m)) continue; vis[xx][yy]=1; dp[x][y]=(dp[x][y]+dfs(xx,yy,mk[xx][yy]))%mod; vis[xx][yy]=0; } } return dp[x][y]; } int main(int argc, char const *argv[]) { scanf ("%d%d",&n,&m); for (int i=1; i<=n; i++) scanf("%s",s[i]+1); for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) if (s[i][j]=='R') mk[i][j]=0; else if (s[i][j]=='D') mk[i][j]=1; else mk[i][j]=2; memset(dp,0,sizeof dp); printf("%lld\n",dfs(1,1,mk[1][1])); return 0; }
B.牛牛的DRB迷宫II
题目大意:和A题题面反过来的,那种地图如果有n种走法,输出地图(大小在50*50)
示例
输入
25
输出
5 5 RBBBR BBBBB BBBDB BDBBB RBBBB
emmmm,这张地图其实可以表示任何一个数,两种走法和一种走法,这样可以安排组合也就是几个两种走法的,几个一种走法的,这样一定能构成n。
#include <bits/stdc++.h> using namespace std; char mp[55][55]; int main(int argc, char const *argv[]) { int n,m,k; n=32,m=30; scanf ("%d",&k); for (int i=1; i<=m; i++) mp[n][i]='R'; for (int i=1; i<n; i++){//对角线的前一个到后一个先设为B,其余废弃 for (int j=1; j<=m; j++){ if (i==1) mp[1][j]=(j<=2)?'B':'R'; else { if (j<i-1) mp[i][j]='D'; else if (j<i+2) mp[i][j]='B'; else mp[i][j]='R'; } } }//此时mp[n][m]为2^(n-1) for (int i=1; i<=m; i++){//将k拆解为二进制数 if (!(k&(1<<(i-1)))) mp[i+1][i]='R';//如果k在此位为0,那么就变B为R } printf("%d %d\n",n,m); for (int i=1; i<=n; i++) printf("%s\n",mp[i]+1); return 0; }
C.牛牛的数组越位
题目大意:T组数据,给你一个二维数组[n][m](n*m<=2e7)。接下来p个操作,每次操作$x,y,val$如果$x,y$有越界行为,则输出整个数组,之后输出“Undefined Behaviour”,如果有非法行为,直接输出“Runtime error”,否则输出整个数组后输出“Accepted”。
示例
输入
4 2 4 8 -1 4 1 1 -3 2 2 -6 3 0 3 4 -1 8 5 -2 13 6 -100 406 7 100 -393 8 5 5 1 -1 8 1234 10 10 1 5 -51 1 1 1 1 0 0 7
输出
1 2 3 4 5 6 7 8 Undefined Behaviour 0 0 0 1234 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Undefined Behaviour Runtime error 7 Accepted
就是个纯粹的模拟题,只不过如何判断二维数组的大小不知道,而且清空太麻烦了,我们可以用map,这样就免去了清空数组的时间:
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define ok(x,y,n,m) ((x>=0) && (x<n) && (y>=0) && (y<m)) int main(int argc, char const *argv[]) { int t; scanf ("%d",&t); while (t--){ int n,m,pp; scanf ("%d%d%d",&n,&m,&pp); int re=0,ub=0; typedef pair<int,int>use; map<use,int>q; int lim=n*m; while (pp--){ int x,y,val; scanf ("%d%d%d",&x,&y,&val); int use=m*x+y; if (use<0 || use>=lim) re=1;//注意有等于号 if (!ok(x,y,n,m)) ub=1; int xx=use/m,yy=use%m; q[make_pair(xx,yy)]=val; } if (re) printf("Runtime error\n"); else if (ub) { for (int i=0; i<n; i++) for (int j=0; j<m; j++){ printf("%d",q[make_pair(i,j)]); printf("%c",j==(m-1)?'\n':' '); } printf("Undefined Behaviour\n"); } else { for (int i=0; i<n; i++) for (int j=0; j<m; j++){ printf("%d",q[make_pair(i,j)]); printf("%c",j==(m-1)?'\n':' '); } printf("Accepted\n"); } } return 0; }
D.牛牛与二叉树的数组存储
题目大意:给你一颗满二叉树(数组表示)(不存在的节点用-1代替)输出树的尺寸和根节点,并按照顺序打印每个节点的父亲节点、左孩子、右孩子
示例
输入
7 3 -1 2 -1 -1 1 4
输出
The size of the tree is 4 Node 3 is the root node of the tree The father of node 1 is 2, the left child is -1, and the right child is -1 The father of node 2 is 3, the left child is 1, and the right child is 4 The father of node 3 is -1, the left child is -1, and the right child is 2 The father of node 4 is 2, the left child is -1, and the right child is -1
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; struct node { int fa,l,r; }ans[mac]; int a[mac],n; int sonval(int rt) { if (rt>n) return -1; return a[rt]; } void build(int rt,int lim,int fa) { if (rt>lim) return ; if (a[rt]==-1) return ; ans[a[rt]]=node{fa,sonval(rt<<1),sonval(rt<<1|1)}; build(rt<<1,lim,a[rt]);build(rt<<1|1,lim,a[rt]); } int main(int argc, char const *argv[]) { int sz=0; scanf ("%d",&n); for (int i=1; i<=n; i++){ scanf("%d",&a[i]); if (a[i]!=-1) sz++; ans[i].fa=ans[i].l=ans[i].r=-1; } build(1,n,-1); printf("The size of the tree is %d\n",sz); printf("Node %d is the root node of the tree\n",a[1]); for (int i=1; i<=sz; i++){ printf("The father of node %d is %d, ",i,ans[i].fa); printf("the left child is %d, ",ans[i].l); printf("and the right child is %d\n",ans[i].r); } return 0; }
E.牛牛的随机数
题目大意:T组数据(<=1e5),从区间$[l_{1},r_{1}]$和$l_{2},r_{2}$($l_{1},r_{1},l_{2},r_{2}<=1e18$)中分别取一个数$a,b$,问你$a\bigoplus b$的数学期望,并对1e9+7取模
示例
输入
2 3 5 7 8 1 3 3 5
输出
500000011 222222228
没办法,先看看暴力吧,$\frac{\sum_{x=l_{1}}^{r_{1}}\sum_{y=l_{2}}^{r_{2}}x\bigoplus y}{(r_{1}-l_{1}+1)\times (r_{2}-l_{2}+1)}$
然后就是跑数位DP了,出题人说的明白了,我就不多说了,一些小细节打了注释
以下是AC代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll mod=1e9+7; ll a[105],b[105],na[105],nb[105]; struct node { ll s,ss; }; node dp[105]; bool vis[105]; node dfs(int pos,bool limt1,bool limt2)//x,y的第pos位限制 { if(pos==-1) return node{0,1}; if(!limt1 && !limt2 && vis[pos]) return dp[pos];//pos位如果没有限制 if(!limt1 && limt2)//y没有限制 return node{(((1ll<<pos)%mod)*((2ll<<pos)%mod-1)%mod)*((nb[pos]+1)%mod)%mod,(((nb[pos]+1)%mod)*((2ll<<pos)%mod))%mod}; if(limt1 && !limt2) return node{(((1ll<<pos)%mod)*((2ll<<pos)%mod-1)%mod)*((na[pos]+1)%mod)%mod,(((na[pos]+1)%mod)*((2ll<<pos)%mod))%mod}; int up1=limt1?a[pos]:1;//如果没有限制,那你们此位的二进制数位最大1 int up2=limt2?b[pos]:1; node temp{0,0}; for (int i=0; i<=up1; i++) for (int j=0; j<=up2; j++){ node sm=dfs(pos-1,limt1 && i==a[pos],limt2 && j==b[pos]); if (i^j){ temp.s=(temp.s+(1ll<<pos)%mod*sm.ss%mod+sm.s)%mod; temp.ss=(temp.ss+sm.ss)%mod; } else{ temp.s=(temp.s+sm.s)%mod; temp.ss=(temp.ss+sm.ss)%mod; } } if(!limt1 && !limt2){ vis[pos]=true; dp[pos]=temp; } return temp; } ll solve(long long x,long long y) { memset(na,0,sizeof(na)); memset(nb,0,sizeof(nb)); memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); int len1=0,len2=0; while(x){//二进制下的每一位 a[len1++]=x&1; x>>=1; } while(y){ b[len2++]=y&1; y>>=1; } int len=max(len1,len2)-1; for(int i=0;i<=len;++i){ x+=a[i]<<i; na[i]=x; y+=b[i]<<i; nb[i]=y; } return dfs(len,true,true).s; } ll qick(ll x,ll y) { ll ans=1; while(y){ if(y&1) ans=(x*ans)%mod; x=(x*x)%mod; y>>=1; } return ans; } int main() { int t; memset(vis,false,sizeof(vis)); scanf("%d",&t); while(t--){ ll l1,l2,r1,r2; scanf("%lld%lld%lld%lld",&l1,&r1,&l2,&r2); ll ans=0; ans+=solve(r1,r2); ans-=solve(r1,l2-1); ans-=solve(l1-1,r2); ans+=solve(l1-1,l2-1); ans=(ans%mod+mod)%mod; ans=ans*qick(((r1-l1+1)%mod)*((r2-l2+1)%mod)%mod,mod-2)%mod; printf("%lld\n",ans); } return 0; }
F. 牛牛的Link Power I
题目大意:给你一个01串,dis(u,v)为数组上u到v的距离,(u,v)和(v,u)被认为是同一对。求所有字符1的dis的和,答案对1e9+7取模
示例
输入
3 101
输出
2
记录一下1的个数和每个1的位置,然后记录一下所有1位置的和,第一位置对答案的贡献就是:$(a_{2}-a_{1})+(a_{3}-a_{1})+\cdots +(a_{n}-a_{1})=s_{n}-a_{1}-(n-1)*a_{1}$
由于是无序对,所以,每次s都会减去当前的位置值。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; const int mod=1e9+7; typedef long long ll; char s[mac]; vector<int>g; int main(int argc, char const *argv[]) { int n; scanf ("%d",&n); scanf ("%s",s+1); int nb=0; for (int i=1; i<=n; i++) if (s[i]=='1') g.push_back(i); ll sum=0; for (auto x:g) sum+=x; ll ans=0; int len=g.size(); for (auto x:g){ ans=(ans+(sum-x)%mod-1ll*x*(len-1)%mod+mod)%mod; sum-=x;len--; } printf("%lld\n",ans); return 0; }
G.牛牛的Link Power II
题目大意:和上面的I差不多,只不过多了个修改操作,q,pos,q=1即将pos位置的0改成1,q=2,将pos位置1改为0
示例
输入
5 00001 7 1 1 2 5 2 1 1 2 1 4 1 3 1 1
输出
0 4 0 0 0 2 4 10
和上面差不多的思路,维护一下pos前面和后面的数量(num)和位置和(sum),那么新加一个点对前面的贡献也就是$ans+pos*num-sum$,对后面的贡献就是$ans+sum-pos*num$,这个可以用线段树维护
以下是AC代码:
#include <bits/stdc++.h> using namespace std; #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 const int mac=1e5+10; const int mod=1e9+7; typedef long long ll; struct node { ll nb,val; }tree[mac<<2]; char s[mac]; void build(int l,int r,int rt) { tree[rt].nb=tree[rt].val=0; if (l==r) return; int mid=(l+r)>>1; build(lson);build(rson); } void update(int l,int r,int rt,int pos,int mk,int val) { if (l==r) { tree[rt].nb=mk; tree[rt].val=val; return; } int mid=(l+r)>>1; if (pos<=mid) update(lson,pos,mk,val); else update(rson,pos,mk,val); tree[rt].nb=tree[rt<<1].nb+tree[rt<<1|1].nb; tree[rt].val=tree[rt<<1].val+tree[rt<<1|1].val; } node solve(node a,node b) { return node{a.nb+b.nb,a.val+b.val}; } node query(int l,int r,int rt,int L,int R) { node ans={0,0}; if (l>=L && r<=R) return tree[rt]; int mid=(l+r)>>1; if (L<=mid) ans=solve(ans,query(lson,L,R)); if (R>mid) ans=solve(ans,query(rson,L,R)); return ans; } int main(int argc, char const *argv[]) { int n; scanf ("%d",&n); build(1,n,1); scanf ("%s",s+1); ll sum=0; for (int i=1; i<=n; i++) if (s[i]=='1') update(1,n,1,i,1,i); int m; scanf ("%d",&m); for (int i=1; i<=n; i++){ if (s[i]=='1'){ node p=query(1,n,1,i+1,n); sum=(sum+p.val-p.nb*i%mod+mod)%mod; } } printf("%lld\n",sum); while (m--){ int q,pos; scanf ("%d%d",&q,&pos); if (q==1){ node p=query(1,n,1,1,pos); sum=(sum+(pos*p.nb%mod)-p.val+mod)%mod; p=query(1,n,1,pos,n); sum=(sum+p.val-(pos*p.nb%mod)+mod)%mod; update(1,n,1,pos,1,pos); } else { node p; if (pos!=1){ p=query(1,n,1,1,pos-1); sum=(sum-(((pos*p.nb%mod)-p.val+mod)%mod)+mod)%mod; } if (pos!=n){ p=query(1,n,1,pos+1,n); sum=(sum-((p.val-(pos*p.nb%mod)+mod)%mod)+mod)%mod; } update(1,n,1,pos,0,0); } printf("%lld\n",sum); } return 0; }
H.牛牛的k合因子数
题目大意:输出1~n(1e5)中给定k的情况下k合因子数(有k个因子为合数)的数目。m次询问
示例
输入
10 5 1 2 3 4 5
输出
4 1 0 0 0
埃式筛筛出质数,再对1到n中的所有数用$\sqrt{n}$的复杂度求每个数的因子并判断是否为质数就好了。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; int disprime[mac],vis[mac],ans[mac]; int solve(int x)//判断有几个合数因子 { int ans=0; int p=sqrt(x); if (p*p==x) if (vis[p]) ans++; for (int i=1; i<=p; i++){ if (i*i==x) continue; if ((x%i)==0) { int u=i,v=x/i; if (vis[u]) ans++; if (vis[v]) ans++; } } return ans; } int main(int argc, char const *argv[]) { int n,m; scanf ("%d%d",&n,&m); int p=sqrt(n); for (int i=2; i<=p; i++) if (!vis[i]) for (int j=i*i; j<=n; j+=i) vis[j]=1; for (int i=2; i<=n; i++){ disprime[i]=solve(i); ans[disprime[i]]++; } while (m--){ int x; scanf("%d",&x); printf("%d\n",ans[x]); } return 0; }
I.牛牛的汉诺塔
题目大意:给你n个盘子,输出移动次数(A-B,A-C,B-A,B-C,C-A,C-B)以及所有的次数:
示例
输入
3
输出
A->B:1 A->C:3 B->A:1 B->C:1 C->A:0 C->B:1 SUM:7
方法一:打表找规律。。。。嘿嘿嘿,这个公式可不好推,不建议,不过它跑得非常快。。。时间复杂度O(1)
规律:
A->B:0,1,1,4,4,15,15,58,58
A->C:1,1,3,3,9,9,31,31,117,117
B->A:0,0,1,1,6,6,27,27,
B->C:0,1,1,4,4,15,15,58,58
C->A:0,2,2,12,12,54,54,224,224
C->B:0,0,1,1,6,6,27,27
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll qick(ll a,ll b) { ll ans=1; while (b){ if (b&1) ans*=a; a*=a; b>>=1; } return ans; } int main(int argc, char const *argv[]) { int n; scanf ("%d",&n); ll sum=1ll<<n; sum--; printf("A->B:"); ll nb=(n-1)/2; if ((n-1)%2==0) nb--; if (n==1) printf("0\n"); else printf("%lld\n",(3*nb+1+qick(2,2*nb+3))/9); printf("A->C:"); nb=n/2; if (n%2==0) nb--; printf("%lld\n",(qick(4,nb+1)+6*nb+5)/9); printf("B->A:"); nb=n/2; if (n%2==0) nb--; printf("%lld\n",(qick(4,nb+1)-3*nb-4)/9); printf("B->C:"); nb=(n-1)/2; if ((n-1)%2==0) nb--; if (n==1) printf("0\n"); else printf("%lld\n",(3*nb+1+qick(2,2*nb+3))/9); printf("C->A:"); nb=(n-1)/2; if ((n-1)%2==0) nb--; printf("%lld\n",2*(qick(4,nb+1)-3*nb-4)/9); printf("C->B:"); nb=n/2; if (n%2==0) nb--; printf("%lld\n",(qick(4,nb+1)-3*nb-4)/9); printf("SUM:%lld\n",sum); return 0; }
方法二:暴搜加个记忆化就完事了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; struct node { ll val[6]; node(){memset(val,0,sizeof val);} }; node dp[4][4][4][100]; int vis[4][4][4][100]; void solve(int x,int y,node &a) { if(x==0&&y==1) a.val[0]++; else if(x==0&&y==2) a.val[1]++; else if(x==1&&y==0) a.val[2]++; else if(x==1&&y==2) a.val[3]++; else if(x==2&&y==0) a.val[4]++; else if(x==2&&y==1) a.val[5]++; } node deal(node a,node b) { node ans; for (int i=0; i<6; i++) ans.val[i]=a.val[i]+b.val[i]; return ans; } node dfs(int n,int a,int b,int c) { if (vis[a][b][c][n]) return dp[a][b][c][n]; if (n==1){ solve(a,c,dp[a][b][c][n]); vis[a][b][c][n]=1; return dp[a][b][c][n]; } node tmp; tmp=deal(tmp,dfs(n-1,a,c,b)); solve(a,c,tmp); tmp=deal(tmp,dfs(n-1,b,a,c)); vis[a][b][c][n]=1; return dp[a][b][c][n]=tmp; } int main(int argc, char const *argv[]) { int n; scanf("%d",&n); node ans=dfs(n,0,1,2); printf("A->B:%lld\n",ans.val[0]); printf("A->C:%lld\n",ans.val[1]); printf("B->A:%lld\n",ans.val[2]); printf("B->C:%lld\n",ans.val[3]); printf("C->A:%lld\n",ans.val[4]); printf("C->B:%lld\n",ans.val[5]); printf("SUM:%lld\n",(1ll<<n)-1); return 0; }
J.牛牛的宝可梦Go
题目大意:n个城市(<=200),m条公路(<=10000)每条长度为1,有k个宠物,(<=1e5)将会在时间$t$,地点$pos$出现,其攻击力为$val$请问他能得到的最大攻击为多少?
示例
输入
3 2 1 2 2 3 3 1 1 5 2 3 10 3 2 1
输出
11
我们可以先考虑暴力,枚举每一个宠物,那么第i个宠物肯定是由前i-1个宠物转移过来的,也就是说$dp[i]=max(dp[i],dp[j]+val)$那么复杂度就是$O(K^{2})$然后就直接爆表了。但是我们可以知道的是只有200个城市,那么也就是说一次最多往前拓展200个,那么200个之后的我们直接用前缀最大值维护就好了。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; typedef long long ll; const ll inf=1e18; ll dis[250][250]; ll dp[mac]; struct node { int t,pos; ll val; bool operator <(const node &a)const{ return t<a.t; } }a[mac]; int main(int argc, char const *argv[]) { int n,m; scanf ("%d%d",&n,&m); memset(dis,0x3f,sizeof dis); for (int i=1; i<=n; i++) dis[i][i]=0; for (int i=1; i<=m; i++){ int u,v; scanf("%d%d",&u,&v); dis[u][v]=dis[v][u]=1; } int q; scanf("%d",&q); for (int i=1; i<=q; i++){ int t,p,val; scanf("%d%d%d",&t,&p,&val); a[i]=node{t,p,val}; } for (int k=1; k<=n; k++) for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); sort(a+1,a+1+q); for (int i=1; i<=q; i++) dp[i]=-inf; a[0].t=0;a[0].pos=1; ll tmp=0; ll ans=0; for (int i=1; i<=q; i++){ if (i>200) { tmp=max(tmp,dp[i-200]); dp[i]=a[i].val+tmp; } for (int j=1; j<=min(i,200); j++){//[i-200,i] if (a[i].t-a[i-j].t>=dis[a[i].pos][a[i-j].pos]) dp[i]=max(dp[i],dp[i-j]+a[i].val); } ans=max(ans,dp[i]); } printf("%lld\n",ans); return 0; }