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\)

为了确保解密的有效性,破译系统要求满足以下关键条件:

\[\text{mex}(\{\min S_1,\cdots,\min S_{m - 1}\}) = \min(\{\text{mex } S_1,\cdots,\text{mex } S_{m - 1}\}) \]

isj2OO9 需要计算所有满足条件的分割方案数量,以便评估破译的成功率。由于结果可能很大,请输出答案对 \(15112009\) 取模的值。

数据范围:\(1 \leq n \leq 5 \times 10^5\)\(0 \leq a_i \leq 10^9\)

题解

先说结论,证明可以看代码里注释的思考过程:

如果原来序列中没有 \(0\),则答案为 \(2^{n-1}\)。否则,分情况讨论:

  1. 原序列中有 \(1\)。此时答案为所有区间都包含 \(0\) 的分割方案数减去所有区间包含 \(0\) 且所有区间包含 \(1\) 的方案数

  2. 没有 \(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;
}
posted @ 2025-11-11 13:48  zhang_kevin  阅读(6)  评论(0)    收藏  举报