2024航电多校暑假2

1001鸡爪

题意:一个鸡爪是由 4 个部分组成,一个点与三个与该点相邻的边,三个边的另一端点被认为不在鸡爪中。

一个图上的鸡爪数是该图最多成形成几个鸡爪,使得图上每个点与边最多在一个鸡爪中(注意上文点与边是否在鸡爪中的定义)。

现在给你 n 条边,你可以使用任意个点,构造一个简单无向图(没有自环重边),要求最大化该图的鸡爪数,并输出n条边的两端点。如果有多解,请让输出的 2𝑛个数字在行优先遍历的顺序下,字典序最小。

思路:一个比较神奇的构造题,赛时过的时候以为是特判加一点点规律,赛后大概把这个规律弄懂了,首先就是当你不能组成一个鸡爪的时候肯定把多的边都变成1和新的点连的边,这样的字典序肯定是最小的,1比任何的都要优先。然后考虑给出的边足够组成鸡爪的情况,组成一个鸡爪的时候就是让1和别的点连满三条边就行,组成两个鸡爪怎么考虑呢,因为1这个点已经在一个鸡爪内了,我们肯定考虑让2去连,1和2的边已经用过了,2就算和3,4连满边也不够一个鸡爪的数量。那么我们考虑给1多加一条边,这样多了1和5这条边之后我们发现,1和2连的边就可以顺延给2这样2就满足鸡爪了。那么同理多个鸡爪的时候,其实就是在上一个情况下,让1多一条边,然后把边一路顺延给下一个点就行,直到最后一个点够边,但是我们发现我们不能一直给1去连边,不然往后延的时候有些点就会只有一条边,延下来一条仍旧不够构成鸡爪,所以我们把2和这个点和3和这个点都要连过,这样边一条条延下来才能让每个点都正好满足鸡爪的条件。具体实现可以看代码

代码

#include<iostream>
#include<string>
#include<map>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;

#define ll long long

struct edge
{
	ll a;
	ll b;
};

ll cmp(edge x, edge y)
{
	if (x.a != y.a)
	{
		return x.a < y.a;
	}
	else
	{
		return x.b < y.b;
	}
}

void solve()
{
	ll n;
	cin >> n;
	vector<edge> arr;
	ll c1 = 2;
	if (n / 3 == 1)
	{
		arr.push_back({ 1,2 });
		arr.push_back({ 1,3 });
		arr.push_back({ 1,4 });
		c1 = 5;
	}
	else if (n / 3 >1)
	{
		arr.push_back({ 1,2 });
		arr.push_back({ 1,3 });
		arr.push_back({ 1,4 });
		arr.push_back({ 1,5 });
		arr.push_back({ 2,3 });
		arr.push_back({ 2,4 });
		c1 = 6;
	}
	ll c2 = 5;
	ll c3 = 4;
	for (int i = 1; i <= n / 3 - 2; i++)
	{
		arr.push_back({ 1,c1++ });
		arr.push_back({ 2,c2++ });
		arr.push_back({ 3,c3++ });
	}
	for (int i = 1; i <= n % 3; i++)
	{
		arr.push_back({ 1,c1++ });
	}
	sort(arr.begin(), arr.end(), cmp);
	for (int i = 0; i < arr.size(); i++)
	{
		cout << arr[i].a << ' ' << arr[i].b << endl;
	}
}

signed main()
{
	ll t;
	cin >> t;
	while (t--)
	{
		solve();
	}
}
/*
1
ftp://hdu-oj-bucket/problem=1/type=data/
*/

1006 传奇勇士小凯

题意:给出一个无重边无自环的图,每个点有pi/15的概率胜利,失败了就要一直打一直打直到胜利为止,每天只能打一次,问最多期望能停留多久,答案输出最简分数。

思路:首先计算每个点的期望,停n天的贡献是n*(1-pi/15)的n-1次*pi/15.把1到n天的贡献加起来就是一个点的贡献。经过计算(把pi/15提取出来,然后再把后面的设置为t,整体去×一个(1-pi/15)然后相减做差就变成一个等比数列求和,把t算出来带回式子)可以得到一个点的期望就是这个点概率的倒数,然后从起始点跑一次dfs更新最大值就行,注意最后要输出一个最简分数,所以每个点的值我们也要以分数形式记录,这样最后直接上下同时除一个最大公约数就是最简分数,实现可看代码

代码

#include<iostream>
#include<string>
#include<map>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;

#define ll long long

ll gcd(ll a, ll b)
{
	if (a < b)
	{
		swap(a, b);
	}
	ll c = a % b;
	while (c)
	{
		a = b;
		b = c;
		c = a % b;
	}
	return b;
}

struct node
{
	ll son;
	ll mo;
}arr[100005];
ll n;
node mx;
vector<vector<ll>> tu(n + 5);

void dfs(ll now, ll fa, node val)
{
	if (fa == -1)
	{
		val.mo = arr[now].mo;
		val.son = arr[now].son;
	}
	else
	{
		if (arr[now].mo != val.mo)
		{
			ll sum = arr[now].mo * val.mo;
			ll x = gcd(arr[now].mo, val.mo);
			ll nmo = sum / x;
			ll sonn = arr[now].son * (nmo / arr[now].mo);
			ll sonv = val.son * (nmo / val.mo);
			ll nson = sonn + sonv;
			val.mo = nmo;
			val.son = nson;
		}
		else
		{
			val.son = arr[now].son + val.son;
		}
		if (mx.mo == 0)
		{
			mx.son = val.son;
			mx.mo = val.mo;
		}
		else
		{
			ll sum = mx.mo * val.mo;
			ll x = gcd(mx.mo, val.mo);
			ll nmo = sum / x;
			ll sonm = mx.son * (nmo / mx.mo);
			ll sonv = val.son * (nmo / val.mo);
			if (sonv > sonm)
			{
				mx.son = sonv;
				mx.mo = nmo;
			}
		}
	}
	for (int i = 0; i < tu[now].size(); i++)
	{
		if (tu[now][i] != fa)
		{
			dfs(tu[now][i], now, val);
		}
	}
	return;
}

void solve()
{
	mx = { 0,0 };
	cin >> n;
	tu = vector<vector<ll>>(n + 5);
	for (int i = 1; i < n; i++)
	{
		ll a, b;
		cin >> a >> b;
		tu[a].push_back(b);
		tu[b].push_back(a);
	}
	for (int i = 1; i <= n; i++)
	{
		ll val;
		cin >> val;
		arr[i].mo = val;
		arr[i].son = 15;
	}
	dfs(1, -1, { 0,0 });
	ll p = gcd(mx.mo, mx.son);
	if (p != 1)
	{
		mx.son /= p;
		mx.mo /= p;
	}
	cout << mx.son << '/' << mx.mo << endl;
}

signed main()
{
	ll t;
	cin >> t;
	while (t--)
	{
		solve();
	}
}
/*
1
ftp://hdu-oj-bucket/problem=1/type=data/
*/

1007 URL划分

题意:给出一个网址,让你提取指定的值

思路:签到题,暴力模拟即可

1010 女神的智慧

题意:把8个碎片合成一个睿智,碎平和结晶合成时,颜色相同就是那个颜色,不同就是左边的颜色,大结晶合成时候颜色相同就是那个颜色,不同就看下8个碎片里面左右颜色哪个数量更多,多的那个颜色就是睿智的颜色,相同就随机,给出8个碎片问最后是什么颜色,随机输出‘N’

思路:暴力模拟即可,记录一下每个颜色的数量方便最后一步合成判断。

1011在A里面找所有包含C的B

题意:给出一个长串a和c,再给出n个b串和b’串,问哪几个b串满足b在a内且c在b’内。

思路:分两部处理,判断c是否在b’很显然可以通过kmp算法优化,问题就是前面的b在a内如何快速处理,通过队友的教导发现可以使用ac自动机,ac自动机是一种多字符串匹配算法,可以在一篇长文a里面找到多个不同的我们要求的字符串。跟这题的前部分完全重合,具体实现可以看代码

代码

#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long

const ll N = 1e5 + 10;

map<int, int> M;
ll trie[26][N] = { 0 };
ll net[N] = { 0 };
ll ne[10010] = { 0 };
ll enda[N] = { 0 };
ll cnt = 0;
string brr[N];
vector<vector<ll>> strs(N);
vector<ll> ansa;

string s1, s2;

void insert(string s, int xu)
{
	ll now = 0;
	for (int i = 0; i < s.size(); i++)
	{
		ll v = s[i] - 'a';
		if (trie[v][now])
		{
			now = trie[v][now];
		}
		else
		{
			trie[v][now] = ++cnt;
			now = cnt;
		}
	}
	enda[now] = 1; //记录这个点是否是一个字符串的结尾。
	strs[now].push_back(xu);  //因为bi可能是一样的,所以我们要把对应的序号在对应的结束位置加上。
	return;
}

void build()  //建立ac自动机,主要就是回跳边和转移边
//回跳边,顾名思义,当我下一个位置不匹配时,我跳到另一个和当前以匹配部分相同但是后面不同的位置继续匹配
//转移边,当我一个单词匹配完了之后,如果这个单词后面加了新的字符后能成为新的满足要求的单词的话,去寻找对应的位置,转移边的建立需要用到会跳边,因为已经匹配的位置肯定是相同的,加新字符会有新的单词的情况肯定是已匹配位置相同但后面不同情况下会发生
{
	queue<ll> q; //bfs一层层的去建立ac自动机,因为每个点的会跳边都要去上一层找,因此一层层去处理是最稳妥的
	for (int i = 0; i < 26; i++)
	{
		if (trie[i][0])
		{
			q.push(trie[i][0]); //把第一层的点入队。
		}
	}
	while (q.size())
	{
		int u = q.front();
		q.pop();
		for (int i = 0; i < 26; i++)
		{
			int v = trie[i][u];
			if (v) net[v] = trie[i][net[u]], q.push(v); //构建回退边,回退边所指结点一定是当前结点的最长后缀,即已经匹配的位置完全相同。同时把下一层入队。
			else trie[i][u] = trie[i][net[u]]; //如果走到底了,就通过回退边退到另外一个已匹配的位置都相同的另外的点去看看能不能满足新加一个字符仍是要找的单词。
		}
	}
	return;
}

void query(string a, int len) //搜索长串a里面有几个满足条件的b
{
	for (int k = 0, i = 0; k < a.size(); k++) //k表示从长串的哪里开始找
	{ 
		i = trie[a[k] - 'a'][i];  //在字典树里更新以下此时结点所在位置 
		for (int j = i; j && enda[j] != -1; j = net[j]) //从这个结点开始找这个结点有关的回退点,把所有可以回退的都查找一遍看看是不是结束点,是的话就说明这个单词在a串里
		{
			if (enda[j])
			{
				for (int z = 0; z < strs[j].size(); z++)
				{
					if (M[strs[j][z]])
					{
						ansa.push_back(strs[j][z]); //在本题里面就表示这组b满足条件。
					}
				}
				enda[j] = -1;
			}
		}
	}
	return;
}

void solve()
{
	M.clear();
	strs = vector<vector<ll>>(N);
	ansa.clear();
	memset(ne, 0, sizeof(ne));
	memset(net, 0, sizeof(net));
	memset(enda, 0, sizeof(enda));
	memset(trie, 0, sizeof(trie));
	cnt = 0;
	ll n;
	cin >> n;
	cin >> s1 >> s2;
	s2 = ' ' + s2;
	int j = 0;
	for (int i = 2; i < s2.size(); i++)
	{
		while (j && s2[i] != s2[j + 1]) j = ne[j];
		if (s2[j + 1] == s2[i]) j++;
		ne[i] = j;
	}
	for (int i = 1; i <= n; i++)
	{
		string b, c;
		cin >> b >> c;
		insert(b, i);
		c = ' ' + c;
		int nj = 0;
		for (int j = 1; j < c.size(); j++)
		{
			while (nj && c[j] != s2[nj + 1]) nj = ne[nj];
			if (c[j] == s2[nj + 1]) nj++;
			if (nj == s2.size() - 1)
			{
				M[i] = 1;
				break;
			}
		}
	}
	build();
	query(s1, s2.size() - 1);
	sort(ansa.begin(), ansa.end());
	for (int i = 0; i < ansa.size(); i++)
	{
		cout << ansa[i] << ' ';
	}
	cout << endl;
}

signed main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t;
	cin >> t;
	while (t--)
	{
		solve();
	}
}

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值