0%

题目大意

给定 nn 个人从左到右站成一排。每个人的身份只有两种:诚实者或欺诈者。

  • 诚实者一定说真话。

  • 欺诈者可能说真话,也可能说假话。

  • ii 个人会报一个数 aia_i,表示“自己左边有多少个欺诈者”。

  • 额外保证:不存在两个相邻的欺诈者。

任务是统计:一共有多少种身份安排(每个人是诚实者或欺诈者)能够与所有人的发言同时不矛盾。结果对 998244353 取模输出。

输入为两行:

  • 第一行是 nn

  • 第二行是 nn 个整数 aia_i

输出为一行:满足条件的方案数(取模后)。

样例:

1
2
3
4
5
6
输入:
6
0 0 0 0 0 0

输出:
2

样例含义:6 个人都说自己左边有 0 个欺诈者。在“欺诈者不相邻”的前提下,能成立的身份安排共有 2 种。

思路讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 0 shuo huang
for(int i=1;i<=N;++i){
if(i==1){
if(A[i]==0){
dp[i][1]=1;
dp[i][0]=1;
}else{
dp[i][0]=1;
}
continue;
}
// 说谎者只能从前面一个的诚实者转移过来
// 因为不能连续两个都是说谎者
dp[i][0]=dp[i-1][1];
if(A[i]==A[i-1]){
// 可以从诚实者转移过来的条件
dp[i][1]+=dp[i-1][1];
dp[i][1]%=mod;
}
if(A[i]==A[i-2]+1){
// 可以从说谎者转移过来的条件
// 之所以是这个,是因为如果前面一个是说谎者的话,那么再前面一个就是诚实者
dp[i][1]+=dp[i-1][0];
dp[i][1]%=mod;
}
}

image

AC代码

AC

http://10.199.227.101/contest/1005/problem/L1-7/submission-detail/2146

心路历程(WA,TLE,MLE……)

基本情况

image

还是有继续进步的这个空间的。

心得感悟

l1-3(while 写成 if)

image

l1-5(map 效率不够高)

原来使用了这个 map,对于 2e6 的这个数据,1000ms 的时限,可能不是很够。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>
#define debug(x) cerr<<#x<<":["<<x<<"]";
#define SZ(x) ((ll)x.size())
#define all(x) x.begin(),x.end()

using namespace std;
using ll = long long;
using DB = double;
const DB pi = acos(-1.0);
//const ll mod = 998244353;
const ll INF = (1ll<<61)-1;
ll lT;

void Solve(){
ll N,mod;
cin>>N>>mod;
vector<ll> A(N+2);
for(int i=1;i<=N;++i){
cin>>A[i];
}
// map<ll,ll> mp;
ll ans=0;
vector<ll> cnt(mod+2);
for(int i=1;i<=N;++i){
ll r=A[i]%mod;
cnt[r]++;
if (cnt[r]==2) {
++ans;
}
}
// for(auto it=mp.begin();it!=mp.end();++it){
// if(it->second>=2){
// ++ans;
// }
// }
cout<<ans<<"\n";
}
int main(){
cin.tie(0)->sync_with_stdio(false);
// cin>>lT;
// while(lT--)
Solve();
}
/*
AC
http://10.199.227.101/contest/1005/problem/L1-5/submission-detail/2134



*/

l1-7(+,-搞错了)

2026天梯赛选拔赛-L1-7-简单计数(赛时-号搞错成加号了,绷不住了)

image

l1-9(二分范围设置错误)

AC

http://10.199.227.101/contest/1005/problem/L1-9/submission-detail/2144

赛时 r 写了r=N。(确实是比较搞笑了,因为 n 可能比 diff 小,diff 是一个值,N 是数组大小)

image

题目大意

题目描述
有一棵包含 nn 个节点的无向树,为了让它更有趣,你对它的 n1n-1 条边任意指定了方向,将其变成了一棵有向树。
现在你忘记了这棵树的结构和边的方向,但你找到了一份记录,上面写着给边指定方向后,所有节点对之间的可达性。具体来说,给定 nn 个长度为 nn 的 01 字符串。如果第 ii 个字符串的第 jj 个字符为 1,则表示在有向树中,节点 ii 可以到达节点 jj(必然存在一条从 iijj 的有向路径);如果为 0 则表示无法到达。特别地,一个节点总是可以到达它自己。

你的任务是根据这个可达性信息,判断是否存在合法的树结构与边的方向。如果存在,请构造并输出任意一个满足条件的解;如果不存在,请输出 No。本题为 Hard 版本,与 Easy 版本的唯一区别是 nn 的数据范围更大。

输入格式
第一行包含一个整数 tt1t1041 \le t \le 10^4),表示测试用例的数量。
每个测试用例的第一行包含一个整数 nn2n80002 \le n \le 8000),表示节点的数量。
接下来 nn 行,每行包含一个长度为 nn 的 01 字符串,表示可达性矩阵。
保证所有测试用例中 n2n^2 的总和不超过 800028000^2

输出格式
对于每个测试用例,如果存在解,输出 Yes,并在接下来的 n1n-1 行中输出构造的有向边。每行包含两个整数 xxyy,表示存在一条从 xx 指向 yy 的有向边。如果有多个解,输出任意一个即可。
如果不存在解,输出 No
(输出 YesNo 时不区分大小写,例如 yEsYES 都会被接受)。

样例输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
11
4
1000
1111
1010
0001
4
1111
0111
0010
0111
4
0011
0111
0011
0001
4
1000
0110
0010
1111
4
1000
0110
1010
1111
5
10000
01011
00111
00010
00001
5
10000
11000
10101
10111
00001
5
10000
01101
00100
01110
10001
4
1100
0100
0011
0001
4
1110
0100
0010
0101
3
100
111
101

样例输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Yes
2 3
2 4
3 1
No
No
Yes
2 3
4 1
4 2
No
No
Yes
2 1
3 1
3 5
4 3
No
No
Yes
1 2
1 3
4 2
Yes
2 3
3 1

样例解释
对于第一个测试用例,给定的矩阵表示:节点 1144 只能到达它们自己,节点 22 可以到达所有节点,节点 33 只能到达节点 1133。输出构造的边为 232 \to 3242 \to 4313 \to 1,这三条边构成了一棵合法的有向树,并且恰好满足上述可达性限制。
对于第二个测试用例,可以证明不存在任何合法的一棵树能够满足给定的可达性要求,因此输出 No

思路讲解

Codeforces Round 1086 (Div. 2)——CF-2208-D1. Tree Orientation (Easy Version)

这道题目的简单版本确实是比较 easy 的。

Codeforces Round 1085 (Div. 1 + Div. 2)——CF-2207-E1. N-MEX (Constructive Version)

我觉得和这道题目有一点像,都是要使用这个贪心方法,来解决一些问题

具体而言,我们要解决的,就是这个两个循环。我们发现,现在的问题在于,全部的 a,都要把全部的 b 给遍历一遍。这个太耗时耗力了。说白了,这个其实是一个连通性问题,即根节点 i,能不能通过一个中间点 b,访问到这个 a?当然,我们可以把

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
for (int i=1;i<=N;++i) {
vector<ll> nodes;
for (int j=1;j<=N;++j) {
if (j==i) continue;
if (Reach[i][j]=='1') {
nodes.push_back(j);
}
}
for (auto a:nodes) {
bool ok=true;
for (auto b:nodes) {
if (b==a) continue;
if (Reach[b][a]=='1') {
ok=false;
break;
}
}
if (ok) {
edges.push_back({i,a});
}
}
}

我们可以使用离线思想和排序思想,找到一种顺序,使得先处理顺序靠前的,后处理顺序靠后的,不会造成问题

我们可以按照 cnt_reach(即所有可到达点的数量) 降序排序

为什么降序排列有效? cnt_reach 最大的节点,其子树规模最大在合法树中,ii 的各个孩子的子树是互不相交的。最大子树的根必定是直系孩子(不可能被更小的子树"遮挡"),所以先处理最大的是安全的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
for (int i=1;i<=N;++i) {
vector<ll> nodes;
dsu.init(N);
for (int j=1;j<=N;++j) {
if (j==i) continue;
if (Reach[i][j]=='1') {
nodes.push_back(j);
}
}
ll guard=0;
sort(all(nodes),[&](const ll &a,const ll &b) {
return cnt_reach[a]>cnt_reach[b];
});
for (auto a:nodes) {
// 想恶意卡我?没门
if (guard>N) {
cout<<"No\n";
return;
}
if (!dsu.same(i,a)) {
guard+=cnt_reach[a];
edges.emplace_back(i,a);
dsu.merge(i,a);
for (auto b:node_reach[a]) {
dsu.merge(i,b);
}
}
}
}

AC代码

AC
https://codeforces.com/contest/2208/submission/366894699

心路历程(WA,TLE,MLE……)

image

1
2
3
4
5
6
7
8
9
10
11
_______________[ Stress test, testcase 6170 INPUT ]_______________
1
4
1101
0100
1111
0001

_______________[ Stress test, testcase 6170 OUTPUT]_______________
No

题目大意

题目描述
曾经有一棵包含 nn 个节点的无向树。为了让它更有趣,每条无向边都被随意赋予了一个方向,使其变成了一棵有向树。
现在树的结构和边的方向都遗失了,但给出了一份记录了这棵树连通性的矩阵。该连通性矩阵表现为 nn 个长度为 nn01 字符串。如果第 ii 行的第 jj 个字符是 1,则代表在原图中存在一条从节点 ii 到节点 jj 的有向路径;如果是 0 则代表无法到达。(特别地,任何节点都始终可以到达它自身)。
你需要根据给出的连通性矩阵,判断是否存在符合条件的有向树结构。如果存在,请构造并输出任意一种满足条件的边连接方式;如果不存在,则指出无解。

输入格式
第一行包含一个整数 tt (1t1041 \le t \le 10^4),表示测试用例的数量。
每个测试用例的第一行包含一个整数 nn (2n5002 \le n \le 500),表示树的节点数量。
接下来的 nn 行,每行包含一个长度为 nn 的仅由 01 组成的字符串。第 ii 个字符串的第 jj 个字符为 1 当且仅当节点 ii 可以到达节点 jj
保证所有测试用例中 n3n^3 的总和不超过 5003500^3

输出格式
对于每个测试用例,如果存在满足条件的有向树,输出 Yes(大小写均可),并在随后的 n1n-1 行中输出你构造的有向边。每行输出两个整数 xxyy,表示存在一条从 xx 指向 yy 的有向边(即 xyx \to y)。如果有多种合法的树结构,输出其中任意一种即可。
如果不存在满足条件的解,输出 No

样例输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
11
4
1000
1111
1010
0001
4
1111
0111
0010
0111
4
0011
0111
0011
0001
4
1000
0110
0010
1111
4
1000
0110
1010
1111
5
10000
01011
00111
00010
00001
5
10000
11000
10101
10111
00001
5
10000
01101
00100
01110
10001
4
1100
0100
0011
0001
4
1110
0100
0010
0101
3
100
111
101

样例输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Yes
2 3
2 4
3 1
No
No
Yes
2 3
4 1
4 2
No
No
Yes
2 1
3 1
3 5
4 3
No
No
Yes
1 2
1 3
4 2
Yes
2 3
3 1

样例说明
对于第一个测试用例,节点 1144 只能到达它们自身;节点 22 可以到达所有节点;节点 33 可以到达节点 1133。构造的有向边为 232 \to 3242 \to 4313 \to 1,该结构刚好满足所有可达性限制。
对于第二个测试用例,可以证明不存在任何一种满足给出连通性的树结构。

说白了,就是给你点与点之间的联通情况,你要重建出一颗有向的树,能够满足题目的要求。

思路讲解

如果只是做一个 D1 的话,还是非常 easy 的。

image

只要一个点比如说 6,不出现在其他 2 可达的点中(比如说图上的 4,3,5,1)的可达性表上,那么 2,6 直接相连。(⚠️注意,之所以这么连,是因为如果不连这条边,可达性就无法满足了,我们采用这一策略是为了连最少的边,达到题目要求)

这个做法的正确性是这样的,如果不连我们的算法所选择的边,可达性就无法满足了如果比我们的这个做法连的边更多,要么你一定会改变这个可达性规律,要么你会把一棵树,变成一个环。多也不行,少也不行,那我们的做法就是正确的了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vector<pair<ll,ll>> edges;
for (int i=1;i<=N;++i) {
vector<ll> nodes;
for (int j=1;j<=N;++j) {
if (j==i) continue;
if (Reach[i][j]=='1') {
nodes.push_back(j);
}
}
for (auto a:nodes) {
bool ok=true;
for (auto b:nodes) {
if (b==a) continue;
// 检查是否出现在其他点的可达性表上
if (Reach[b][a]=='1') {
ok=false;
break;
}
}
if (ok) {
edges.push_back({i,a});
}
}
}

当然,最后需要写一个校验程序,也非常 easy 啊,先写一个东西校验这个树结构是否正确,然后再直接跑一遍可达性判断,看看和题目给出的是否一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
do {
if (SZ(edges)!=N-1) {
cout<<"No\n";
return;
}
vector<vector<ll>> g(N+2);
for (auto [u,v]:edges) {
merge(u,v);
g[u].push_back(v);
}
// 对化为无向图后,树的可达性作校验
set<ll> st;
for (int i=1;i<=N;++i) {
ll fai=find(i);
st.insert(fai);
}
if (SZ(st)!=1) {
cout<<"No\n";
return;
}
// 直接模拟,校验我们所构造
vector<vector<char>> R(N+2,vector<char>(N+2,'0'));
for (int i=1;i<=N;++i) {
queue<ll> q;
q.push(i);
R[i][i]='1';
while (!q.empty()) {
ll a=q.front();
q.pop();
for (auto v:g[a]) {
if (R[i][v]=='1') {
continue;
}
R[i][v]='1';
q.push(v);
}
}
}
for (int i=1;i<=N;++i) {
for (int j=1;j<=N;++j) {
if (Reach[i][j]!=R[i][j]) {
cout<<"No\n";
return;
}
}
}
}while (false);

AC代码

AC
https://codeforces.com/contest/2208/submission/366753217

心路历程(WA,TLE,MLE……)

基本情况

image

这个打的不算太好。

但是,说实话嘛,这个 C 确实没对上脑电波。 Codeforces Round 1086 (Div. 2)——CF-2208-C. Stamina and Tasks(赛时 AC)(确实,乘法衰减,概率 dp,都是用这个反向居多) 所以说做的比较慢。

这个 A 唐完了,我以为要构造那个矩阵,就过只要判断 yes,no。还因为 > 写成了 ≥ WA 了一发。

image

心得感悟

还是要看清楚是不是需要构造。