蓝桥杯14届省赛B组
A:
int a[105];
int day[]={0,31,28,31,30,31,30,31,31,30,31,30,31};//记录每个月有多少天
set<int> st;//记录不重复的日期void check(int mm,int dd){if (mm>12||mm<1||dd<1||dd>day[mm]) return;else st.insert(mm*100+dd);//st存日期
}void judge(int v){for (int m=v+1;m<=n;m++)for (int mm=m+1;mm<=n;mm++)for (int d=mm+1;d<=n;d++)for (int dd=d+1;dd<=n;dd++)check(a[m]*10+a[mm],a[d]*10+a[dd]);//枚举月份和年份
}void solve(void){int n=100;for (int i=1;i<=n;i++) cin>>a[i];for (int i=1;i<=n;i++)if (a[i]==2)for (int j=i+1;j<=n;j++)if (a[j]==0)for (int u=j+1;u<=n;u++)if (a[u]==2)for (int v=u+1;v<=n;v++)if (a[v]==3)//选出2023年judge(v);//从v位开始选月份和日期cout<<st.size();//输出自动去重后的set的大小
}
超级无敌暴力题,别把循环写错就行
不重复的日期用 set
存,将月份和日期看作一个四位数记录即可
B:
const double hs=11625907.5798;
const int n=23333333;bool judge(int x){int cnt0=x,cnt1=n-x;double sum=0;sum+=(1.0*cnt0*cnt0)/n * log(1.0*n/cnt0)/log(2) + (1.0*cnt1*cnt1)/n * log(1.0*n/cnt1)/log(2);return sum<hs;
}void solve(void){int l=0,r=(n+1)/2;while (l+1!=r){int mid=l+r>>1;if (judge(mid))l=mid;else r=mid;}cout<<r<<endl;
}
设 \(1,0\) 的个数分别为 \(cnt_1,cnt_0\),原公式可以变形为
不难看出这个函数根据 \(1,0\) 的数量对称分布,题目给定的 \(0\) 数量小于 \(1\) ,所以在区间 \([1,n/2]\) 进行二分
同时这个区间内的 \(H(S)\) 满足单调,套一个整数二分模板就能求得
C:
数学推导:
const int N=1e4+10;int a[N],b[N];
int n;void solve(void){cin>>n;for (int i=1;i<=n;i++)cin>>a[i]>>b[i];int mmin=0,mmax=1e9;for (int i=1;i<=n;i++){mmin=max(a[i]/(b[i]+1)+1,mmin);mmax=min(a[i]/b[i],mmax);}cout<<mmin<<' '<<mmax;
}
最小情况是,考虑如果要能多造一个 \(B\) ,那么转化率会如何变化
此时的 \(V^\prime\) 就是所有记录中,不满足条件的最大情况,所以 \(V_{min}=V^\prime +1\)
同理,考虑所有记录中的转化率
此时的 \(V^\prime\) 就是所有记录中,满足条件的最大情况,所以 $V_{max}=V^\prime $
二分法:
const int N=1e4+10;int a[N],b[N];
int n;//两次二分区别在judge函数的返回值bool judge1(int x){for (int i=1;i<=n;i++){if (a[i]/x>b[i])return 1;if (a[i]/x<b[i])return 0;}return 0;//如果恰好等于,那么缩小右区间
}bool judge2(int x){for (int i=1;i<=n;i++){if (a[i]/x>b[i])return 1;if (a[i]/x<b[i])return 0;}return 1;//如果恰好等于,那么缩小左区间
}void solve(void){cin>>n;for (int i=1;i<=n;i++)cin>>a[i]>>b[i];int l=0,r=1e9+1;while (l+1!=r){int mid=l+r>>1;if (judge1(mid))l=mid;elser=mid;}cout<<r;//左区间l=0,r=1e9+1;while (l+1!=r){int mid=l+r>>1;if (judge2(mid))l=mid;elser=mid;}cout<<' '<<l;//右区间
}
两次二分,第一次二分满足条件的最小情况,第二次二分满足条件的最大情况
D:
const int N=10+2;int n;
int t[N],d[N],l[N];
bool st[N];bool dfs(int x,int dt){//x表示选了几个飞机降落了,dt表示上一个飞机降落的时间if (x>n) return 1;for (int i=1;i<=n;i++){if (st[i]) continue;if (t[i]+d[i]>=dt){st[i]=1;//表示第i个飞机被选过了if (dfs(x+1,max(t[i],dt)+l[i])) return 1;st[i]=0;//dfs后恢复原来状态}}return 0;
}void solve(void){memset(st,0,sizeof st);//重置st数组cin>>n;for (int i=1;i<=n;i++)cin>>t[i]>>d[i]>>l[i];if (dfs(0,0)) cout<<"YES"<<endl;//从第0个飞机开始,上一个飞机0时刻降落else cout<<"NO"<<endl;
}
仍然是暴力做法,枚举每个飞机降顺序的全排列判断是否会冲突
写法上存在一些细节问题,DFS函数开成 bool
形式
if (x>n) return 1;//如果选完全部的飞机都没有冲突,那么回溯1,表示可以实现if (dfs(x+1,max(t[i],dt)+l[i])) return 1;//如果上一层回溯的时候为1,那么当前层立刻回溯1
E:
const int N=1e5+10;int n;
string s[N];
unordered_map<char,int> dp;void solve(void){cin>>n;for (int i=1;i<=n;i++) cin>>s[i];int ans=0;for (int i=1;i<=n;i++){dp[s[i].back()]=max(dp[s[i].back()],dp[s[i].front()]+1);ans=max(ans,dp[s[i].back()]);}cout<<n-ans;
}
动态规划的优化,删去 \(k\) 个数相当于在原来的数组中选出 \(n-k\) 个数组成一个接龙数列
考虑类似于最长上升子序列的问题,遍历一遍所有元素,将读入的整数按照字符串的形式读入
利用 unordered_map
记录以 \(x\) 结尾的数能构成的接龙数列的最长长度
状态转移方程为
其中 \(bk\) 表示当前访问的字符串 \(s_i\) 的最后一个字符,\(ft\) 表示这个字符串的第一个字符
dp[s[i].back()]=max(dp[s[i].back()],dp[s[i].front()]+1);
\(ans\) 记录能选出的接龙数组的最长长度,答案即为 \(n-ans\)
F:
const int N=55;int m,n;
char g[N][N];
bool vis[N][N];
int ans;
int dx[]={0,1,0,-1,1,-1,1,-1};
int dy[]={1,0,-1,0,1,-1,-1,1};//八连通void bfs_dy(int sx,int sy){queue<pair<int,int>> q;q.push({sx,sy});vis[sx][sy]=1;while (q.size()){auto t=q.front();q.pop();for (int i=0;i<4;i++){int tx=t.first+dx[i],ty=t.second+dy[i];if (tx<1||ty<1||tx>m||ty>n) continue;if (g[tx][ty]=='1'&& !vis[tx][ty]){q.push({tx,ty});vis[tx][ty]=1;}}}
}void bfs(int sx,int sy){queue<pair<int,int>> q;q.push({sx,sy});vis[sx][sy]=1;while (q.size()){auto t=q.front();q.pop();for (int i=0;i<8;i++){int tx=t.first+dx[i],ty=t.second+dy[i];if (tx<0||ty<0||tx>m+1||ty>n+1) continue;if (g[tx][ty]=='0'&& !vis[tx][ty]){q.push({tx,ty});vis[tx][ty]=1;}if (g[tx][ty]=='1'&& !vis[tx][ty]){bfs_dy(tx,ty);ans++;}}}
}void init(void){for (int i=0;i<N;i++)for (int j=0;j<N;j++)g[i][j]='0';memset(vis,0,sizeof vis);ans=0;
}//多个测试数据需要清空void solve(void){init();cin>>m>>n;for (int i=1;i<=m;i++)for (int j=1;j<=n;j++)cin>>g[i][j];bfs(0,0);cout<<ans<<endl;
}
BFS 洪水填充
当一块陆地上下左右四个方向联通时,这个块才联通
思路大概是 BFS 八连通的海水,如果过程中遇到了陆地,那么对这个陆地进行 BFS 四联通,同时 vis
记录访问过的位置
if (g[tx][ty]=='1'&& !vis[tx][ty]){//bfs海水时,遇到陆地,那么bfs这块陆地bfs_dy(tx,ty);ans++;//记录这个联通块后,答案数+1
}
有一个技巧就是将这个地图一开始初始化为 '0'
,全是海水,给的数据较小 \(N,M\le50\)
因为每个点被 vis
只会记录一次,所以时间复杂度为 \(O(T*M*N)\)