2021.8.12 比赛题整理

UPDATE 21/08/26

更改题面、转移链接等。


2021.8.12 新初一暑假测试六

链接集合

1 / 18,260 / 400。

总结

整体还可以吧,该拿的分都尽量拿了,也证明最近的训练有效 (我近来的效率是真的低),除了 T4。

排名我是真没想到 ,考试的时候看见教练对着我笑还来问候我,自己还是做了心理斗争的,最后毅然决然选择比赛划水

小视野主页的文章不够看啊,催更。

T1 一遍 ac;

T2 纯暴力 60 分;

T3 知道是强连通分量,推的时候按桥来推,这个不应该错;

T4 做过,栽在了排列组合上,忘记答案除以二了。

检查很重要!还是要沉下心来比较好,当时就是信心满满检查 T4,直接忽略了错误代码。

T1

题目概述

太水了,懒得说了。

这是一道大水题,签到题,所以为什么刚开始有一半人没 ac?

话说这两次考试第一题都是送给你的??

T2

题目概述

初始 n 个点互相独立,之间没有连边(则初始图为一个 DAG)。输入 t 条边,若加上这条边之后图不再是一个 DAG,ans 加一,最后输出 ans。

也就是求那些能让图成环的边的数量。

求解

这个。。记忆化搜索。

教练说出了搜索,我就直接暴力了。。

m p u , v ← t r u e mp_u,_v \gets true mpu,vtrue 时,表示能从 u 走到 v。反之, m p u , v ← f a l s e mp_u,_v \gets false mpu,vfalse 表示还不能从 u 走到 v。

  1. 我们初始化一下数组—— m p i , i ← t r u e mp_i,_i \gets true mpi,itrue

  2. 没当输入一条边 ( u , v ) (u,v) (u,v) 时,我们判断 i f ( m p v , u = = 1 ) if(mp_v,_u==1) if(mpv,u==1)——能够从 v 出发走到 u,如果可以,就统计这条边。

    因为我们发现,如果要让 u 和 v 处于一个环内,那么必须要满足 m p u , v = = m p v , u = = 1 mp_u,_v==mp_v,_u==1 mpu,v==mpv,u==1——能从 v 出发走到 u,反之,也要可以。(此题灵魂)

  3. 如果不可以,我们就可以让 m p u , v ← t r u e mp_u,_v \gets true mpu,vtrue

    这时候记忆化搜索就体现出来了。如果我们已经赋值过 m p u , v mp_u,_v mpu,v 了,那就可以直接跳过下面的赋值操作,处理下一条边。如果没有,我们就给他赋值。

  4. 赋值的时候,我们可以连其他的边一起赋值, 这样就可以在后面遍历到赋值过的边时大大减小处理它的时间,优化代码以及时间复杂度。

    我们发现,如果连接了 ( u , v ) (u,v) (u,v) 这条边,仅仅会对两部分点造成影响,而我们一起赋值的,也是他们。

    假设能从点 i 走到点 u,点 j 走到点 v,那么当我们将 ( u , v ) (u,v) (u,v) 连上之后,i 就也能和 j 互达了。

    于是,我们找到所有的 i 和 j,让他们都连上, m p i , j ← t r u e mp_i,_j \gets true mpi,jtrue

还是要学会灵活运用记搜啊,能打暴力或写 dp 的时候想想记搜吧。

#include<bits/stdc++.h>
using namespace std;

int n, t;
const int maxn = 300;
int mp[maxn][maxn], ut[maxn], vt[maxn];
int ans;

int main ()
{
	cin >> n >> t;
	for (int i = 1; i <= n; i++) mp[i][i] = 1;
	while (t--)
	{
		int u, v;
		cin >> u >> v;
		int tot = 0, tot2 = 0;
		if (mp[v][u]) {ans++;continue;}
		if (mp[u][v]) continue;
		for (int i = 1; i <= n; i++) 
		{
			if (mp[i][u]) ut[++tot] = i;
			if (mp[v][i]) vt[++tot2] = i;	
		}	
		for (int i = 1; i <= tot; i++)
		for (int j = 1; j <= tot2; j++)mp[ut[i]][vt[j]] = 1;
	} 
	cout << ans << endl;
	return 0;
}

T3 稳定婚姻

智推 yyds!推给我了,我点开了,题读了,但就!是!没!做!

题目概述

P1407 稳定婚姻

有 n 对夫妻,以及 m 对前尘恋情

若一对夫妇离婚,女的会去找前 npy,男的也会去找前 npy,然后各个 npy 的另一半就也会和他们离婚去找属于自己的前 npy…

要是最后每个人都成功地找到了新的伴侣,那么最初离婚的那对夫妇的夫妻关系就被称作是不安全的(什么鬼),反之就是安全的(???)。

求解

我一开始真的觉得不就是判桥吗?

但是实际上并不是桥,准确来说,我们是要在夫妻之间没有连边的情况下,判断图是否连通、成环, 如果连通,那么就是不安全的,反之就是安全的。

同时在保证代码、时间复杂度尽量低的前提下,最佳也是最简单的算法当然是短小而精悍的强连通分量

建边

阅览了无数题解,翻遍了最优解代码, 我发现关于建边实际上就两种方法:

  1. 夫妻之间不建边(这里我们把夫妻看为一体,一个单独的节点),对于前尘恋情 b o y ← g i r l boy \gets girl boygirl 或者 g i r l ← b o y girl \gets boy girlboy

  2. 夫妻之间 b o y ← g i r l boy \gets girl boygirl夫妻各为一个独立的节点),前尘恋情 g i r l ← b o y girl \gets boy girlboy(反过来也可以,只要两个不同即可)。

无论是以上哪一种,只要夫妻最后在同一个强连通分量中,那么这对关系就是不安全的

反之就是安全的咯~

map

这道题里涉及到了 STL 中的 map。

wtcl,才注意到还有这么强大的 1 v 1 对应关系存储。

我比赛没用 map (没意识用这) : 输入前尘恋情的时候循环一个个找,找到再建边,但这很明显会一发 TLE。

而 map,作为 STL 大家庭中的一员,拥有自动建立 key - value 的对应。 key 和 value 可以是任意你需要的类型,包括自定义类型。

map<string, int> mp1, mp2;

这是这道题我们需要的,定义了之后,我们就可以用一个字符串 string 去指向一个(一对夫妻或一个人的)编号 int

char c1[10], c2[10];
scanf ("%s %s", c1, c2);
add (mp1[c1], mp2[c2]);

具体可以像这样直接当成数组来使用 (好强大,Orz)

#include<bits/stdc++.h>
using namespace std;

int n, m;
const int maxn = 25005;
int cnt, hd[maxn];
struct node{
	int to, nxt;
}e[maxn * 2];
struct inpt{
	string namv;
	string nama;
}in[maxn];
int nt, root, tmp, top, col;
int dfn[maxn], low[maxn], vis[maxn], co[maxn];
int cut[maxn], st[maxn], siz[maxn];
map<string, int> mp1, mp2;

int read ()
{
	int x = 1, s = 0;
	char ch = getchar ();
	while (ch < '0' or ch > '9') {if (ch == '-') x = -1; ch = getchar ();}
	while (ch >= '0' and ch <= '9') {s = s * 10 + ch - '0'; ch = getchar ();}
	return x * s;
}

void add (int u, int v)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else if (!co[v]) low[u] = min (low[u], dfn[v]);
	}
	if (dfn[u] == low[u])
	{
		siz[++col]=1;
		co[u] = col;
		while (st[top] != u)
		{
			co[st[top]] = col;
			siz[col]++;
			top--;
		}
		top--;
	}
}

int main ()
{
	n = read ();
	for (int i = 1; i <= n; i++)
	{
		int u, v;
		char c1[10], c2[10];
		scanf ("%s %s", c1, c2);
		mp1[c1] = i;
		mp2[c2] = i;
	}
	m = read ();
	for (int i = 1; i <= m; i++)
	{
		char c1[10], c2[10];
		scanf ("%s %s", c1, c2);
		add (mp1[c1], mp2[c2]);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i]) tarjan (i);
		if (siz[co[i]] <= 1) printf ("Safe\n");
		else printf ("Unsafe\n");
	}
	return 0;
}

T4 矿场搭建

Emmm…做过的一道题,多亏了他我才能位居榜首(膜拜第二名大佬 szx,唯一 ac T3)。

题目概述

P3225 矿场搭建

把每一个开采矿的地方看成一个点 (有一说一钱真多),问至少要在几个点上建逃生出口以及有几种建立出口的方式,

使建了出口之后无论哪一个节点炸了(出口也有可能炸,且只会炸掉一个节点)之后其他所有点的工人都能从某一个逃生出口逃出来。

求解

很明显,一道点双联通分量

首先,如果原图就是一个点双,也就是没有割点,任意两点互通,我们就只需要建两个出口——一个炸了走另一个。

如果原图不是点双,那么我们就把原图分成若干个点双,逐一求解。

先 Tarjan,求出每一个割点,然后 dfs,把原图分成若干点双。

我们每找一个点双,就边找边统计该点双中的割点数及非割点数。

  1. 割点数为 0,建两个出口(和整体为一个点双原因相同)。

  2. 割点数为 1,建一个出口(割点没炸走割点,割点炸了走点双里面的出口)。

  3. 割点数为 2,不用建了(一个割点炸了走另一个割点)。

总数量直接加数量,总方案数乘非割点数的组合情况(每次初始化为 1):

  1. 割点数为 0: a n s s × s i z u anss \times siz_u anss×sizu

  2. 割点数为 1: a n s s × s i z u × ( s i z u − 1 ) / 2 anss \times siz_u \times (siz_u-1) /2 anss×sizu×(sizu1)/2

记得除以 2!!!排列组合!!!

#include<bits/stdc++.h>
using namespace std;

#define int long long
int n, m;
const int maxn = 1005;
int cnt, hd[maxn];
struct node{
	int to, nxt;
}e[maxn * 2];
int dfn[maxn], low[maxn], cut[maxn], co[maxn];
int ans, anss;
int tmp, root, col;
int ct, siz;

void init ()
{
	n = cnt = ans = tmp = ct = siz = col = 0;
	anss = 1;
	memset (hd, 0, sizeof hd);
	memset (e, 0, sizeof e);
	memset (dfn, 0, sizeof dfn);
	memset (low, 0, sizeof low);
	memset (cut, 0, sizeof cut);
	memset (co, 0, sizeof co);
}

int read ()
{
	int x = 1, s = 0;
	char ch = getchar ();
	while (ch < '0' or ch > '9') {if (ch == '-') x = -1; ch = getchar ();}
	while (ch >= '0' and ch <= '9') {s = s * 10 + ch - '0'; ch = getchar ();}
	return x * s;
}

void add (int u, int v)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

void tarjan (int u, int fa)
{
	dfn[u] = low[u] = ++tmp;
	int chil = 0;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			chil++;
			tarjan (v, u);
			low[u] = min (low[u], low[v]);
			if ((dfn[u] <= low[v] and u != root) or (u == root and chil > 1)) 
			{
				cut[u] = 1;
			}
		}
		else if (v != fa) low[u] = min (low[u], dfn[v]);
	}
}

void dfs (int x)
{
	co[x] = col;
	siz++;
	for (int i = hd[x]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (cut[v] and co[v] != col) 
		{
			co[v] = col;
			ct++;	
		}	
		if (!co[v])
		{
			dfs (v);
		}
	} 
}

signed main ()
{
	int T = 0;
	m = read ();
	while (m != 0)
	{
		init ();
		T++;
		for (int i = 1; i <= m; i++)
		{
			int u, v;
			u = read (), v = read ();
			add (u, v), add (v, u);
			n = max (n, max (u, v));
		}
		for (int i = 1; i <= n; i++)
		{
			if (!dfn[i])
			{
				root = i;
				tarjan (i, -1);
			}
		}
		for (int i = 1; i <= n; i++)
		{
			if (cut[i] or co[i]) continue;
			col++;
			siz = ct = 0;
			dfs (i);
			if (ct == 0)
			{
				ans += 2;
				anss *= (siz * (siz - 1)) / 2;
			}
			if (ct == 1)
			{
				ans += 1;
				anss *= siz;
			}
		}
		printf ("Case %lld: %lld %lld\n", T, ans, anss);
		m = read ();
	}
	return 0;
}

—— E n d End End——

阳 和 启 蛰 , 枯 木 逢 春 。 阳和启蛰,枯木逢春。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值