NOIP 2024 游记

· · 生活·游记

Day -14

在路上偶遇了信竞老师。在他的建议下,我每天晚自习去集训。

Day -12

不用写作业真爽。

Day -5

这一周的所有集训我都是先做出T2再做出T1的,这使我有不祥的预感。

Day 0

所有与具体做法强相关的将放在题解部分。

T1(上)

对着草稿纸比划了 0.5h,打了个贪心,证不出来。

小样例过了,程序在大样例的答案居然比样例输出还大,这使我疑惑,调了 0.5h 没调出来。

T2

正难则反。反着想不久(<0.5h)就想出来了。T2 比 T1 简单,Day -5 的预感成真了。

踩了俩坑。

然后第 2, 3 个大样例一起过了。

T3

太长没看。

T4

想出了 ST 表 \Theta(n\log^2n+q[(L-k)+\log n]) 的做法(其中 L=r-l+1),然而写挂了,只有小样例过得去。

在调 T4 和 T1 之间还是选择了 T1,因为就算这种解法调好也不超过 40 pts,事实证明这是明智的。

T1(下)

此时距离结束还有 2h.

首先重构了一下代码。原来的代码是将 s_1s_2 均未被分割的编成一块,统一计算转移,重构之后就是一个一个字符转移的,虽然常数稍大但是更简洁清晰。

然后就是打了几个断点,手造样例。中途一个插曲是造样例的时候把顺序造成 s_1, t_1, s_2, t_2 了,还以为自己发现了症结所在。

最后终于发现自己把 n1 = (t1[i]=='0' || t1[i]!=t1[i-1]) 打成了 n1 = (t1[i]==0 || t1[i]!=t1[i-1]),打少了个单引号,这导致了没有把连续的 0 隔开。

改完之后就过了全部样例,此时距离结束还有 45min.

然后我心情舒坦地上了个厕所。(吸取 CSP 教训,我这次上了 3 次厕所)

例行拜了拜电子佛祖。

老师请吃了披萨,好耶!

同学们太热情了,刚出炉的披萨立刻被瓜分了,只能等他们吃饱了才能吃到。

肴核既尽,杯盘狼藉。老师先走了,同学们在打牌。我不会打牌,于是也走了。

T1 也许是因为贪心难以证明迟迟没出数据。

民间数据 T2 AC.

T4 估计寄了,\le 10\text{ pts}.

一首改编的打油诗

一题夸自己,两题了不起。 T4 写挂了?有写就是牛。

希望能时刻保持良好的心态。

题解

T1

不难发现原题可以转化为: 将 s_1, s_2 分别划成几个区间,区间内打乱。

例如样例

$t_1=$ `111010` 可以划成 $011/1/0/1$. 也就是说,我们在每个 $t_i=0$ 的左右分别划一刀。 接下来就是贪心。 如果 $i$ 所在的段内同时有未匹配的 $s_{1,j}=s_{2,k}$,就把 $s_{1,j}, s_{2,k}$ 挪到 $i$ 的位置并标记为匹配。 考场上粗略地证了一下,但是考完发现有漏洞,所以不发出来误人子弟了。 #### Code ```cpp #include <bits/stdc++.h> using namespace std; const int maxn=1e5+100; char s1[maxn], s2[maxn], t1[maxn], t2[maxn]; struct area { int cnt[4]; } sa1[maxn], sa2[maxn]; int belong1[maxn], belong2[maxn]; int sa1tot, sa2tot; int main(){ ios::sync_with_stdio(0); cin.tie(0); freopen("edit.in", "r", stdin); freopen("edit.out", "w", stdout); int t; cin >> t; while (t--){ memset(sa1, 0, sizeof sa1); memset(sa2, 0, sizeof sa2); sa1tot = sa2tot = 0; int n; cin >> n; cin >> s1+1 >> s2+1 >> t1+1 >> t2+1; for (int i=1; i<=n; i++){ int n1 = (t1[i]=='0' || t1[i]!=t1[i-1]), n2 = (t2[i]=='0' || t2[i]!=t2[i-1]); sa1tot += n1; sa2tot += n2; sa1[sa1tot].cnt[s1[i]-'0']++; sa2[sa2tot].cnt[s2[i]-'0']++; belong1[i] = sa1tot; belong2[i] = sa2tot; } int ans=0; for (int i=1; i<=n; i++){ int bl1=belong1[i], bl2=belong2[i]; if (sa1[bl1].cnt[0] && sa2[bl2].cnt[0]){ sa1[bl1].cnt[0]--; sa2[bl2].cnt[0]--; ans++; } else if (sa1[bl1].cnt[1] && sa2[bl2].cnt[1]) { sa1[bl1].cnt[1]--; sa2[bl2].cnt[1]--; ans++; } } cout << ans << '\n'; } return 0; } ``` ### T2 基本操作:先将限制条件按 $c_i$ 排序。 不难发现,如果一个位置没有被两个限制夹着,那么 $a_i, b_i$ 可以任取,即每个位置有 $v^2$ 种取值。 证明也很简单,如果一个位置的左边没有限制而右边有,那我们只需令 $x_i\ne a_i$, 如果右边没有限制而左边有,那么填什么都不影响。 那如果被两个限制 $c_j, c_{j+1}$ 夹着呢? 正难则反。 我们考虑 $a_i, b_i$ 应该等于什么才能让 $x_{c_{j+1}} \mathop{\ne}\limits^{一定} d_{j+1}$. 那么显然 $a_i, b_i$ 一定是一条完整的逻辑链条,才能把 $x_{c_{j+1}}$ 限制死。 链条的起始端一定为 $d_i$,中间是任意的(有 $v$ 种取值),末端一定不为 $d_{i+1}$(有 $(v-1)$ 种取值) 于是 $negans=v^{\Delta c}(v-1)$,其中 $\Delta c=c_{j+1}-c_j$. 该段的答案为 $v^{2\Delta c}-negans$. 把所有段(包括没有被两个限制夹着的段)的答案乘起来即可。 #### Code ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; const ll maxm=1e5+100, mod=1e9+7; struct xianz { long long c, d; bool operator < (xianz &other){ return c < other.c; } bool operator == (xianz &other){ return c==other.c && d==other.d; } } xz[maxm]; ll qpow(ll a, ll b){ ll ans=1; while (b){ if (b&1){ ans *= a; ans %= mod; } a *= a; a %= mod; b >>= 1; } return ans; } int main(){ ios::sync_with_stdio(0); cin.tie(0); // freopen("assign.in", "r", stdin); // freopen("assign.out", "w", stdout); int t; cin >> t; while (t--){ ll n, m, v; cin >> n >> m >> v; for (int i=1; i<=m; i++){ cin >> xz[i].c >> xz[i].d; } sort(xz+1, xz+m+1); m = unique(xz+1, xz+m+1) - (xz+1); bool ansis0=false; ll ans=1; for (int i=2; i<=m; i++){ if (xz[i].c == xz[i-1].c){ ansis0 = true; break; } ll posans = qpow(v, 2*(xz[i].c-xz[i-1].c)), negans = qpow(v, xz[i].c-xz[i-1].c-1); negans *= v-1; negans %= mod; ans *= (posans-negans + mod) % mod; ans %= mod; } if (m){ ans *= qpow(v, 2*(xz[1].c-1)); ans %= mod; ans *= qpow(v, 2*(n-xz[m].c)); ans %= mod; } else { ans *= qpow(v, 2*(n-1)); ans %= mod; } if (ansis0){ cout << "0\n"; } else { cout << ans%mod << '\n'; } } return 0; } ```