OIFC 2025.11.11 模拟赛总结
\(88 + 12 + 0 + 20 = 120pts\),不用退役了。
终于会做 T1 了,虽然我没删调试信息挂了 \(12\) 分...
好在比 \(20\) 分的人高了 \(100\) 分(
T1 破译系统
终于会做 T1 了。
但忘了删除调试信息,还好只挂了 \(12\) 分。
题目描述
特工 isj2OO9 奉命破解一个秘密组织的加密通信系统。该系统发送一段长度为 \(n\) 的非负整数序列 \(a_1,a_2,\cdots a_n\),每个数字代表一个加密信号。isj2OO9 需要将序列分割成若干连续段,以便进行解密分析。
分割方案由序列 \(p_1,p_2,\cdots p_m\) 表示,其中 \(p_1 = 1\),\(p_m = n + 1\),且 \(p_1 < p_2 < \cdots < p_m\)。这定义了 \(m - 1\) 个连续子序列 \(S_i = \{a_k | k \in [p_i,p_{i+1})\}\)。对于每个子序列 \(S_i\),isj2OO9 计算其最小值 \(\min S_i\) 和最小的未在 \(S_i\) 中出现的非负整数 \(\text{mex } S_i\)。
为了确保解密的有效性,破译系统要求满足以下关键条件:
isj2OO9 需要计算所有满足条件的分割方案数量,以便评估破译的成功率。由于结果可能很大,请输出答案对 \(15112009\) 取模的值。
数据范围:\(1 \leq n \leq 5 \times 10^5\),\(0 \leq a_i \leq 10^9\)。
题解
先说结论,证明可以看代码里注释的思考过程:
如果原来序列中没有 \(0\),则答案为 \(2^{n-1}\)。否则,分情况讨论:
-
原序列中有 \(1\)。此时答案为所有区间都包含 \(0\) 的分割方案数减去所有区间包含 \(0\) 且所有区间包含 \(1\) 的方案数。
-
没有 \(1\),则答案为所有区间都包含 \(0\) 的分割方案数。
现在考虑怎么求解。所有区间都包含 \(0\) 的分割方案数可以通过找到序列内的所有 \(0\),计算间隔个数加 \(1\) 的乘积来得到。具体证明见代码。
而所有区间包含 \(0\) 且所有区间包含 \(1\) 的方案数可以通过动态规划求解。
令 \(dp_{i,0/1,0/1}\) 表示考虑到了第 \(i\) 个间隔(定义 \(a_i\) 和 \(a_{i+1}\) 之间的间隔为第 \(i\) 个),且目前最后一个区间(可能还没分割完)内是否含有 \(0\)、是否含有 \(1\),的方案数。
显然,初始值是 \(dp_{0,0,0} = 1\),答案为 \(dp_{n,1,1}\)。转移方程就是根据 \(a_i\) 的值判断是否分割。注意,上一个区间必须同时含有 \(0\) 和 \(1\),才能进行一次分割。
时间复杂度 \(\mathcal{O}(n)\)。
参考代码(内含考场全部思考过程):
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int Mod = 15112009;
/*
if 有0:
mex({min S1, min S2, ···, min Sm-1}) = mex({一个有 0 的集合}) >= 1
此时如果相等,需要满足min({mex S1, mex S2, ···, mex Sm-1}) >= 1
也就是说,任意一段的 mex 要大于等于 1,即每一段都有 0.
else:
(特殊性质 A)
mex({min S1, min S2, ···, min Sm-1}) = mex({一个没 0 的集合}) = 0.
由于没有 0,所以 min({mex S1, mex S2, ···, mex Sm-1}) 也一定是 min({0, 0, 0, ···, 0}) = 0.
所以,任意分割方案都满足要求。
此时,我们需要做的就是在 n 个数的 n - 1 个空位中插入板子来分割.
每个位置都可以插或不插,因此答案就是 2^{n-1}.
那继续考虑有 0 的情况(条件为“每一段都有 0”),分析一下
if 有1:
由于前提条件是“每一段都有 0”,所以 mex({min S1, min S2, ···, min Sm-1}) = mex({0, 0, 0, ···, 0}) = 1
而如果 mex({min S1, min S2, ···, min Sm-1}) = 1,则所有存在 1 的区间全部有 0,显然满足
此时要求 min({mex S1, mex S2, ···, mex Sm-1}) = 1. 由于所有的 mex 都大于 0,所以每一段都存在 0.
而存在一个区间的 mex = 1,因此存在一段不包含 1.
综上,这部分的条件时所有区间包含 0,且存在一段不包含 1.
假设所有区间都包含 0 的分割方案数为 x,则x = (所有区间包含 0 且存在一段不包含 1) + (所有区间包含 0 且所有区间包含 1)
我们要求的是 x - (所有区间包含 0 且所有区间包含 1). 做完了。
else:
mex({min S1, min S2, ···, min Sm-1}) = mex({一个有 0 没 1 的集合}) = 1.
此时要求 min({mex S1, mex S2, ···, mex Sm-1}) = 1.
因此,我们需要把原序列分成好多段,满足每段都有 0 且存在一段没有 1.
但这时原序列没有 1,因此满足每段都有 0 就行。
考虑序列形如:
0 0 x x x 0 x 0 x x 0 x x x (x>0)
可以转化为:
0 (1个间隔) 0 (4个间隔) 0 (2个间隔) 0 (3个间隔) 0
如何证明符合要求等价与两个 0 之间没有第二块板子呢?
考虑一下,如果有第二块则这个区间一定没有 0,不符合要求。因此,符合要求的区间不插第二块板子。
另一方面,如果不插第二块板子,确实把区间分成了好多带 0 的区间(可以不插)
因此,每一段间隔都可以不插或者插一块. 4个间隔的贡献为 4 + 1 = 5.
综上:
如果没有0,则答案为2^{n-1}.
否则,分情况讨论:
1. 有1. 答案为 (所有区间都包含 0 的分割方案数) - (所有区间包含 0 且所有区间包含 1 的方案数)
2. 没有1. 则此时找到序列中所有的 0,然后分割即可。
对于样例:
0 x x x x x 0 ==> 6
0 1 x x x 1 0 ==> 4
这样做是正确的!
而且我们刚才得到了如何求所有区间都包含 0 的分割方案数;故现在考虑如何求包含 0 和 1.
原题已经转化为:给定一个序列,求使得所有区间包含 0 且所有区间包含 1 的分割方案数.
能dp做吗?令 dp_{i,0/1,0/1} 表示考虑到第 i 个间隔,最后的那一段(可能还没分割完)是否包含0和1,的方案数。
此时,分情况讨论这个位置切不切,能不能切开即可!
做完了!?
做出来了!!!通过了所有大样例!!!
*/
inline int fp(int a, int b){
int res = 1;
while(b){
if(b & 1) res = res * a % Mod;
a = a * a % Mod;
b >>= 1;
}
return res;
}
int n, a[500005], dp[500005][2][2];
inline void add(int &x, int y){
x = (x + (y % Mod + Mod) % Mod) % Mod;
return;
}
signed main(){
freopen("decryption.in", "r", stdin);
// freopen("20.in", "r", stdin);
freopen("decryption.out", "w", stdout);
n = read();
for(int i = 1; i <= n; i++) a[i] = read();
bool has0 = false, has1 = false;
for(int i = 1; i <= n; i++){
if(a[i] == 0) has0 = true;
if(a[i] == 1) has1 = true;
if(has0 && has1) break;
}
if(!has0){
// cout << "No zero!" << '\n';
cout << fp(2, n - 1) << '\n';
return 0;
}
//has0 = true
int first0pos = n, last0pos = 1;
for(int i = 1; i <= n; i++){
if(a[i] == 0){
first0pos = min(first0pos, i);
last0pos = max(last0pos, i);
}
}
//calc 0
vector<int> v;
int lastnum = 1;
for(int i = first0pos + 1; i <= last0pos; i++){
if(a[i] == 0){
v.push_back(lastnum);
lastnum = 1;
}else{
lastnum++;
}
}
// for(auto p : v) cout << p << " ";
int all0 = 1;
for(auto p : v) all0 = all0 * ((p + 1) % Mod) % Mod;
if(!has1){
// cout << "No one!" << '\n';
cout << all0 << '\n';
return 0;
}
//has1 = true
dp[0][0][0] = 1;
for(int i = 1; i <= n; i++){
//令 dp_{i,0/1,0/1} 表示考虑到第 i 个间隔,最后的那一段(可能还没分割完)是否包含0和1,的方案数。
if(a[i] == 0){
add(dp[i][0][0], dp[i - 1][0][1] + dp[i - 1][1][1]);
add(dp[i][0][1], 0);
add(dp[i][1][0], dp[i - 1][0][0] + dp[i - 1][1][0]);
add(dp[i][1][1], dp[i - 1][0][1] + dp[i - 1][1][1]);
}else if(a[i] == 1){
add(dp[i][0][0], dp[i - 1][1][0] + dp[i - 1][1][1]);
add(dp[i][0][1], dp[i - 1][0][0] + dp[i - 1][0][1]);
add(dp[i][1][0], 0);
add(dp[i][1][1], dp[i - 1][1][0] + dp[i - 1][1][1]);
}else{
add(dp[i][0][0], dp[i - 1][1][1] + dp[i - 1][0][0]);
add(dp[i][0][1], dp[i - 1][0][1]);
add(dp[i][1][0], dp[i - 1][1][0]);
add(dp[i][1][1], dp[i - 1][1][1]);
}
}
cout << ((all0 - dp[n][1][1]) % Mod + Mod) % Mod << '\n';
return 0;
}
T2 单源最短路
无语,纯 Dijkstra 的 \(4\) 分没拿到。。。
题目描述
isj2OO9 是著名的网络探险家,负责在复杂的数字城市网络中导航。这个网络由 \(n\) 个节点和 \(m\) 条有向边组成。每条边 \(i\) 都有特定的通行成本 \(w_i\),且 \(w_i\) 为非负值。
最近,网络中出现了一些新的安全协议:对于第 \(i\) 条边,存在 \(k_i\) 条边 \(t_{i,1\sim k_i}\),不能在经过这些边后立刻走第 \(i\) 条边。
isj2OO9 的任务是从起点节点 \(1\) 出发,找到到达所有其他节点的最短路径,同时遵守这些限制。如果某个节点无法到达,isj2OO9 将标记为 -1,表示该节点不可访问。
现在,请你帮助 isj2OO9 解决这个问题:给定图 \(G\) 和限制条件,计算从节点 \(1\) 到所有点的最短路径。
数据范围:\(1 \leq T \leq 3\),\(1 \leq n,m \leq 5 \times 10^5\),\(k_i \geq 0\),\(0 \leq \sum k_i \leq 3 \times 10^6\),\(1 \leq u_i,v_i \leq n\),\(0 \leq w_i \leq 10^9\),\(1 \leq t_{i,j} \leq m\)。
题解
#include<bits/stdc++.h>
#include <cctype>
#define int long long
using namespace std;
namespace IO {
char Is[(1<<21)+10],Os[(1<<21)+10];
int Ipt,Opt;
char gc() {
if(Ipt==1<<21)
Ipt=0;
if(!Ipt)
Is[fread(Is,1,1<<21,stdin)]=0;
return Is[Ipt++];
}
void flush() {
fwrite(Os,1,Opt,stdout);
Opt=0;
}
void pc(char x) {
if(Opt==1<<21)
flush();
Os[Opt++]=x;
}
int read() {
int x=0,f=1;
char ch=gc();
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=gc();
}
while(ch<='9'&&ch>='0')
x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
int t[100];
void write(int x) {
if(x<0)
pc('-'),x=-x;
if(!x) {
pc('0');
return;
}
int len=0;
while(x)
t[++len]=x%10,x/=10;
for(int i=len;i>=1;--i)
pc(t[i]+48);
}
}
using namespace IO;
int n, m;
struct Edge{
int to, w, id;
}edge[500005];
vector<Edge> e[500005];
struct Node{
int id, d;
bool operator > (Node x) const {
return d > x.d;
}
};
priority_queue<Node, vector<Node>, greater<Node> > q;
int dis[500005], vis[500005], DIS[500005];
vector<int> ban[500005];
bool isBanned[500005];
inline void Solve(){
for(int i = 1; i <= n; i++) e[i].clear();
for(int i = 1; i <= m; i++) ban[i].clear();
n = read(), m = read();
for(int i = 1; i <= m; i++){
int u = read(), v = read(), w = read();
e[u].push_back(edge[i] = {v, w, i});
}
for(int i = 1; i <= m; i++){
int k = read();
while(k--) ban[read()].push_back(i);
}
while(!q.empty()) q.pop();
memset(dis, 0x3f, sizeof(dis)), memset(vis, 0, sizeof(vis));
for(auto p : e[1]) q.push({p.id, p.w}), dis[p.id] = p.w;
while(!q.empty()){
int u = q.top().id; q.pop();
if(vis[u]) continue;
vis[u] = true;
for(auto p : ban[u]) isBanned[p] = true;
for(int i = 0; i < (int)e[edge[u].to].size(); i++) if(!isBanned[e[edge[u].to][i].id]){
if(dis[e[edge[u].to][i].id] > dis[u] + e[edge[u].to][i].w){
dis[e[edge[u].to][i].id] = dis[u] + e[edge[u].to][i].w;
q.push({e[edge[u].to][i].id, dis[e[edge[u].to][i].id]});
}
swap(e[edge[u].to][i], e[edge[u].to].back()), e[edge[u].to].pop_back(), i--;
}
for(auto p : ban[u]) isBanned[p] = false;
}
memset(DIS, 0x3f, sizeof(DIS)); DIS[1] = 0;
for(int i = 1; i <= m; i++) DIS[edge[i].to] = min(DIS[edge[i].to], dis[i]);
int INF = DIS[0];
for(int i = 1; i <= n; i++) write((DIS[i] == INF ? -1 : DIS[i])), pc(" \n"[i == n]);
return;
}
signed main(){
freopen("SSSP.in", "r", stdin);
freopen("SSSP.out", "w", stdout);
int T = read(), id = read();
while(T--) Solve();
flush();
return 0;
}

浙公网安备 33010602011771号