20230812比赛

T1 【NOIP2014模拟】逻辑的连通性

Description

假如有命题p 一定能推出命题q,则称p 是q 的充分条件,q 是p 的必要条件。
特别的,当p 既是q 的充分条件,又是q 的必要条件时,称p 和q 互为充要条件
现在有n 个命题,其中一些是另一些的充分条件。请问有多少对命题互为充要条件?

Input

第一行三个正整数n,m,分别表示命题数、已知关系数
接下来m 行,每行两个正整数p 和q,表示命题p 是命题q 的充分条件

Output

仅一行,一个整数,表示充要条件的对数

Sample Input

5 5
1 3
3 2
2 1
4 5
5 4

Sample Output

4
样例说明:
4 对充要条件分别是(1, 2)、(2, 3)、(1, 3)、(4, 5)

Data Constraint

对于10% 的数据,n <= 10;m <= 50
对于40% 的数据,n <= 500;m <= 1000
对于另外10% 的数据,数据中保证没有重边且m = n^2
对于100% 的数据,n<= 50000;m <= 600000

就是一个强连通分量,对于大小为 \(n\) 的强连通分量,它的充要条件数量为 \(C^2_n=\frac{n(n-1)}{2}\)

用 Tarjan 过了。

#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
ll head[600010];
ll nxt[600010];
ll to[600010];
ll cnt;
ll ans;
void addEdge(ll u, ll v) {
cnt++;
to[cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt;
}
ll dfn[50010];
ll col[50010];
ll co[50010];
ll ct[600010];
ll top;
ll cover;
ll num;
ll n, m;
void tarjan(ll u) {
dfn[u] = ++num;
col[u] = num;
ct[++top] = u;
for(ll i = head[u]; i; i = nxt[i]) {
ll v = to[i];
if(!dfn[v]) {
tarjan(v);
col[u] = min(col[u], col[v]);
}
else if(!co[v]) {
col[u] = min(col[u], dfn[v]);
}
}
if(dfn[u] == col[u]) {
co[u] = ++cover;
ll cnt = 1;
while(ct[top] != u) {
co[ct[top]] = cover;
top--;
cnt++;
}
ans += cnt * (cnt - 1) / 2;
top--;
}
}
int main() {
scanf("%lld %lld", &n, &m);
for(ll i = 1; i <= m; i++) {
ll u, v;
scanf("%lld %lld", &u, &v);
if(u != v) addEdge(u, v);
}
for(ll i = 1; i <= n; i++) {
if(!dfn[i]) {
tarjan(i);
}
}
/*=======================Debug Begin=========================*/
// for(ll i = 1; i <= n; i++) {
// printf("id:%lld co:%lld\n", i, co[i]);
// }
/*=======================Debug Finish========================*/
printf("%lld", ans);
}

T2 【NOIP2014模拟】树的连通性

Description

给定一个n 个点的无向图,保证联通且无环无重边,每个点上有一个可修改的权值,每次断掉一条边、修改某个节点上的权值或询问两个点之间的连通性。

Input

输入数据的第一行是两个数N、M,点的个数和操作的个数
接下来N 行,每行一个正整数,表示每个点上的初始权值
接下来若干行,每行两个数x 和y,表示x 和y 之间有一条无向边
接下来M 行,每行三个数t x y,含义如下:
t=1 将x 和y 之间的边断开(保证存在)
t=2 查询x 和y 是否联通,若联通,输出他们权值的乘积,否则输出他
们权值的和
t=3 将点x 上的权值修改为y
为了保证算法强制在线,设上一次查询的结果为lastans,则最后M 行输入
数据的x 和y 的实际值= 输入值xor lastans, lastans 的初始值为0

Output

对于每个查询操作,输出一行一个数,要求如上所示

Sample Input

8 5
1
2
3
4
5
6
7
8
1 2
1 5
4 2
3 2
5 7
5 6
8 5
1 5 1
2 4 6
2 11 9
3 0 15
2 0 11

Sample Output

10
3
20
样例说明:
先断开1 和5 之间的边,然后查询4 和6 的连通性。由于不联通,所以答案是10
然后查询1 和3 的连通性,由于联通,所以答案是3
然后将3 号节点上的数值修改为12
最后查询3 和8 的连通性。由于不联通,所以答案是它们的权值和20

Data Constraint

对于10% 的数据,没有断边操作
对于40% 的数据,n<=1000; m<= 500
对于100% 的数据,n <= 200000; m <= 200000,0 < 点上的权值 <=1000

我暴力过的,正解不是我。

我们使用前向星连边,认较小的为爹。跑 dfs 标记深度,然后对于删边操作,就直接把儿子的父亲标为自己(不用更新深度,因为查询时发现找到子树的根节点了,就是两个点不连通),对于查询联通操作,直接判断哪个边深度小,深度小的边向上爬(如果深度一样随便一个爬),直到两个点重合为止。如果找到根节点(包括子树的根节点),就结束,不连通。

对于修改权值操作,直接改。

#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
ll n, m;
struct node {
ll val;
ll fa;
ll dep;
} a[200010];
ll head[600010];
ll nxt[600010];
ll to[600010];
ll cnt;
ll lastans;
void addEdge(ll u, ll v) {
cnt++;
to[cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt;
}
bool query(ll u, ll v) {
while(u != v) {
if(a[u].dep > a[v].dep) swap(u, v);
if(a[v].fa == v) return false;
v = a[v].fa;
}
return true;
}
void dfs(ll u) {
for(ll i = head[u]; i; i = nxt[i]) {
ll v = to[i];
a[v].dep = a[u].dep + 1;
dfs(v);
}
}
int main() {
scanf("%lld %lld", &n, &m);
for(ll i = 1; i <= n; i++) {
scanf("%lld", &a[i].val);
}
for(ll i = 1; i < n; i++) {
ll u, v;
scanf("%lld %lld", &u, &v);
if(u > v) swap(u, v);
addEdge(u, v);
a[v].fa = u;
}
a[1].fa = 1;
a[1].dep = 1;
dfs(1);
for(ll i = 1; i <= m; i++) {
ll op, x, y;
scanf("%lld %lld %lld", &op, &x, &y);
x ^= lastans;
y ^= lastans;
if(op == 1) {
if(x > y) swap(x, y);
a[y].fa = y;
} else if(op == 2) {
if(query(x, y)) {
lastans = a[x].val * a[y].val;
printf("%lld\n", a[x].val * a[y].val);
} else {
lastans = a[x].val + a[y].val;
printf("%lld\n", a[x].val + a[y].val);
}
} else {
a[x].val = y;
}
}
}

T3 【NOIP2014模拟】图的连通性

Description

“有一个无向图,每次断掉一条边,并询问两个点时候联通,你会维护么?
” 琼很认真地问。
“为什么要知道这个呢?”
“我们总要知道自己是否身陷囹囵……你必须立刻告诉我答案哦”

Input

测试数据的第一行是三个正整数n、m、t, 表示无向图有n 个点,m 条边和t 个操作
接下来m 行,每行两个正整数x、y,表示x 和y 之间有一条边(允许存在重边)
接下来t 行,每行三个整数w x y,w=1 时表示将x 和y 之间的边剪断(保证存在这样一条边)。w=2 时表示查询x 与y 时候联通,是输出1, 否则输出0。
为了保证强制在线,最后t 行输入数据中的x 和y 必须要异或(C/C++ 中的^ 运算,PASCAL 中的xor)执行当前操作之前图中剩余的边数,才能得到实际问题中的x 和y

Output

对于每次询问,输出一行,一个数,1 或0,1 表示联通,0 表示不联通。

Sample Input

5 9 6
1 2
2 3
1 3
1 3
3 1
1 5
1 4
3 5
5 4
1 12 13
2 13 12
1 9 12
2 3 2
1 4 6
2 7 5

Sample Output

1
0
1
样例说明:
6 个操作依次为:
1. 断掉5 和4 之间的边
2. 查询5 和4 之间的连通性
3. 断掉1 和4 之间的边
4. 查询5 和4 之间的连通性
5. 断掉1 和3 之间的边
6. 查询1 和3 之间的连通性

Data Constraint

对于40% 的数据,n <=100; m<=1000; t<=500
对于100% 的数据,n<=100000; m<=150000; t<=100000

Hint

关于操作w=1将x 和y 之间的边剪断是删掉一条边.

被坑了QWQ。

比赛时想到了正解,但是以为要在线。其实是强制离线。因为当前操作之前图中剩余的边数,可以很容易算出来。我们用并查集,既然并查集删边难,那我们就让时光倒流,先处理出最后局面,对于删边操作,就连边,对于查询操作,就并查集查询。

然后介绍一个链式前向星取反向边的一种简易的方法,建图时要把cnt置为1,且要保证反向边紧接着正向边建立。则一条id为 \(i\) 边的反向边id为 i^1

#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
ll n, m, t;
struct node {
ll val;
} a[200010];
ll fa[200010];
ll head[600010];
ll nxt[600010];
bool go[600010];
ll to[600010];
ll cnt = 1;
ll ans[600010];
ll last;
struct op {
ll w, x, y;
} q[100010];
void addEdge(ll u, ll v) {
cnt++;
to[cnt] = v;
go[cnt] = 1;
nxt[cnt] = head[u];
head[u] = cnt;
}
void delEdge(ll u, ll v) {
for(ll i = head[u]; i; i = nxt[i]) {
ll y = to[i];
if(y == v && go[i]) {
go[i] = 0;
go[i^1] = 0;
break;
}
}
}
ll find(ll x) {
if(fa[x] == x) return fa[x];
return fa[x] = find(fa[x]);
}
int main() {
// freopen("graph4.in", "r", stdin);
// freopen("graph.out", "w", stdout);
scanf("%lld %lld %lld", &n, &m, &t);
ll last = m;
for(ll i = 1; i <= n; i++) fa[i] = i;
for(ll i = 1; i <= m; i++) {
ll u, v;
scanf("%lld %lld", &u, &v);
addEdge(u, v);
addEdge(v, u);
}
for(ll i = 1; i <= t; i++) {
ll w, x, y;
scanf("%lld %lld %lld", &w, &x, &y);
x ^= last;
y ^= last;
q[i].x = x;
q[i].y = y;
q[i].w = w;
if(w == 1) {
delEdge(x, y);
last--;
}
}
for(ll u = 1; u <= n; u++) {
for(ll j = head[u]; j; j = nxt[j]) if(go[j]) {
ll v = to[j];
ll x = find(u);
ll y = find(v);
fa[x] = y;
}
}
for(ll i = t; i >= 1; i--) {
if(q[i].w == 1) {
ll x = find(q[i].x);
ll y = find(q[i].y);
fa[x] = y;
} else {
ans[i] = (find(q[i].x) == find(q[i].y));
}
}
for(ll i = 1; i <= t; i++) {
if(q[i].w == 2) {
printf("%d\n", ans[i]);
}
}
}

T4 【NOIP2019模拟11.07】极好的问题

Description

Input

从文件 awesome.in 中读入数据。
第一行 2 个用空格隔开的整数 n, P。
第二行 n 个用空格隔开的整数 A1, . . . , An。

Output

输出到文件 awesome.out 中。
输出一行一个整数,表示极好的三元组的数目。

Sample Input

Sample Input1
5 5
1 1 1 2 3
Sample Input2
4 5
7 4 2 2

Sample Output

Sample Output1
2
【样例 1 解释】
唯二的极好的三元组为 (1, 1, 1) 和 (1, 2, 3)。
Sample Output2
2
【样例 2 解释】
(2, 2, 4) 和 (2, 4, 7) 都是极好的三元组。

Data Constraint

极好的三元组有以下三种情况:

  • \(a,a,a\)
  • \(a,a,b\)
  • \(a,b,c\)

对于第一种情况,可以统计每个出现次数大于等于 3 的数的三次方 (在 模P 意义下) 是否等于 1 。

对于第二种情况,统计每个出现次数大于等于 2 的数的二次方乘去重序列中的其它数 (在 模P 意义下) 是否等于 1

对于第三种情况,预处理出所有两个不同的数的乘积,并在预处理中特判 \(a\times b=a'\)\(a\times b=b'\) 的情况。因为此时等价于情况二,不特判会导致重复计算。然后求出每个数的乘法逆元在这些乘积中出现的次数。这些次数之和会反复出现三次,所以记得除以3。

#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
ll n, p, ans;
ll a[40000];
ll b[40000];
ll g[40000];
ll inv[40000];
ll an[2334*2334];
void solve(ll l, ll r) {
if(l == r) return;
ll mid = (l + r) >> 1;
solve(l, mid);
solve(mid + 1, r);
ll pos1 = l, pos2 = mid + 1;
for(ll i = l; i <= r; i++) {
if(pos2 > r || (pos1 <= mid && a[pos1]<a[pos2])) {
g[i] = a[pos1++];
}
else {
g[i] = a[pos2++];
}
}
for(ll i = l; i <= r; i++) {
a[i] = g[i];
}
}
ll qpow(ll x, ll y) {
if(y == 0) return 1;
if(y % 2 == 0) {
ll res = qpow(x, y / 2) % p;
return (res % p) * (res % p) % p;
}
return (x % p) * (qpow(x, y - 1) % p) % p;
}
int main() {
freopen("awesome.in", "r", stdin);
freopen("awesome.out", "w", stdout);
scanf("%lld %lld", &n, &p);
for(ll i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
a[n + 1] = -114514;
solve(1, n);
memcpy(b, a, sizeof(a));
ll e = unique(b + 1, b + 1 + n) - b - 1;
for(ll i = 1; i <= e; i++) {
inv[i] = qpow(b[i], p - 2);
}
ll last = -1919810, num = 0;
for(ll i = 1; i <= n + 1; i++) {
if(a[i] != last) {
if(num >= 2) {
for(ll j = 1; j <= e; j++) {
if(b[j] != last && ((last % p) % p * (last % p) % p * (b[j] % p)) % p == 1) ans++;
}
}
if(num >= 3) { // 之所以不用else if,因为在num>=2中处理了a*a*b的情况,这里虽然num>=3,但也可以舍去第一个,变成a*a*b
if(((last % p) % p * (last % p) % p * (last % p)) % p == 1) ans++;
}
last = a[i];
num = 1;
} else {
num ++;
}
}
ll tot = 0, res = 0;
for(ll i = 1; i <= e; i++) {
for(ll j = i + 1; j <= e; j++) {
an[++tot] = (b[i] % p) % p * (b[j] % p) % p;
if(an[tot] == inv[i]) res--;
if(an[tot] == inv[j]) res--;
}
}
sort(an + 1, an + 1 + tot);
for(ll i = 1; i <= e; i++) {
ll rig = upper_bound(an + 1, an + 1 + tot, inv[i]) - an;
ll lef = lower_bound(an + 1, an + 1 + tot, inv[i]) - an;
res += rig - lef;
}
printf("%lld", res/3 + ans);
}
posted @   ZnPdCo  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示