2023牛客寒假算法基础集训营第一场题解(A-M)
比赛链接:[2023牛客寒假算法基础集训营1]
比赛情况: 10/1310/1310/13
本场个人觉得难度排行: $$A≤C≤L≤H≤K≤D≤M≤G≤F≤E≤B≤I≤JA\le C\le L\le H\le K\le D\le M\le G\le F\le E\le B\le I\le JA\le C\le L\le H\le K\le D\le M\le G\le F\le E\le B\le I\le J$$
以下是赛时通过的代码,只A了10题,最后1h直接开摆了,还是太菜了~~
值得注意的就是千万千万不要相信题目名字!!!!!!
B,I,J 后面再补充。。。。。
这里是牛客出题人给出的难度分析:
下面我们按照题目难度顺序来讲:
A题[World Final? World Cup! (I)]
算法考察:模拟
题意:给定一个长度为10的01串,1代表获胜,0代表失败,有两个人PK,输出在第几回合决出胜负。
解:
我们直接定义a1,b1分别为在当前轮数最少获胜的次数,a2,b2为该人还可能获胜的次数,这里我们直接对每轮进行判断 (a1+a2-b1)*(b1+b2-a1)<0(a1+a2-b1)*(b1+b2-a1)<0 是否成立即可。
AC代码:
void solve() {
char s[20];
cin >> s + 1;
int a1 = 0, a2 = 5, b1 = 0, b2 = 5;
rep(i, 1, 10) {
if (i & 01) {
if (s[i] == '1')a1++;
a2--;
}
else {
if (s[i] == '1')b1++;
b2--;
}
if ((a1 + a2 - b1) * (b1 + b2 - a1) < 0) {
cout << i << endl; return;
}
}
cout << -1 << endl;
}
C题 [现在是,学术时间 (I)]
算法考察:思维
题意:分配论文发表,使得 \sum_{i=1}{n}{hi}\sum_{i=1} 最大,初始每个人论文发表数为0,如果当前论文的引用量大于发表的文章数量,则获得一点贡献,输出最大贡献。(一个人至多发表一篇)
解:
我们发现这是一个诈骗题,一开始所有人论文发表都是0,那么只要引用量大于0,贡献就会加1,所有我们直接按照原来的顺序即可。
AC代码:
void solve() {
int n;
cin >> n;
vector<int>a(n + 10);
rep(i, 1, n)cin >> a[i];
int res = 0;
rep(i, 1, n)if (a[i] != 0)res++;
cout << res << endl;
}
L题 [本题主要考察了运气]
算法考察:高中数学期望
题意:有20人,分别属于5个团体,每个团体4人,输出与答案最接近的编号,具体看题目。
解:
每个团呵每个人彼此没有区别,所以依次猜就可以了。
猜团: 5个团,第1次猜中概率是0.2,第二次是0.2,第三次是0.2,第四次就是0.4
猜人:4人,第一次0.25,第二次0.25,第三次0.5
数学期望 E=1*0.2+2*0.2+3*0.2+4*0.4+1*0.25+2*0.25+3*0.5=5.05E=1*0.2+2*0.2+3*0.2+4*0.4+1*0.25+2*0.25+3*0.5=5.05
所以 ans->3.45+0.05*i=5.05ans->3.45+0.05*i=5.05 -> ans=32ans=32
AC代码:(PHP)
32
H题 [本题主要考察了DFS]
算法考察:暴力,模拟
题意:给你 n*n-1n*n-1 块拼图,制作成本=10-削去几个半圆+补上几个半圆,输出制作成本,其中每块拼图用0,1,2表示不变、凸出、缺口。
解:
统计几个削去的和几个补上的,直接cout就可以了。
AC代码:
void solve()
{
int n;
string s[410];
cin >> n;
int L = 0, R = 0;
rep(i, 1, n * n - 1) {
cin >> s[i];
rep(j, 0, 3) {
if (s[i][j] == '1')
L++;
if (s[i][j] == '2')
R++;
}
}
ll ans = 10 + L - R;
cout << ans << endl;
}
K题 [本题主要考察了dp]
算法考察:状压DP || 贪心+思维+暴力
题意:给定n,m,代表长度为n的01串,有m个1,对长度为3的连续子区间,cout(1)>cout(0)区间数量的个数,输出构造出来的01串坏区间最少的字符串有几个坏区间。
解法一:
贪心+思维+暴力
我们直接构造1001001001111,前面全部以100的形式存在,0不足了再补1,最后暴力求出几个坏区间即可。
AC代码:
void solve()
{
int n, m;
cin >> n >> m;
int cn1 = m, cn0 = n - m;
string s;
rep(i, 1, n) {
if (i % 3 == 1) {
if (cn1 > 0)
cn1--, s += '1';
else
cn0--, s += '0';
}
else {
if (cn0 > 0)
s += '0', cn0--;
else
cn1--, s += '1';
}
}
ll ans = 0;
for (int i = 0; i + 2 < n; ++i) {
int res = 0;
rep(j, i, i + 2) {
if (s[j] == '1')res++;
else res--;
}
if (res > 0)ans++;
}
cout << ans << endl;
}
解法二:
利用状压DP来写,创建 dp[1050][1050][10]dp[1050][1050][10] , dp[i][j][[k]dp[i][j][[k] 表示前i个数字,有j个1,以k的二进制结尾的坏区间最少的个数。
状态转移方程: dp[i][j][k]=min(dp[i-1][j-(k\&1)][(k>>1)],dp[i-1][j-(k\&01)][k>>1|4])+baddp[i][j][k]=min(dp[i-1][j-(k\&1)][(k>>1)],dp[i-1][j-(k\&01)][k>>1|4])+bad
状态转移方程含义:
当前状态由选取前一个数字 (i-1)(i-1) , j j 前一个状态1的数量要通过当前状态 kk 二进制结尾是否有1,即
j-(k\&01)j-(k\&01) ,其中 j-(k\&01)>=0j-(k\&01)>=0 , kk 之前的一个状态有两种情况,可能是 0k0k 也可能 1k1k ,枚举这两种情况取 \min\min 即可。
状态转移方程初始化:
对于 dpdp 数组全部置为 infinf ,对于 dp[3][cnt(k)][k]=cnt(k)>=2dp[3][cnt(k)][k]=cnt(k)>=2 ,其中 cnt(k)cnt(k) 为k二进制中1的数量。
AC代码:
void solve() {
int n, m; cin >> n >> m;
vector dp(n + 10, vector(m + 10, vector<int>(10, inf)));
//选取前i个数字,j个为1,以k的二进制结尾,坏区间的个数
rep(i, 0, 7) {
int x = (i & 01) + (i >> 1 & 01) + (i >> 2 & 01);
dp[3][x][i] = x >= 2;
}
int ans = inf;
rep(i, 4, n) {
rep(j, 0, m) {
rep(k, 0, 7) {
int te = (k & 01) + (k >> 1 & 01) + (k >> 2 & 01);//转移后的1
int bad = te >= 2;
if (j - (k & 1) >= 0)//符合转移条件
dp[i][j][k] = min(dp[i - 1][j - (k & 01)][k >> 1],
dp[i - 1][j - (k & 01)][(k >> 1) | 4]) + bad;
}
}
}
rep(i, 0, 7)
ans = min(ans, dp[n][m][i]);
cout << ans << endl;
}
D题 [现在是,学术时间 (II)]
算法考察:分类讨论
题意:给你一个点A (x,y)(x,y) ,它和原点构成一个矩形的GT区域,我们定义两个矩形的IOU == 两个矩形的交集 // 两个矩形的并集,再给你点P (x_{p},y_{p})(x_{p},y_{p}) ,以P为另外一个矩形的顶点,构建一个边都平行于坐标轴的预测目标框,使得与GT目标的IOU最大,输出这个最大值,误差不超过 10{-4}10 。
解:
如图所示(盗了炸鸡giegie的图),我们当前的GT区域为区域1,我们可以选择的有 2,3,42,3,4 ,
对每种情况进行讨论即可。
PS:这题我的代码就不放了,分类讨论写的太丑了,然后去看别人的代码的时候发现了一个非常简便的代码。
AC代码:
void solve() {
int x, y, xp, yp, s, a, b;
cin >> x >> y >> xp >> yp;
a = max(abs(xp - x), xp);
b = max(abs(yp - y), yp);
s = a * b + x * y;
a = min(a, x);
b = min(b, y);
double ans;
ans = double(a * b) / (s - a * b);
printf("%.9f\n", ans);
}
M题 [本题主要考察了找规律]
算法考察:DP+背包思想
题意:有n个人,m个仙贝,需要分发给n个人,同一个人不能送两次仙贝,允许自己剩下来仙贝,允许有人一个也不送,每次送仙贝都会增加好感值,设 当前剩下的仙贝 xx,并送个那个人 yy 个仙贝,好感值 == y/xy/x (double)
输出所有人最大好感值之和是多少。
解:
因为 n,mn,m 都在 500500 以内,所以我们直接暴力DP就可以了。
DPDP 含义: dp[i][j]表示前i个人送出去了j个仙贝的好感度之和dp[i][j]表示前i个人送出去了j个仙贝的好感度之和
状态转移方程: dp[i][j]=\max(\{dp[i][j],dp[i-1][j],dp[i-1][k]+te\})dp[i][j]=\max(\{dp[i][j],dp[i-1][j],dp[i-1][k]+te\})
AC代码:
double dp[1000][1000];//到第i个一共拿了j个
void solve() {
ll m, n;
cin >> n >> m;
int ans = 0;
rep(i, 1, n) {
rep(j, 0, m) {
rep(k, 0, j) {
double te = 1.0 * (j - k) / (m - k);
if (m == k || j == k)dp[i][j] = max(dp[i - 1][j], dp[i][j]);
dp[i][j] = max(dp[i][j], dp[i - 1][k] + te);
}
}
}
cout << SQR(9) << dp[n][m] << endl;
}
G题 [鸡格线]
算法考察:多种数据结构||set维护存所有未到达 0的下标||并查集维护某个数下一个未到达 0的下标
赛时的话我直接用线段树来维护了
题意:有一个长为 nn 的数组 aa ,你需要支持以下两种操作:
1、输入 l,r,k,l,r,k, ,对区间 [l,r][l,r] 中所有数字执行 a_{i}=f(a_{i})a_{i}=f(a_{i}) 操作 kk 次(式中等号表示赋值操作),之中 f(x)=round(10\sqrt x)f(x)=round(10\sqrt x) , roundround 为四舍五入函数。
2、输出当前数组所有数字的和。
你需要正确处理 mm 次这样的操作。 1\le n,m\le10^5,0\le a_{i}\le10^91\le n,m\le10^5,0\le a_{i}\le10^9
解:
性质: f(x_{0})f(x_{0}) 经过不多次数的操作会收敛到一个不变的值 f(x_{0})=x_{0}f(x_{0})=x_{0}
其中 x_{0}x_{0} 有三个: 0,99,1000,99,100
我们建立树的节点并且直接重载 pushuppushup 操作:
const int MAXN = 4e5 + 5;
struct node
{
int val, flag;
node() {}
node(int x, int y) :val(x), flag(y) {}
node operator + (const node& M) const {
node p;
p.val = val + M.val;
p.flag = flag + M.flag;
return p;
}
//默认复制构造
node(const node& M) {
val = M.val;
flag = M.flag;
}
void operator=(const node& M) {
val = M.val;
flag = M.flag;
}
}tr[MAXN];
这里我们对每次操作进行判断,然后记录下操作后是否等于原来的值,如果等于flag=1,否则flag=0;
这里是一个优化,我们可以直接在修改操作的时候,在满足这个条件直接return
if (tr[u].flag == r - l + 1)return;//优化核心
下面直接给出AC代码,就正常的线段树直接维护就可以AC了
const int MAXN = 4e5 + 5;
struct node
{
int val, flag;
node() {}
node(int x, int y) :val(x), flag(y) {}
node operator + (const node& M) const {
node p;
p.val = val + M.val;
p.flag = flag + M.flag;
return p;
}
//默认复制构造
node(const node& M) {
val = M.val;
flag = M.flag;
}
void operator=(const node& M) {
val = M.val;
flag = M.flag;
}
}tr[MAXN];
int a[MAXN];
int ch(int x) {
long double p = 10.0 * sqrt(x);
int k = (p + 0.5);
if (k == x)return 1;
return 0;
}
int cacl(int x)
{
double pp = 10.0 * sqrt(x);
int ans = (pp + 0.5);
return ans;
}
void build(int u, int l, int r)
{
if (l == r) {
tr[u] = {a[l],ch(a[l]) };
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
tr[u] = tr[u << 1] + tr[u << 1 | 1];
}
void modify(int u, int l, int r, int L, int R)
{
if (tr[u].flag == r - l + 1)return;//优化核心
if (l == r)
{
a[l] = cacl(a[l]);
tr[u] = { a[l],ch(a[l]) };
return;
}
int mid = (l + r) >> 1;
if (R <= mid)
modify(u << 1, l, mid, L, R);
else if (L > mid)
modify(u << 1 | 1, mid + 1, r, L, R);
else
{
modify(u << 1, l, mid, L, mid);
modify(u << 1 | 1, mid + 1, r, mid + 1, R);
}
tr[u] = tr[u << 1] + tr[u << 1 | 1];
}
void solve() {
int n, m;
cin >> n >> m;
rep(i, 1, n)cin >> a[i];
build(1, 1, n);
while (m--)
{
int op; cin >> op;
if (op == 1){
int l, r, k;
cin >> l >> r >> k;
k = min(k, 16LL);
rep(i, 1, k)
modify(1, 1, n, l, r);
}
else cout << tr[1].val << endl;
}
}
F题 [鸡玩炸蛋人]
算法考察:并查集+思维
题意:有一个鸡要下蛋,鸡在一个n个节点,m条边的无向图中,可以通过相连的边移动到没有蛋的节点上,每到一个位置可以下任意数量的蛋,给出最终的图上下蛋情况,问鸡从S->T的方案数。若无合法方案输出0。
解:
诈骗题,一个位置可以下许多蛋,那就说明只要考虑最终情况有蛋的点有哪些,不需要考虑有几个蛋,而且所有的蛋必须处于同一连通块,因为鸡不能跨越一个连通块到另外一个去。所以对于不合法的情况就是蛋不下在同一个连通块,对于没有下蛋的,我们直接对于每个连通块大小平方相加就可以了。
AC代码:
int a[N], p[N];
int find(ll x) {
return p[x] == x ? x : p[x] = find(p[x]);
}
void solve() {
int n, m;
cin >> n >> m;
rep(i, 0, n + 10)p[i] = i;
set<int>vis;
map<int, int>mp;
bool ok = 0;
rep(i, 1, m) {
int u, v;
cin >> u >> v;
u = find(u);
v = find(v);
if (u != v)
p[u] = v;
}
rep(i, 1, n)cin >> a[i];
rep(i, 1, n)mp[find(i)]++;
int mx = *max_element(a + 1, a + 1 + n);
int ans = 0;
rep(i, 1, n) {//all 的炸弹在一个连通块
if (a[i]) {
if (vis.empty())
vis.insert(find(i));
else if (vis.find(find(i)) == vis.end()) {
ok = 1;
break;
}
}
}
if (ok) {
cout << 0 << endl;
return;
}
if (mx == 0) {
for (auto& i : mp)
ans += i.second * i.second;
cout << ans << endl; return;
}
else {
ans = mp[*vis.begin()] * mp[*vis.begin()];
cout << ans << endl;
return;
}
}
E题 [鸡算几何]
算法考察:几何+叉乘
题意:给定 A,B,C,D,E,FA,B,C,D,E,F ,六个点,其中 A,B,CA,B,C 组成一个L型,通过三种操作使得 A,B,CA,B,C 构成的L型能和 D,E,FD,E,F 对应起来,判断是否进行了第三种操作。
解:
判断有无第三种操作只需要判断铁丝有没有变化,铁丝不会发生形变,题目不保证,ABC和DEF对应,考虑叉积判断。第三个操作的特点比如A变为关于BC的对称点,另外我们还要特判长度是否相等的情况,这里我们要用到fabs和eps用于判断。
AC代码:
struct point { double x, y; };
point a, b, c, d, e, f;
double DBA, DBC, DED, DEF;
double Dist(point u, point v)
{
double dx = u.x - v.x, dy = u.y - v.y;
double ans = sqrt(dx * dx + dy * dy);
return ans;
}
void init()
{
DBA = Dist(b, a);
DBC = Dist(b, c);
DED = Dist(e, d);
DEF = Dist(e, f);
}
int ch(point u, point v, point w)
{
point uv, uw;
uw.x = w.x - u.x;
uw.y = w.y - u.y;
uv.x = v.x - u.x;
uv.y = v.y - u.y;
int f = uv.x * uw.y - uv.y * uw.x;
if (f > 0)return 1;
if (f == 0)return 0;
return -1;
}
void solve() {
cin >> a.x >> a.y >>
b.x >> b.y >>
c.x >> c.y >>
d.x >> d.y >>
e.x >> e.y >>
f.x >> f.y;
init();
if (fabs(DBA - DBC) < 1e-9) {
No();
return;
}
if (fabs(DBA - DED) < 1e-9) {
if (ch(b, a, c) == ch(e, d, f))No();
else Yes();
return;
}
if ((ch(b, a, c) == ch(e, f, d)))No();
else Yes();
}
发布于 2023-01-17 15:32・IP 属地安徽