蓝桥杯14届省B
蓝桥杯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;
else
r=mid;
}
cout<<r;//左区间
l=0,r=1e9+1;
while (l+1!=r){
int mid=l+r>>1;
if (judge2(mid))
l=mid;
else
r=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)\)

浙公网安备 33010602011771号