2021年8月12日模拟赛题解

更好的阅读体验

T1 出现次数超过一半的数

损坏的传送门

题目大意

给出一个含有 n0<n<=1000n(0 < n <= 1000) 个整数的数组,请找出其中出现次数超过一半的数。 数组中的数大于 50-50 且小于 5050

如果存在这样的数,输出这个数;否则输出 no

解题思路

显然,简单的模拟题,用一个桶记录一下就行了。

由于数组的下标不能为负数,所以全员加上 100100 就行了,输出时在减去 100100

就没了。。。

怎么可能!!!

来一道加强版

洛谷 P2397

共有 nn 个正整数 aia_i ,要你找一个众数,这个众数出现次数超过了一半。

对于 100%100\% 的数据,1n2×1061\le n \le 2\times 10^6ai[1,231)a_i \in [1,2^{31})

这就需要介绍 摩尔投票法 了。

注意洛谷上的这道题是保证有答案的,如果不保证有答案,就不能用摩尔投票法了。\color{red}{注意洛谷上的这道题是保证有答案的,如果不保证有答案,就不能用摩尔投票法了。}

摩尔投票法的基本思想很简单,在每一轮投票过程中,从数组中找出一对不同的元素,将其从数组中删除。这样不断的删除直到无法再进行投票,如果数组为空,则没有任何元素出现的次数超过该数组长度的一半。如果只存在一种元素,那么这个元素则可能为目标元素。

为了便于理解,我们想象一个情景,有一堆人在打架,我们假设每一个人的战斗力都是 1111。也就是 AA 打死 BBAA 也会死。

假设只有 22 波人(我们把不是众数的数字看作一个数)且各占二分之一,显然继续打下去两边人都会死光。

但是我们现在有 22 波人,其中一波人特别多(一半以上),显然这波人必然是赢家,虽然不知道打完还剩几个,但一定会有剩下的。因为一换一。若第二波人有 xx 个,则第一波人有(nxn-x)个且 nx>xn-x>x。那么最后剩下的人就是 n2xn-2x 个。假设这 22 波人头上顶着 22 个不同的数字,这个剩下的人的数字就是众数。

以上用于帮助理解代码。

所以我们可以把摩尔投票法看作一种抵消的思想,当前数字进场,然后和上一个判断,如果是同一个数计数器++++,如果不是计数器--

当计数器等于 00 时,ansans 重新登记为新输入的数,进行抵消的操作,最后剩下的一定是众数(但众数必须满足大于总数的一半)。

摩尔投票法其实就是随便找一个数 xx,将他跟和他不同的数抵消:

  • 如果 xx 不是答案,那么跟他不一样的数一定比他多,所以最后一定会找到答案的。

  • 如果 xx 是答案,那么跟他不一样的数一定比他少,所以最后的答案一定会是他自己。

AC CODE

原题

#include<bits/stdc++.h>
using namespace std;
int n;
int a, b[10000];
bool flag = 0;
signed main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i)
	{
		scanf("%d", &a);
		b[a + 100] ++;
	}
	for(int i = 1; i <= 1000; ++i)
	{
		if((n % 2 == 0 && b[i] > n / 2) || (n % 2 == 1 && b[i] >= (n + 1) / 2))
		{
			printf("%d\n", i - 100);
			flag = 1;
			return 0;
		}
	}
	if(!flag)
	{
		printf("no\n");
	}
	return 0;
}

洛谷 P2397

#include <bits/stdc++.h>
using namespace std;
int n, x, ans, y = 0;
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &x);
		if (y == 0)
			ans = x;
		if (ans == x)
			y++;
		if (ans != x)
			y--;
	}
	printf("%d", ans);
	return 0;
}

T2 添边问题

损坏的传送门

题目大意

没有环的有向图称为有向无环图,这是一个多么美好的结构吖。

如果有一张有 NN 个点的有向图,我们可能需要删掉一些边使它变成一张有向无环图。假设初始时我们只有 NN 个互不相连的点,当然它也是一张有向无环图。依次给出 TT 条边和每条边的方向。 每给出一条边就要立即决定是否要加入这一条边,使得这张图始终是一张有向无环图(意思是:按顺序处理每条边,能加就加,让你模拟这个过程,自环不能加入)。计算在满足要求的情况下一共有多少条边没有被加入。如果所有边都可以加入这张图则输出 00

注意有向无环图是可以存在重边的。即:若原图有边 121 \to 2,则新边 121 \to 2 可被加入。

对于 100%100\% 的数据,n<=250n<=250T<=100000T<=100000

解题思路

用一个数组 Map 记录 uu 是否可以到达 vv

对于一组输入的边 uvu \to v,其需要经历一下流程:

  1. 如果 vv 可达 uu,说明如果建了 uvu \to v,就形成了一个环,答案加 11
  2. 如果已经有一条边 uvu \to v 了,那么当前输入的这条边不再需要执行任何操作,直接跳过。
  3. 用两个临时数组 aabb 记录有哪些点可达点 uu,以及点 vv 可达哪些点。
  4. 双重循环遍历两个数组,将 Map[ai][bj]Map[a_i][b_j] 标记为 11,因为将边 uvu \to v 加上后,这些点都可以到达。

其实这道题用记忆化搜索也可以。

就是对于一组输入的边 uvu \to vdfs 一下,判断 vv 是否可达 uu

  • 若可以到达,则不加上这条边。
  • 若不能到达,则加上这条边。

dfs 的话用数组记录答案就行了,避免重复地 dfs

其实这题还可以改改,mango13mango13 曰:可以将本题改成强制离线(这题是在线判断的)的。

以下目前我的思路,期待被 HACK

先用有向图 Tarjan 判环,然后循环一下所有的边,记录强连通分量里有多少条边。

则将强连通分量拆成有向无环图,就需要拆 mn+1m-n+1 条边(mm 是强连通分量里的边的数量,nn 是强连通分量里的点的数量)。

正在创数据中~~~

等待更新~~~

AC CODE

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

int Map[251][251];
int a[251], b[251];
int al, bl, ans;
int n, m, u, v;

signed main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
		Map[i][i] = 1;
	while (m--)
	{
		cin >> u >> v;
		if (Map[v][u])
		{
			++ans;
			continue;
		}
		if (Map[u][v])
			continue;
		al = bl = 0;
		for (int i = 1; i <= n; ++i)
			if (Map[i][u])
				a[++al] = i;
		for (int i = 1; i <= n; ++i)
			if (Map[v][i])
				b[++bl] = i;
		for (int i = 1; i <= al; ++i)
			for (int j = 1; j <= bl; ++j)
				Map[a[i]][b[j]] = 1;
	}
	cout << ans;
	return 0;
}

T3 稳定婚姻

传送门

题目大意

假设有婚姻 ii,且 BiB_i 和其配偶 GiG_i 感情不和,于是 BiB_iGjG_j 旧情复燃,进而 BjB_j 因被戴绿帽而感到不爽,联系上了他的初恋情人 GkG_k ……一串串的离婚事件像多米诺骨牌一般接踵而至。若在 BiB_i 和Gi离婚的前提下,这 2n2n 个人最终依然能够结合成 nn 对情侣,那么我们称婚姻 ii 为不安全的,否则婚姻i就是安全的。给定所需信息,你的任务是判断每对婚姻是否安全。

对于 100%100\% 的数据,1n40001≤n≤40000m200000≤m≤20000

简单来说就是绿与被绿的故事。

解题思路

我们可以将所有有联系的人连起来,可以发现出现了一些环,而处在环中的几对夫妻都可以更换伴侣,也就是题目中所说的婚姻不安全。那么我们找出这些环,判断哪些夫妻处在环中即可。

对于找环,我们想到了 Tarjan 求强连通分量,但是这个算法是在有向图上进行的,于是我们尝试给我们连接出的无向图定向,发现只要按照 从 男到女 的顺序,男女交替就可以 Tarjan 判出环来。

对于 Tarjan,,不会的 出门右转 不谢。

所以我们可以这样建图:

  • 夫妻:girlboygirl \to boy
  • 情人:boygirlboy \to girl

当然,反过来也可以。

对于字符串读入,直接用 map 映射就行了,不会的出门右转(啥都没有~)不谢。

AC CODE

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

int n, m;

int flag = 0;

int a[_], b[_];

int tot, head[_], to[_ << 1], nxt[_ << 1];

int cnt_node, cntn, dfn[_], low[_], id[_], siz[_];

stack<int> s;

map<string, int> mp;

void add(int u, int v)
{
	to[++tot] = v;
	nxt[tot] = head[u];
	head[u] = tot;
}

void tarjan(int u)
{
	dfn[u] = low[u] = ++cnt_node;
	s.push(u);
	for(int i = head[u]; i; i = nxt[i])
	{
		int v = to[i];
		if(!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if(!id[v])
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
	if(dfn[u] == low[u])
	{
		cntn++;
		while(1)
		{
			int now = s.top();
			s.pop();
			id[now] = cntn;
			siz[cntn]++;
			if(now == u) break;
		}
	}
}

signed main()
{
	scanf("%d", &n);
	for(int i = 1;i <= n; ++i)
	{
		string c, d;
		cin >> c >> d;
		mp[c] = i;
		mp[d] = i + n;
		add(mp[c], mp[d]);
	}
	scanf("%d", &m);
	for(int i = 1; i <= m; ++i)
	{
		string c, d;
		cin >> c >> d;
		add(mp[d], mp[c]);
	}
	for(int i = 1; i <= 2 * n; ++i)
		if(!dfn[i]) tarjan(i);
	for(int i = 1; i <= n; ++i)
	{
		if(id[i] == id[i + n])
		{
			puts("Unsafe");
		}
		else
		{
			puts("Safe");
		}
	}
	cout << endl;
}

T4 矿场搭建

三倍经验

P3225 [HNOI2012]矿场搭建

UVA1108 Mining Your Own Business

SP16185 BUSINESS - Mining your own business

题目大意

煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。 请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。

数据范围: n500n \leq 500,输入数据保证答案小于 2642^{64}

解题思路

显然,先 Tarjan 跑出割点,然后 DFS 搜索所有的联通块(也可以直接跑点双,不过我考场时是这样写的)。

计算每一个联通块中的割点数目。

然后再分类讨论:

  • 如果没有割点,至少需要建立两个出口,从任意地方选择两个点建立 ,如果设连通块的大小为 sizsiz,则对答案的贡献为 (siz1)siz2\frac{(siz-1)*siz}{2}
    • 如果轰炸其中一个出口,则其他点都可以从另一个出口逃走。
    • 如果轰炸的是其他点,不啥事都没有吗~
  • 如果这个连通块只有一个割点,只需要在连通块内设立一个出口,可以设立在任意一个非割点的地方,如果设连通块的大小为 sizsiz,则贡献为 siz1siz-1(因为不能建立在割点上)。
    • 如果轰炸的是割点,则连通块内的其他点都可以通过刚刚建立的出口出去。
    • 如果轰炸的是我们刚刚建立的出口,那由于我们刚刚建立的出口不是割点,所以连通块内除了轰炸了的点,都可以从割点通向另一个连通块内的出口。
  • 如果这个连通块有两个及以上个割点,则不需要建立出口。
    • 如果轰炸的是其中一个割点,则可以通过其他割点到达其他的连通块。
    • 如果轰炸的是其他点,则轰炸的不是割点,啥事都没有~

最后将所有连通块的贡献乘起来(简单的乘法原理)。

如果有两个及以上个割点,则无需建立,可以直接到达其他联通块

AC CODE

边双联通分量

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

#define _ 905

long long Case = 1;
long long n, m, Ans1, Ans2;

long long Cut, cnt_node, cntn, root;

int dfn[_], vis[_], low[_], siz[_];

stack<int> s;

bool cut[_];

int head[_], cnt;

struct Node
{
	int v, next;
} e[_ * _];

vector<int> cutt[_];

void add(int u, int v)
{
	e[++cnt] = (Node)
	{
		v, head[u]
	};
	head[u] = cnt;
}

void Tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++cnt_node;
	s.push(u);
	int flag = 0;
	if(u == root && !head[u])
	{
		cutt[++cntn].push_back(u);
		return;
	}
	for (int i = head[u]; i; i = e[i].next)
	{
		int v = e[i].v;
		if (!dfn[v])
		{
			Tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u])
			{
				flag++;
				if(u != root || flag > 1) cut[u] = 1;
				cntn++;
				while(1)
				{
					int now = s.top();
					s.pop();
					cutt[cntn].push_back(now);
					if(now == v) break;
				}
				cutt[cntn].push_back(u);
			}
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

void init()
{
	memset(head, 0, sizeof(head));
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	memset(cut, 0, sizeof(cut));
	memset(vis, 0, sizeof(vis));
	for(int i = 0; i < _; ++i) cutt[i].clear();
	cnt_node = 0;
	cnt = 0;
	n = 0;
	Ans1 = 0;
	Ans2 = 1;
	cntn = 0;
	while(!s.empty()) s.pop();
}

signed main()
{
	while (cin >> m && m)
	{
		init();

		for (long long i = 1; i <= m; ++i)
		{
			long long u, v;
			scanf("%lld%lld", &u, &v);
			add(u, v);
			add(v, u);
			n = max(n, max(u, v));
		}

		for (int i = 1; i <= n; ++i)
			if (!dfn[i]) Tarjan(root = i, i);

		int opt = 0;

		for (int i = 1; i <= cntn; ++i)
		{
			Cut = 0;
			for(int j = 0; j < cutt[i].size(); ++j)
				if(cut[cutt[i][j]]) Cut++;
			
			if (Cut == 0)
			{
				Ans1 += 2;
				Ans2 *= (long long)(cutt[i].size() - 1) * cutt[i].size() / 2;
			}
			
			if (Cut == 1)
			{
				Ans1++;
				Ans2 *= (long long)cutt[i].size() - 1;
			}
		}

		cout << "Case " << Case++ << ": " << Ans1 << " " << Ans2 << endl;
	}
	return 0;
}

割点 ++ dfs

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

#define _ 905

long long Case = 1;
long long n, m, Ans1, Ans2;

long long num, Cut, cnt_node, root;

int dfn[_], vis[_], low[_];

bool cut[_];

int head[_], cnt;
struct Node
{
	int v, next;
} e[_ * _];

void add(int u, int v)
{
	e[++cnt] = (Node){v, head[u]};
	head[u] = cnt;
}

void Tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++cnt_node;
	int flag = 0;
	for (int i = head[u]; i; i = e[i].next)
	{
		int v = e[i].v;
		if (!dfn[v])
		{
			Tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u])
			{
				flag++;
				if(u != root || flag > 1) cut[u] = 1;
			}
		}
		else if (v != fa)
			low[u] = min(low[u], dfn[v]);
	}
}

void dfs(int u, int id)
{
	vis[u] = id;
	num++;
	for (int i = head[u]; i; i = e[i].next)
	{
		int v = e[i].v;
		if (cut[v] && vis[v] != id)
		{
			Cut++;
			vis[v] = id;
		}
		if (vis[v]) continue;
		dfs(v, id);
	}
}

void init()
{
	memset(head, 0, sizeof(head));
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	memset(cut, 0, sizeof(cut));
	memset(vis, 0, sizeof(vis));
	cnt_node = 0;
	cnt = 0;
	n = 0;
	Ans1 = 0;
	Ans2 = 1;
}

signed main()
{
	while (cin >> m && m)
	{
		init();

		for (long long i = 1; i <= m; ++i)
		{
			long long u, v;
			scanf("%lld%lld", &u, &v);
			add(u, v);
			add(v, u);
			n = max(n, max(u, v));
		}

		for (int i = 1; i <= n; ++i)
			if (!dfn[i]) Tarjan(root = i, i);

		int opt = 0;
		
		for (int i = 1; i <= n; ++i)
		{
			if (!vis[i] && !cut[i])
			{
				num = 0;
				Cut = 0;
				dfs(i, ++opt);
				if (Cut == 0)
				{
					Ans1 += 2;
					Ans2 *= (long long)(num - 1) * num / 2;
				}
				if (Cut == 1)
				{
					Ans1++;
					Ans2 *= (long long)num;
				}
			}
		}
		cout << "Case " << Case++ << ": " << Ans1 << " " << Ans2 << endl;
	}
	return 0;
}
posted @   蒟蒻orz  阅读(15)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示