寒假训练第五周
寒假训练第五周
第一天
蓝桥杯训练赛
G.左孩子右兄弟
大意:
将多叉树通过左孩子有兄弟表示法转化成二叉树,问高度最高是多少。
思路:
最大高度=父节点孩子数+以它的孩子为父节点的子树的最大高度
代码:
#include<iostream>
#include<vector>
using namespace std;
vector<int> f[100050];//f[i]容器中装的是以i结点为父亲的所有孩子结点
int dfs(int node) {
int count = 0;//存储每一层的孩子的最大孩子数目
if (f[node].size() == 0) return 0; //递归出口, 似乎也可以不用这一句,到最后一层 count和f[node].size()都为0
for (int i = 0; i < f[node].size(); i++) {
count = max(count, dfs(f[node][i]));//求结点孩子的最深层数
}
return count + f[node].size();//递归出口,count为node结点孩子的最大孩子数目,f[node].size()是node这一层兄弟数目
}
int main() {
int n;//n为结点个数
int t;//
cin >> n;
for (int i = 2; i <= n; i++) {
cin >> t;
f[t].push_back(i);// i结点的父亲是x结点 所以利用f[t].size()可以求得t结点的孩子数目
}
cout << dfs(1);
return 0;
}
C.砝码称重
大意:
给定n个砝码的重量,计算出可以称出多少种不同的重量。
思路:
背包
dp[i][j]表示前i个砝码能否称出j的重量,1为能,0为不能。
3种转移
1:dp[i-1][j],前i个砝码能称出j的重量。
2:dp[i-1][j+w[i]],前i个砝码能称出j+w[i]的重量,则把当前砝码放在物品那一边,即可称出当前重量。
3:dp[i-1][j-w[i]],前i个砝码能称出j-w[i]的重量,则把当前砝码放在砝码那一边,即可称出当前重量。
代码:
#include <iostream>
#include <math.h>
using namespace std;
int dp[101][100001] = {0};
int n,sum,ans;
int main()
{
cin >> n;
int w[n+1];
for(int i = 1;i <= n;i++)
{
cin >> w[i];
sum += w[i]; // 总重
}
dp[0][0] = 1; // 初始状态
for(int i = 1;i <= n;i++)
{
for(int j = 0;j <= sum;j++)
{
dp[i][j] = max(dp[i-1][j],max(dp[i-1][j+w[i]],dp[i-1][abs(j-w[i])]));
}
}
for(int i = 1;i <= sum;i++)
{
if(dp[n][i])
{
ans++;
}
}
cout << ans;
return 0;
}
第二天
补题
F.阿宁的二进制
大意:
给定一个数组,和k次操作,每次操作选择一个数换成该数在二进制下1的个数。
问k次操作后最大的数最小值是多少。
思路:
对数进行操作就是将数变小,1除外。
每次取最大的数进行操作。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 3 + 2e5;
int n, q;
int a[N], b[N * 5], t;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n; ++i) {
while (a[i] > 1) {
// 不用存1
b[++t] = a[i];
// __builtin_popcount就是求二进制中1的个数
a[i] = __builtin_popcount(a[i]);
}
}
sort(b + 1, b + t + 1);
while (q--) {
int k;
cin >> k;
if (k >= t) {
cout << 1 << endl;
} else {
// 升序排序,b[t-k]就是第k+1大
cout << b[t - k] << endl;
}
}
return 0;
}
K.阿宁大战小红
大意:
给定一个正整数n,两个操作,+1或除以2向下取整。
当前数不能为0或曾出现过的数,否则输,问谁胜。
思路:
dfs搜索,
博弈必胜态必败态,
若当前数变成2n,则平局(一直+1操作)
当前数x,曾经最小y,最大z,
当前这个人p除以2下取整,p一定能赢,否则不如选+1.
当x+1<z,进行+1操作,
当x除以2向下取整<y,并且>0,可进行除2操作。
代码:
#include <bits/stdc++.h>
using namespace std;
map<int, bool> mp;
int n;
int get(int x, int y, int z) {
return x * 1000000 + y * 1000 + z;
}
// (x,y,z)
// true 表示 (x,y,z) 必胜
// false 表示 (x,y,z) 必败
bool dfs(int x, int y, int z) {
// 将(x,y,z)映射到一个正数
int id = get(x, y, z);
if (mp.count(id)) {
// 记忆化搜索
// 如果搜索过了,就不要再搜下去
return mp[id];
}
// 没搜索过
// 在mp创建一个bool
// ans是mp[id]的一个引用,默认false
bool& ans = mp[id];
if (x + 1 < z && !dfs(x + 1, y, z)) {
// (x + 1, y, z)是必败态
// 那么(x, y, z)就是必胜态
return ans = true;
}
if (x / 2 && x / 2 < y && !dfs(x / 2, x / 2, y)) {
// (x/2, x/2, y)是必败态
// 那么(x, y, z)就是必胜态
return ans = true;
}
// 不能通过两种操作得到必败态
// 那么(x,y,z)就是必败态
return ans;
}
int main() {
cin >> n;
for (int i = n; i < 2 * n; ++i) {
// 除以2后,当前值是i/2最小值是i/2,最大值是初始值n
if (i / 2 && !dfs(i / 2, i / 2, n)) {
if ((i - n) % 2 == 0) {
cout << "ning" << endl;
} else {
cout << "red" << endl;
}
return 0;
}
}
cout << "draw" << endl;
return 0;
}
外卖店优先级
大意:
n家外卖店初始优先级为0,每过一个时间单位没有订单优先级-1,最小为0,有订单+2.
某时刻优先级>5,加入优先缓存,<=3,清除出缓存。
问T时刻有几家店在优先缓存中。
思路:
对订单按分店铺按时间先后排序,
记录下上次订单的时间,
用当前时间-间隔
代码:
#include <iostream>#include <algorithm>
using namespace std;
//顺带复习一下结构体的排序hhhint m,n,t;//store[id]第id号店上一次有的订单//flag[id]存储第id号店在不在优先缓存中//value[id]存储第id号店的得分
int store[100010],flag[100010],value[100010];
struct node{
int time,id;}a[100010];
bool cmp(node a , node b){
if(a.id==b.id)
return a.time<b.time;
return a.id<b.id;}
int main(){
cin>>n>>m>>t;
for(int i=0;i<m;i++){
cin>>a[i].time>>a[i].id;
}
sort(a,a+m,cmp);
for(int i=0;i<m;i++){
int tt=a[i].time,id=a[i].id;
//如果当前的订单不等于上一次的订单,则减去它们之间的间隔
//因为如果没有订单,则每个时刻减1
if(tt!=store[id])
value[id]-=tt-store[id]-1;
//value的值要大于等于0
value[id]=max(0,value[id]);
if(value[id]<=3)
flag[id]=0;
value[id]+=2;
if(value[id]>5)
flag[id]=1;
store[id]=tt;
}
for(int i=1;i<=n;i++){
//如果这个店的最后一个订单不是第 t 分钟的
//就减去最后一个订单的时间到 t 时间之间应该减去的
if(store[i]<t){
value[i]-=t-store[i];
if(value[i]<=3)
flag[i]=0;
}
}
int ans=0;
for(int i=0;i<=n;i++)
if(flag[i])
ans++;
cout<<ans;
return 0;}
修改数组
大意:
大小为n的数组,从下标2开始,若元素与之前的元素相同,则+1。问最后的数组。
思路:
vis[]数组记录该数是否出现过,
并查集,对相邻数判断,进行压缩,提高搜索效率。
代码:
#include<bits/stdc++.h>
using namespace std;
#define MAXA 1100005
int a,N,fa[MAXA],vis[MAXA];
int get_father(int x) //查找祖先
{
if(x==fa[x]) return x;
return fa[x] = get_father(fa[x]);
}
int main()
{
cin>>N;
for(int i=1;i<MAXA;i++) fa[i] = i; //并查集初始化
for(int i=0;i<N;i++)
{
cin>>a;
int ans=vis[a]?get_father(a)+1:a; //判断是否已被访问,已访问的话占取下一个位置
cout<<ans<<' '; //输出ans
vis[ans] = 1; //ans为已访问
if(ans!=1&&vis[ans-1]) fa[ans-1] = ans; //表示ans-1的父亲是ans
if(vis[ans+1]) fa[ans] = ans+1; //表示ans的父亲是ans+1
}
return 0;
}
第三天
cf
H.Perfect Array
大意:
对空数组进行以下操作:
第i个操作选择任意一个在1-i的整数并把它插入到第i个位置上。
给定一个数组判断它是否由以上操作得到,若是,输出选择数的原顺序。
思路:
逆向思维,
最后插入的一定是数值与位置对应的,
数组下标从0开始,故从右往左遍历a[i]==i+1的,加入ans数组,最后倒序输出。
从左往右会影响后面数的对应,故从右往左。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+5;
vector<int> v;
vector<int> ans;
int main()
{
int n;cin>>n;
for(int i=0;i<n;i++){
int t;cin>>t;
v.push_back(t);
}
bool g= false;
while(v.size()) {
bool f= false;
for (int i = v.size() - 1; i >= 0; i--) {
if (v[i] == i + 1) {
ans.push_back(v[i]);
v.erase(v.begin() + i);
f= true;
break;
}
}
if(!f){
g= true;
break;
}
}
if(g){
cout<<"NO";
return 0;
}
cout<<"YES"<<endl;
for(int i=ans.size()-1;i>=0;i--){
cout<<ans[i];
if(i!=0)cout<<' ';
}
return 0;
}
第四天
二分和三分
二分模板(单调函数)
int main()
{
int num[9] = {1,2,3,3,4,4,5,5,7};//测试数据
int L = 0,R = 8;
int target = 5;
//求小于等于target的最后一个数的下标
while(L < R)
{
int mid = L + R + 1 >> 1;
if(num[mid] <= target) L = mid;
else R = mid - 1;
}
//求大于等于target的第一个数的下标
while(L < R)
{
int mid = L + R >> 1;
if(num[mid] >= target) R = mid ;
else L = mid + 1;
}
二分STL
lower_boudnd //返回第一个大于等于value的迭代器
upper_bound //返回第一个大于value的迭代器
binary_search //如果存在value,返回true,否则返回false
三分模板(单峰函数)
int sf(int l,int r){
while(l<r-1){
int mid=l+r>>1;
int mmid=mid+r>>1; if(f(mid)>f(mmid))r=mmid; else l=mid;
}
return f(l)>f(r)?l:r;}
第五天
cf
C.KK算日期
大意:
判断闰年,无公元0年,公元前年份取绝对值
思路:
无公元0年,
公元前年份--再判断
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=3e4+10;
priority_queue<ll,vector<ll>,greater<ll>> xz;
priority_queue<ll,vector<ll>,less<ll>> df;
priority_queue<ll,vector<ll>,greater<ll>> xf;
priority_queue<ll,vector<ll>,less<ll>> dz;
int main()
{
ll n;scanf("%lld",&n);
ll a;
int cntf=0;
for(ll i=1;i<=n;i++){
scanf("%lld",&a);
if(a>0){
xz.push(a);
dz.push(a);
}
else {
df.push(a);
xf.push(a);
cntf++;
}
}
if(cntf==0||cntf%2==0) {
if(xz.size())
printf("%lld",xz.top());
else {
printf("%lld",xf.top());
}
}
else {
printf("%lld",df.top());
}
return 0;
}
第六天
错排问题
错排公式
f [ 0 ] = 1 , f [ 1 ] = 0 , f [ 2 ] = 1
f[ n ] = ( n − 1 ) ∗ ( f [ n − 1 ] + f [ n − 2 ] )
全错的概率 = 全错数 / 全部情况。
全部情况就是N的阶乘。
全错数:
1.将第N个数放在k位置,有n-1种。
2.将第k个位置的数拿出来考虑,如果第k个数放在第N个位置,则剩下就是n-2个数全部排错情况;
如果第k个数不是放在第N个位置,则就是n-1个数全部排错情况。
计算多边形面积
多边形的面积公式为s=1/2[(x1y2-x2y1)+(x2y3-x3y2)+...... +(XkYk+1-Xk+1Yk)+...+(Xny1-x1*Yn) ]