一、日期统计
题目

个人见解
暴力遍历即可,本题的数组初始化比较多,牺牲空间让逻辑简单化了。
AC代码
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[105];
ll t[4]={2,0,2,3};
ll ans=0;
ll days[14]={0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
void solve()
{
ll cur=0;
ll index;
for(ll i=1;i<=100;i++)
{
cin>>a[i];
if(a[i]==t[cur]&&cur<4)
{
cur++;
if(cur==4)index=i;
}
}
for(ll month=1;month<=12;month++)
{
for(ll day=1;day<=days[month];day++)
{
ll n[]={month/10,month%10,day/10,day%10};
ll cnt=0;
for(ll i=index;i<=100;i++)
{
if(n[cnt]==a[i])cnt++;
if(cnt==4)
{
ans++;
break;
}
}
}
}
cout<<ans;
}
int main( )
{
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
二、01串的熵
题目

个人见解
数学题:

不过依然有收获,至少告诉我们应该重视 < c m a t h cmath cmath> 头文件下的数学函数,后续 up 主会整理常用的数学函数,感兴趣可以持续关注哦。
本题另一个值得注意的点其实是相等浮点数的判定,我们可以知道两个浮点数几乎不可能相等,那么本题的两个浮点数相等的判断依据,其实就在于这两个数相减的绝对值是否小于一个规定的范围,那么该怎么去找这个范围呢?
if(fabs(ans-s)<1e-4)别看我这里写的是 1 e − 4 1e-4 1e−4 ,但其实这也是试出来的结果。
因为是填空题,所以一个个试不失为一个好办法,其实我第一次试的数字为 1 e − 5 1e-5 1e−5 ,在这个范围下没有合适的数字,因此增大了范围,在小于 1 e − 4 1e-4 1e−4 的范围下只有一个数字,当然,符合条件。那么如果你试的范围太大,就会输出很多的数字;如果试的太精细,可能会导致没有输出,只需要根据结果修改就好。
AC代码
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
void solve()
{
ll len=23333333;
double s=11625907.5798;
for(ll i=1;i<=len/2;i++)
{
double ans=i*(-1.0*i/len)*log2(1.0*i/len)+(len-i)*(-1.0*(len-i)/len)*log2(1.0*(len-i)/len);
if(fabs(ans-s)<1e-4)
{
cout<<i;
return;
}
}
}
int main( )
{
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
三、冶炼金属
题目


个人见解
其实一眼看过去第一想到的不是二分哈哈,因为给的样例说明太明显了,可以通过数字比求出来,而且复杂度还只是 O ( n ) O(n) O(n)。
当然二分的代码我也写出来了,大家也可以自己看。
AC代码
暴力遍历
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll maxs=LONG_LONG_MAX,mins;
ll n;
pair<ll,ll> p[10005];
void solve()
{
cin>>n;
for(ll i=1;i<=n;i++)
{
cin>>p[i].first>>p[i].second;
maxs=min(maxs,p[i].first/p[i].second);
mins=max(mins,p[i].first/(p[i].second+1));
}
cout<<mins+1<<" "<<maxs;
}
int main( )
{
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
二分查找
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll maxs=LONG_LONG_MAX,mins;
ll n;
pair<ll,ll> p[10005];
bool check_max(ll mid)
{
for(ll i=1;i<=n;i++)
{
if(p[i].first/mid<p[i].second)return false; //mid太大了
}
return true;
}
bool check_min(ll mid)
{
for(ll i=1;i<=n;i++)
{
if(p[i].first/mid>p[i].second)return false; //mid太小了
}
return true;
}
void solve()
{
cin>>n;
for(ll i=1;i<=n;i++)
{
cin>>p[i].first>>p[i].second;
}
ll l=0,r=1e9+1,mid;
while(l+1!=r)
{
mid=l+r>>1;
if(check_max(mid))l=mid;
else r=mid;
}
maxs=l,l=0,r=1e9+1;
while(l+1!=r)
{
mid=l+r>>1;
if(check_min(mid))r=mid;
else l=mid;
}
mins=r;
cout<<mins<<" "<<maxs;
}
int main( )
{
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
四、飞机降落
题目


个人见解
DFS板子题,唯一需要注意的点在于当前飞机的最早降落时间是取上一架飞机的降落时间和当前飞机的到达时间的最大值,即 t 当前飞机开始降落时间 = m a x ( t 上一架飞机完全降落 , t 当前飞机到达机场 ) t_{当前飞机开始降落时间}=max(t_{上一架飞机完全降落},t_{当前飞机到达机场}) t当前飞机开始降落时间=max(t上一架飞机完全降落,t当前飞机到达机场)。
AC代码
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n;
ll t[15]; //在t时刻到达机场上空
ll d[15]; //持续盘旋d时长
ll l[15]; //降落时间
ll vis[15];
ll ans=0;
ll minv=LONG_LONG_MAX; //最快降落时间
ll ind; //对应下标
void dfs(ll step,ll prev)
{
if(step==n)
{
ans=1;
return;
}
for(ll i=1;i<=n;i++)
{
if(vis[i])continue;
ll cur=t[i]+d[i]; //当前飞机的最晚降落时间
if(cur<prev)return;
else
{
vis[i]=1;
dfs(step+1,max(prev,t[i])+l[i]);
vis[i]=0;
}
}
}
void solve()
{
memset(vis,0,sizeof(vis));
ans=0;
cin>>n;
for(ll i=1;i<=n;i++)
{
cin>>t[i]>>d[i]>>l[i];
}
dfs(0,0);
cout<<(ans==1?"YES":"NO")<<endl;
}
int main( )
{
ios::sync_with_stdio(0);
cin.tie(0);
ll T;
cin>>T;
while(T--)
{
solve();
}
return 0;
}
五、接龙数列
题目


个人见解
简单的动态规划题,但是依然有需要注意的点。
- 对于一个不知道长度的数字,先取出最高位数字,不妨将其当做字符串输入,那么最高位数字就是 s . f r o n t ( ) − ′ 0 ′ s.front()-'0' s.front()−′0′ 。
- 对于最高位和最低位相等的数字一定要注意,可能会发生一个数字增加两次长度的情况
cpp
if(dp[last]==0&&first!=last)dp[last]=1;
dp[last]=max(dp[first]+1,dp[last]);
当面对 22 22 22 这样的数字时,如果去掉 f i r s t ! = l a s t first!=last first!=last 的判断,那么这里为让 l a s t last last 结尾的子序列长度 + 1 +1 +1 ,在下面的动态转移方程中又会让以 f i r s t first first 结尾的子序列长度 + 1 +1 +1,因为 f i r s t = = l a s t first==last first==last ,那么就会导致以 2 2 2 结尾的子序列加了两次。
AC代码
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[10]; //记录以i结尾的最长子序列长度
void solve()
{
ll n;
cin>>n;
for(ll i=1;i<=n;i++)
{
string s;
cin>>s;
ll first=s.front()-'0',last=s.back()-'0';
if(dp[last]==0&&first!=last)dp[last]=1; //这里为了防止同一个数字加两遍
dp[last]=max(dp[first]+1,dp[last]);
}
ll maxv=*max_element(dp,dp+10);
cout<<n-maxv;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
六、岛屿个数
题目



个人见解
挺唬人的一题,刚看完题就在想到底怎么处理题目说的环,单一个岛屿倒是很好深搜得到,然后稀里糊涂的用 并查集 + D F S DFS DFS 写了100多行😭。
但其实转换一下思路就好写了,我们应该去深搜的是外海,也就是包围岛屿的海,这样就直接忽略了判断子岛屿的过程。可以在题目给的点周围再围一圈,这样 ( 0 , 0 ) (0,0) (0,0) 就必定是外海了。
因此对 ( 0 , 0 ) (0,0) (0,0) 进行深搜,一旦碰到岛屿就将它及连通的岛屿全部标记为访问过,同时 a n s ans ans++ 。
AC代码
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll g[55][55];
ll vis[55][55];
ll ans=0;
ll dx[]={ 0,0,1,-1,-1,-1, 1,1};
ll dy[]={-1,1,0, 0, 1,-1,-1,1};
void dfsl(ll x,ll y) //深搜岛屿
{
vis[x][y]=1;
for(ll i=0;i<4;i++)
{
ll nx=x+dx[i],ny=y+dy[i];
if(nx<0||nx>=55||ny<0||ny>=55||vis[nx][ny])continue;
if(g[nx][ny]==1)dfsl(nx,ny);
}
}
void dfsh(ll x,ll y) //深搜外海
{
vis[x][y]=1;
g[x][y]=-1;
for(ll i=0;i<8;i++)
{
ll nx=x+dx[i],ny=y+dy[i];
if(nx<0||nx>=55||ny<0||ny>=55||vis[nx][ny])continue;
if(g[nx][ny]==0)dfsh(nx,ny); //依然是外海
else if(g[nx][ny]==1) //岛屿
{
dfsl(nx,ny);
ans++;
}
}
}
void solve()
{
ll m,n;
ans=0;
cin>>m>>n;
for(ll i=0;i<55;i++)
{
for(ll j=0;j<55;j++)
{
g[i][j]=0,vis[i][j]=0;
}
}
for(ll i=1;i<=m;i++)
{
for(ll j=1;j<=n;j++)
{
char c;
cin>>c;
g[i][j]=c-'0';
}
}
dfsh(0,0);
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
ll T=1;
cin>>T;
while(T--)
{
solve();
}
return 0;
}
七、子串简写
题目


个人见解
前缀和。
s u m b [ i ] sumb[i] sumb[i] 代表从 s . l e n g t h ( ) − 1 s.length()-1 s.length()−1 到 i i i 总共有多少个 c 2 c_2 c2。
AC代码
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll sumb[500005]={};
void solve()
{
ll k;
string s;
char c1,c2;
cin>>k;
cin>>s>>c1>>c2;
for(ll i=s.length()-1;i>=0;i--)
{
sumb[i]=sumb[i+1]+(s[i]==c2?1:0);
}
ll ans=0;
for(ll i=0;i<s.length();i++)
{
if(s[i]==c1&&(i+k-1)<s.length())ans+=sumb[i+k-1];
}
cout<<ans;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
八、整数删除
题目


个人见解
优先队列+数组模拟的链表。
很好的一题,用数组实现链表,需要考虑如何处理堆中旧值的问题,还要注意将更新后的新值压入堆中,细节还是比较多的。
由于题目要求每次删除第一个值最小的元素,那么 l a m b d a lambda lambda 表达式的逻辑就不能太简单了,这是我最开始写的:
cpp
auto cmp=[](pair<ll,ll> p1,pair<ll,ll> p2){ //first:元素值,second:元素位置
return p1.first>p2.first;
};
好吧,确实有点呆了,我默认按顺序存入堆,取的时候也会按顺序取第一个最小值,其实不然,遇见相同大小值时,堆顶元素下标的优先级是不可预计的。
写出这样简单的表达式,代价就是只能过 90 % 90\% 90% 的数据,然后独自调试半天😭,数据还是仁慈哩。
AC代码
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=500005;
ll k,n;
ll L[N],R[N]; //当前结点左节点和右节点的下标
ll cur[N];
bool used[N]; //通过索引判断元素是否已经被删除
void solve()
{
cin>>n>>k;
auto cmp=[](pair<ll,ll> p1,pair<ll,ll> p2){ //first:元素值,second:元素位置
if(p1.first!=p2.first)return p1.first>p2.first;
else return p1.second>p2.second;
};
priority_queue<pair<ll,ll>,vector<pair<ll,ll>>,decltype(cmp)> q(cmp);
for(ll i=1;i<=n;i++)
{
cin>>cur[i];
L[i]=i-1,R[i]=i+1;
q.push({cur[i],i});
}
L[1]=-1,R[n]=-1;
for(ll i=1;i<=k;i++)
{
auto [val,pos]=q.top();
q.pop();
while(used[pos]==1 || val!=cur[pos]) //当前数据已被删除或者为旧值
{
pair<ll,ll> p=q.top();
val=p.first,pos=p.second;
q.pop();
}
used[pos]=1;
ll left=L[pos],right=R[pos]; //左右节点下标
if(L[pos]>0)
{
cur[left]+=cur[pos];
R[left]=R[pos]; //更新左节点的右节点
q.push({cur[left],left}); //更新堆内的左节点数据
}
if(R[pos]>0)
{
cur[R[pos]]+=cur[pos];
L[R[pos]]=L[pos]; //更新右节点的左节点
q.push({cur[right],right}); //更新堆内右节点数据
}
}
for(ll i=1;i<=n;i++)
{
if(!used[i])cout<<cur[i]<<" ";
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
九、景区导游
题目



个人见解
L C A LCA LCA (最近公共祖先) 。
无脑 D F S DFS DFS 会超时。
AC代码
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{
ll v,w;
};
const ll N=1e5+5;
vector<node> e[N];
ll dep[N]; //节点深度
ll fa[N][20]; //ST表:fa[u][i]表示u的2^i级祖先
ll dis[N]; //根节点到点u的路径总权值
ll a[N]; //原来的路径
void dfs(ll u,ll father) //创建ST表
{
dep[u]=dep[father]+1;
fa[u][0]=father;
for(ll i=1;i<=19;i++)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(auto& no:e[u])
{
ll v=no.v,w=no.w;
if(v!=father)
{
dis[v]=dis[u]+w;
dfs(v,u);
}
}
}
ll lca(ll u,ll v) //返回最近公共祖先
{
if(dep[u]<dep[v])swap(u,v);
//先跳到同一层
for(ll i=19;i>=0;i--)
{
if(dep[fa[u][i]]>=dep[v])
{
u=fa[u][i];
}
}
if(u==v)return v;
//再跳到LCA的下一层
for(ll i=19;i>=0;i--)
{
if(fa[u][i]!=fa[v][i])
{
u=fa[u][i],v=fa[v][i];
}
}
return fa[u][0];
}
ll get_dis(ll u,ll v) //返回两点间的距离
{
if(u==0||v==0)return 0;
return dis[u]+dis[v]-2*dis[lca(u,v)];
}
void solve()
{
ll n,k;
cin>>n>>k;
for(ll i=1;i<=n-1;i++)
{
ll u,v,t;
cin>>u>>v>>t;
e[u].push_back({v,t}),e[v].push_back({u,t});
}
for(ll i=1;i<=k;i++)
{
cin>>a[i];
}
dfs(1,0);
ll ans=0;
for(ll i=1;i<=k;i++)
{
if(i>1)
{
ans+=get_dis(a[i-1],a[i]);
}
}
for(ll i=1;i<=k;i++)
{
ll delen=0; //删掉的长度
if(i>1)delen+=get_dis(a[i-1],a[i]);
if(i<k)delen+=get_dis(a[i],a[i+1]);
ll addlen=0; //增加的长度
if(i>1&&i<k)addlen+=get_dis(a[i-1],a[i+1]);
cout<<ans+addlen-delen<<" ";
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
十、砍树
题目


个人见解
树上差分+lca。
AC代码
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N = 100005;
struct Edge
{
ll to, id; //边的终点和序号
};
vector<Edge> e[N];
ll fa[N][20], dep[N];
ll edge_id[N]; // 记录点 u 连向父亲的那条边的原始编号
ll cnt[N]; // 差分数组:统计每条边被覆盖的次数
ll n, m, ans = -1;
// 预处理LCA和点边映射
void dfs_pre(ll u, ll f, ll d, ll id) {
dep[u] = d;
fa[u][0] = f;
edge_id[u] = id; // 点u代表了连接(u,f)的那条边
for (ll i = 1; i < 20; i++) {
fa[u][i] = fa[fa[u][i - 1]][i - 1];
}
for (auto &edge : e[u]) {
if (edge.to != f) {
dfs_pre(edge.to, u, d + 1, edge.id);
}
}
}
ll lca(ll u, ll v)
{
if (dep[u] < dep[v]) swap(u, v);
for (ll i = 19; i >= 0; i--)
{
if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
}
if (u == v) return u;
for (ll i = 19; i >= 0; i--)
{
if (fa[u][i] != fa[v][i])
{
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
// 自底向上累加差分值
void dfs_sum(ll u, ll f)
{
for (auto &edge : e[u])
{
ll v = edge.to;
if (v == f) continue;
dfs_sum(v, u); // 先递归子树
// 累加完后,cnt[v]就是边(v, u)被所有路径覆盖的总次数
if (cnt[v] == m) {
ans = max(ans, edge.id); // 如果等于m,说明断开此边可断开所有点对
}
cnt[u] += cnt[v]; // 将子树的覆盖次数往上传给父亲
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
if (!(cin >> n >> m)) return 0;
for (ll i = 1; i < n; i++) {
ll u, v;
cin >> u >> v;
e[u].push_back({v, i});
e[v].push_back({u, i});
}
// 从1号点开始,假设它是根
dfs_pre(1, 0, 1, -1);
// 处理 m 对点,进行树上边差分
for (ll i = 0; i < m; i++) {
ll u, v;
cin >> u >> v;
ll l = lca(u, v);
cnt[u]++; // 路径起点
cnt[v]++; // 路径终点
cnt[l] -= 2; // LCA 处抵消,防止流量流向 LCA 以上的边
}
dfs_sum(1, 0);
cout << ans << endl;
return 0;
}