10 25模拟赛总结
7:40 - 8:00 : 看T1,然后想到了二分做法
8:00 - 8:30 : 尝试证明贪心,发现是对的,然后码完了
8:30 - 8:40 : 看T2, 发现和之前一道计数题很像,但是计数太差了,考虑先跳过回来再看
8:40 - 9:20 :看T3, 发现貌似不太会,尝试性质,好像可以骗一下分?
9:20 - 9:30 : 看T4, 貌似有点头绪,然后就接着想
9:30 - 9:35 : 上了个厕所,目前得分还只是100分,决定了先打T4,然后T3,最后看T2
9:35 - 9:50 : 想到了一个T4的假做法,然后码不出来了
9:50 - 10:40 : 直接冲trajan,然后发现码量优秀,幸好没有什么细节
10:40 - 11:20 : 疯狂调试,然后调出来了样例
11:20 - 11:40 : 尝试优化一下,但是没有什么结果
期望得分 : 100 + 0 + 10 + 55 = 165
实际得分 : 100 + 0 + 0 + 52 = 152
问题or收获?
1.对于T1,并不能快速切掉无法对后面题目建立优势,今天是运气好调试出来了
2.鸡蛋要放在多个篮子里,如果今天T4部分分挂了,那么T2,T3一分不打直接寄完了
3.比赛过程中一直有着"这是模拟赛,我就死磕T4了的念头",如果是noip还能有这样的心态吗?但是就结果而言貌似是有效的,
但实际呢?T4的暴力其实根本不用花费两个小时的时间,如果快点打的话最多一个小时就可以打完了,其实还是自己对待模拟赛的态度不端正吧
订题的态度太懈怠了
题目订正:(10.25号结束前补完)
项链
二分答案,然后
c
h
e
c
k
check
check,如果大于
m
i
d
mid
mid,删除较大的数,正确性反证法
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int MAX = 5e5 + 70;
LL tid, n, m, sk[MAX], tot;
LL a[MAX];
struct made {
LL val;
int id;
}b[MAX];
bool mycmp(made X, made Y) { return X.val < Y.val; }
bool check(LL Max) {
tot = 0;
LL use = 0;
for(int i = 1; i <= n; i++) {
if(!tot || sk[tot] + a[i] <= Max) {
sk[++tot] = a[i];
} else {
bool q = 0;
while(tot && sk[tot] + a[i] > Max) {
if(a[i] > sk[tot]) {
q = 1; use++;
break;
} else { tot--; use++; }
}
if(q == 1) continue;
else sk[++tot] = a[i];
}
}
if(use > (n - m)) return 0;
int r = tot, l = 1;
while(l < r) {
if(sk[r] + sk[l] > Max){
if(sk[r] > sk[l]) { r--; use++; }
else{ l++; use++; }
} else break;
}
if(use <= (n - m)) return 1;
return 0;
}
int main() {
freopen("necklace.in","r",stdin);
freopen("necklace.out","w",stdout);
scanf("%lld%lld%lld", &tid, &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
b[i].val = a[i]; b[i].id = i;
}
sort(b + 1, b + 1 + n, mycmp);
if(m == 2) {
printf("%lld\n", b[1].val + b[2].val);
return 0;
}
else if(m == 3) {
printf("%lld\n", b[2].val + b[3].val);
return 0;
}
else {
LL l = 0, r = 2e9, ans;
while(l <= r) {
LL mid = (l + r) >> 1;
// printf("mid %d\n", mid);
if(check(mid)) {
r = mid - 1;
ans = mid;
} else {
l = mid + 1;
}
}
printf("%lld\n", ans);
return 0;
}
return 0;
}
计树
计数好题:看看数据范围,发现希望得到一个
n
3
n^3
n3的做法
如果有左右儿子不同,我们就不必在意树的形态,只用考虑可以给那些节点当儿子与树数了
如何设计状态, 第一维肯定要枚举当前是那个节点,第二维发现转移时可能会出现森林,故为有多少棵树,第三维考虑还有多少个空位可以放
故设计
D
P
DP
DP状态为:
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示到
i
i
i号节点,有
j
j
j棵树,有
k
k
k个空位可以放的防范书
发现题目限制,至少有一个儿子的编号大于该节点的编号,考虑倒序
D
P
DP
DP然后大力分类讨论
若分配0个儿子:
f
[
i
+
1
]
[
j
+
1
]
[
k
]
+
=
f
[
i
]
[
j
]
[
k
]
f[i + 1][j + 1][k] += f[i][j][k]
f[i+1][j+1][k]+=f[i][j][k]不增加空位,增加一颗树
若分配1个儿子:
\qquad
<1>.不分配父亲:
f
[
i
+
1
]
[
j
]
[
k
]
+
=
f
[
i
]
[
j
]
[
k
]
∗
j
∗
2
f[i + 1][j][k] += f[i][j][k] * j * 2
f[i+1][j][k]+=f[i][j][k]∗j∗2
\qquad
<2>. 分配父亲:
f
[
i
+
1
]
[
j
−
1
]
[
k
]
+
=
f
[
i
]
[
j
]
[
k
]
∗
k
∗
(
j
−
1
)
∗
2
f[i +1][j - 1][k] += f[i][j][k] * k * (j - 1) * 2
f[i+1][j−1][k]+=f[i][j][k]∗k∗(j−1)∗2
若分配2个儿子
\qquad
<1>.给1个儿子,提供一个空位,不给父亲:
f
[
i
+
1
]
[
j
]
[
k
+
1
]
+
=
f
[
i
]
[
j
]
[
k
]
∗
j
∗
2
f[i + 1][j][k + 1] += f[i][j][k] * j * 2
f[i+1][j][k+1]+=f[i][j][k]∗j∗2
\qquad
<2> 给1个儿子,提供一个空位,给父亲:
f
[
i
+
1
]
[
j
−
1
]
[
k
]
+
=
f
[
i
]
[
j
]
[
k
]
∗
k
∗
(
j
−
1
)
∗
2
f[i + 1][j-1][k] += f[i][j][k] * k * (j - 1) * 2
f[i+1][j−1][k]+=f[i][j][k]∗k∗(j−1)∗2
\qquad
<3> 给2个儿子,不提供空位,不给父亲:
f
[
i
+
1
]
[
j
−
1
]
[
k
−
1
]
+
=
f
[
i
]
[
j
]
[
k
]
∗
j
∗
(
j
−
1
)
f[i+1][j-1][k -1] += f[i][j][k] * j * (j - 1)
f[i+1][j−1][k−1]+=f[i][j][k]∗j∗(j−1)
\qquad
<4> 给2个儿子,不提供空位,给父亲:
f
[
i
+
1
]
[
j
−
2
]
[
k
−
1
]
+
=
f
[
i
]
[
j
]
[
k
]
∗
k
∗
(
j
−
1
)
∗
(
j
−
2
)
f[i+1][j-2][k-1] += f[i][j][k] * k * (j - 1) * (j - 2)
f[i+1][j−2][k−1]+=f[i][j][k]∗k∗(j−1)∗(j−2)
细节很多,复杂度
O
(
6
∗
n
3
)
O(6*n^3)
O(6∗n3)
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MM %MOD
const int MAX = 310;
const int MOD = 1e9 + 7;
int tid, T, l[MAX], r[MAX], n;
LL f[MAX][MAX][2 * MAX]; //表示枚举i个节点, 有j颗树, 有k个空位可以填儿子
void slove() {
memset(f, 0, sizeof(f));
f[1][1][0] = 1; //因为最后一个点有儿子一定不合法
if(l[1] != 0) {
printf("0\n");
return ;
}
for(int i = 1; i < n; i++) { //第几个节点
for(int j = 1; j <= i; j++) { //有j棵树
for(int k = 0; k <= 2 * i; k++) {
for(int s = l[i + 1]; s <= r[i + 1]; s++) {
if(s == 0) { //对于儿子数目为0
f[i + 1][j + 1][k] = (f[i + 1][j + 1][k] + f[i][j][k]) MM; //考虑新开一个点
if(k) f[i + 1][j][k - 1] = (f[i + 1][j][k - 1] + f[i][j][k] * k MM) MM; //考虑成为别人的儿子
}
else if(s == 1) {
f[i + 1][j][k] = (f[i + 1][j][k] + (f[i][j][k] * j MM * 2LL MM)) MM; //考虑有1个儿子,不和别人连
if(k && j >= 1) f[i + 1][j - 1][k - 1] = (f[i + 1][j - 1][k - 1] + (f[i][j][k] * 2LL MM * k MM * (j - 1) MM)) MM; //考虑有一个儿子,连一个儿子,再和别人连 先连儿子在看父亲无法转,先连父亲,在看儿子
}
if(s == 2) {
// ; //考虑没有儿子,到后面一定会不合法
f[i + 1][j][k + 1] = (f[i + 1][j][k + 1] + (f[i][j][k] * j MM * 2LL MM) ) MM; // 考虑一个儿子,不和父亲连,提供一个空位
if(j) f[i + 1][j - 1][k] = (f[i + 1][j - 1][k] + (f[i][j][k] * k MM * (j - 1) MM * 2LL MM) ) MM; //考虑一个儿子,和父亲连,提供一个空位
if(j >= 2) f[i + 1][j - 1][k] = (f[i + 1][j - 1][k] + (f[i][j][k] * (j) MM * (j - 1) MM) ) MM; //考虑两个儿子,不和父亲连,不提供空位
if(j >= 3 && k) f[i + 1][j - 2][k - 1] = (f[i + 1][j - 2][k - 1] + (f[i][j][k] * k MM * (j - 1) MM * (j - 2) MM) ) MM; //考虑两个儿子,和父亲连,不提供空位
}
}
// printf("f[%d][%d][%d] %d\n", i, j, k, f[i][j][k]);
}
}
}
cout<<f[n][1][0] MM<<endl;
}
int main() {
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d%d",&tid, &T);
while(T, T--) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d%d", &l[n - i + 1], &r[n - i + 1]);
slove();
}
return 0;
}
书信: 锅
迷宫 : 锅
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!