2024杭电多校第6场
6
1004 不醒人室 (hdu7497)
模拟题,思路和题解略有不同,由于数据保证各时间段有序,可在循环外设变量 \(awake\) 表示在当前循环位置、最后清醒的时间,遍历上课时间的同时用计划的睡觉时间更新 \(awake\) 即可,时间复杂度为线性。
主要代码:
ll awake = 0;
int j = 1;
for(int i = 1; i <= n; i++) {
ll l = c[i].l, r = c[i].r; //c[i]课程时间,s[i]睡觉时间
if(awake < r) {
if(awake >= l) awake = -1;
for(; j <= m; j++) {
ll st = s[j].r, ed = s[j].r + (s[j].r - s[j].l) * 2;
if(j < m) ed = min(ed, s[j + 1].l);
if(st <= l && ed >= r) {
awake = ed;
break;
}
else if(st > l) {
awake = -1;
break;
}
}
if(awake < 0) break;
}
}
至于为什么思路讲得有点抽象,因为本来就没啥思路······这场杭电的前一天晚上打cf去了(参见上一篇题解),赛时脑子还不太清醒,一通乱写过了wyq的数据就交,没想到对了······ wyq评价:最符合题意的一集)
1005 交通管控 (hdu7498)
由 \(k\) 的数据范围想到状压dp,红绿灯三种颜色状态需要三进制状压。对于相同的操作杆组合,每根操作杆的使用顺序不影响最终结果,因此用类似背包的方式求解,使用滚动数组可优化空间复杂度至 \(3^{10}\) 左右。答案对 \(m\) 取模,由于最终答案可能是 \(m\) 的整数倍,取模后为 \(0\),无法区分是否出现该组合,需要另开一个bool数组表示该状态的合法性。
以下为dp代码,需注意初始化:
for(int s = 0; s < p[k]; s++) {
dp[s][0] = dp[s][1] = 0;
vis[s][0] = vis[s][1] = 0;
}
dp[0][0] = 1;
vis[0][0] = 1;
for(int i = 1; i <= n; i++) {
int ii = i & 1;
for(int s = 0; s < p[k]; s++) {
dp[s][ii] = 0;
vis[s][ii] = 0;
}
string c;
cin >> c;
for(int s = 0; s < p[k]; s++) {
if(!vis[s][ii ^ 1]) continue;
vis[s][ii] = 1;
dp[s][ii] += dp[s][ii ^ 1];
dp[s][ii] %= mo;
int to = 0;
for(int z = 0; z < k; z++) {
int cur = (s / p[z]) % 3;
if(c[z] == '+') cur = (cur + 1) % 3;
else if(c[z] == '-') cur = (cur + 2) % 3;
to += p[z] * cur;
}
vis[to][ii] = 1;
dp[to][ii] += dp[s][ii ^ 1];
dp[to][ii] %= mo;
}
}
除此以外我还尝试过分层图+拓扑的方法,逻辑严不严谨另说,由于空间无法优化,喜提MLE
1007 树上 \(MEX\) 问题 (hdu7500)
赛时没想到如何统计最终答案,看了眼题解似乎能动态维护,于是按照之前的想法继续写,还真可以)
直接统计子图的 \(MEX\) 值非常麻烦,可从每个子图对答案的“贡献”角度思考。若某一子图中包含权值为 \(0\) 至 \(x - 1\) 的所有点,可确定其 \(MEX\geq x\),即该子图的贡献不小于 \(x\). 先假设所有子图 \(MEX = 0\),对于包含 \(0\) 权值的 \(t\) 个子图,其贡献至少为 \(1\),故应补充答案,有 \(sum\) \(+\)\(=\) \(t\);在此基础上,对于同时包含 \(0,1\) 权值的子图,同理有 \(sum\) \(+\)\(=\) \(t'\);······ 以此类推,若每种子图的数量可快速计算,即可 \(O(n)\) 统计答案。
树形dp可 \(O(n)\) 求出以每个节点为根节点的子图数量,递推即乘法原理 \(cnt[i] = \prod (cnt[t] + 1)\). 对于包含 \(0\) 至 \(x - 1\) 所有节点的子图数量 \(ans\),考虑动态维护,每次加入新节点时计算 \(ans\) 的变化量即可。以 \(0\) 权值点为根,前 \(x - 1\) 个节点形成的最小子图为 \(s\),若 \(x\) 权值在 \(s\) 内部,所有包含 \(s\) 的子图必然包含 \(x\),\(ans\) 不变;若 \(x\) 在 \(s\) 外,由于 \(s\) 内部所有点必然与根节点 \(0\) 权值处连接,\(x\) 不可能是它们的祖先,因此顺着 \(x\) 的父亲节点向上寻找,即可将 \(x\) 加入 \(s\) 中。如此处理后,\(x\) 至其祖先的链上答案发生变化,其他位置仍然遵循乘法原理,设该链上子图总数 \(k\),答案变化 \(ans = (ans/k) * k'\);\(x\) 向上寻找父节点 \(f\),有 \(k'=k'*cnt[f]/(cnt[x] + 1)\),原先数量除去所有不选择 \(x\) 的情况即可。由于每个点只被加入 \(s\) 一次,复杂度仍然为 \(O(n)\).
sum = cnt[mp[0]];
in[mp[0]] = 1; //in数组表示点是否在s内,由于每次操作的s固定,不需要用并查集
ll ans = cnt[mp[0]];
for(int i = 1; i < n; i++) {
int x = mp[i];
if(in[x] == 0) {
in[x] = 1;
ll tim = cnt[x]; //即原文中k'
int pre = x;
for(int j = f[x]; ; j = f[j]) {
if(in[j]) {
ans = ans * tim % mo * inv(cnt[pre] + 1) % mo; //cnt[pre] + 1即原文中k
sum += ans;
if(sum >= mo) sum -= mo;
break;
}
tim = tim * cnt[j] % mo * inv(cnt[pre] + 1) % mo;
in[j] = 1;
pre = j;
}
} else {
sum += ans;
if(sum >= mo) sum -= mo;
}
}
printf("%lld\n", sum);