European Championship 2024 - Online Mirror(B、I、C、F、G题解)

B. Charming Meals(枚举)(*1486)

将a和b排序后固定a,对b错位相减,枚举所有错位的位置,统计这些情况下答案的最大值。时间复杂度为O(n^{2})

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,n,a[5010],b[5010];
void solve()
{
	cin>>n;
	for(int i=0;i<n;i++)  cin>>a[i];
	for(int i=0;i<n;i++)  cin>>b[i];
	sort(a,a+n),sort(b,b+n);
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		ll res=1e18;
		for(int j=0;j<n;j++)  res=min(res,abs(a[j]-b[(j+i-1)%n]));
		ans=max(ans,res);
	}
	cout<<ans<<endl;
}
int main()
{
	cin>>t;
	while(t--)  solve();
}

I. Disks(二分图染色)(*1858)

判断两个圆相切的方法是两个圆圆心的距离等于两个圆的半径之和,建议不要用根号,不然可能有精度误差,可以将等式两边平方后判断。

首先如果有两个相邻的圆,其中一个半径减小,另一个的半径就必须增加。我们把每个圆看成图论中的节点,两圆相切看成连边,这样整个二维平面就被转化为了图论的几个连通集,然后对单个连通集考虑以下情况:

  • 不含环:很明显这种情况要求集合大小为奇数,这样半径减小的圆就会比半径增大的圆多一个
  • 含有奇数长度的环:按照相邻圆半径变化相反的规则,长度为奇数的环中必定有两个相切的圆的半径变化相同,这与前面的分析矛盾。因此连通块内不能存在奇数环。
  • 含有偶数长度的环:偶数长度的环相当于抵消了半径的影响,对去除偶数环后的部分进行考虑即可。

我们可以发现,相切圆半径变化必须相反的规则类似于二分图中的染色,而且染色法可以轻松地判定奇数环的存在。因此,我们采用染色法来对每个连通块进行染色,判断每个连通块是否成功染色以及两种颜色的数量是否不同。由于n只有1000,我们可以使用很多复杂度为O(n^2)的算法来实现。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,st[1010],color[1010],p[1010];
vector<int>v[1010],ve[1010];
struct node{
	ll x,y,r;
}a[1010];
int find(int x)
{
	if(p[x]!=x)  p[x]=find(p[x]);
	return p[x];
}
bool dfs(int u,int c)
{
	color[u]=c;
	for(int i=0;i<v[u].size();i++)
	{
		int j=v[u][i];
		if(!color[j])
		{
			if(!dfs(j,3-c))  return false;
		}
		else if(color[j]==c)  return false;
	}
	return true;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)  p[i]=i;
	for(int i=1;i<=n;i++)  cin>>a[i].x>>a[i].y>>a[i].r;
	for(int i=1;i<n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			if((a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y)==(a[i].r+a[j].r)*(a[i].r+a[j].r))
				v[i].push_back(j),v[j].push_back(i),p[find(i)]=find(j);
		}
	}
	for(int i=1;i<=n;i++)  ve[find(i)].push_back(i);
	for(int i=1;i<=n;i++)
	{
		if(!color[i])
		{
			if(dfs(i,1)==false)  st[i]=0;
		}
	}
	int judge=0;
	for(int i=1;i<=n;i++)
	{
		if(!ve[i].size())  continue;
		int x=0,y=0,flag=1;
		for(auto j:ve[i])
		{
			for(auto k:v[j])
			{
				if(color[k]==color[j])
				{
					flag=0;
					break;
				}
			}
			if(!flag)  break;
		}
		for(auto j:ve[i])
			if(color[j]==1)  x++;
			else  y++;
		if(x==y||!flag)  continue;
		else
		{
			judge=1;
			break;
		}
	}
	if(judge)  cout<<"YES\n";
	else  cout<<"NO\n";
}

C. Annual Ants' Gathering(拓扑排序)(*1872)

拓扑排序是本题的一个十分简便的做法。

对于两个相邻的节点,一定是子树小的往子树大的节点移动。由于无法确定终点,一个好的方法是让整棵树最外围的节点向内移动。考虑进行贪心策略,用优先队列对子树大小从小到大存放bfs时的节点,这样每次取出的点都是蚂蚁数量最少的,是目前的最优策略,如果在这种情况下都无法移动,那么说明不会再有其他策略能让蚂蚁汇聚,输出NO即可。如果成功完成了拓扑排序,那么蚂蚁就可以汇聚一点,bfs的最后一个点就是汇聚点。

时间复杂度为O(nlogn)

思路来源:qwqIdontHaveAgf

#include<bits/stdc++.h>
using namespace std;
int n,d[200010],sz[200010];
vector<int>v[200010];
int main()
{
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		v[x].push_back(y);
		v[y].push_back(x);
		d[x]++,d[y]++;
		sz[x]=sz[y]=1;
	}
	priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
	for(int i=1;i<=n;i++)
		if(d[i]==1)  q.push({1,i});
	while(!q.empty())
	{
		auto t=q.top();
		q.pop();
		int u=t.second;
		for(auto j:v[u])
		{
			if(d[j]<=1)  continue;
			if(sz[j]>=sz[u])
			{
				sz[j]+=sz[u];
				d[j]--;
				if(d[j]==1)  q.push({sz[j],j});
			}
			else
			{
				cout<<"NO";
				return 0;
			}
		}
	}
	cout<<"YES";
}

F. Dating(排序+集合)(*2277)

遍历所有用户,我们可以用last[i]表示活动i最近一次出现在哪个用户的集合里,我们令last[i]的初值为-1。考虑n个集合之间的关系:包含、相交与不相交。

  • 不相交:如果该用户集合的每个元素的 last 值都是 -1 ,说明每个元素至今都未出现过,该用户集合不与之前的任何一个集合相交。
  • 相交:如果在遍历某个用户的集合时,集合内的每个元素的 last 值并不完全相同,那么说明该集合中与之前的某些集合相交,该集合与前面的任何一个集合都是可能的解。
  • 包含:在上面相交得到的可能解中,我们还需要排除包含的关系。比如用户1的集合为 [1,2] ,遍历完后 last[1] = last[2] = 1;之后用户 2 的集合为 [1,2,5] ,此时 last[1] = last[2] = 1 , last[5]=-1 ,满足相交条件,但同时也是包含关系,不符合题意。为了解决这个问题,我们先遍历大的集合,即将所有的用户按照集合大小排序,这样每次判断的就是被包含的关系。如上面的例子,排序后用户 1 变为 [1,2,5] ,last[1] = last[2] = last[5] = 1 ,用户 2 为 [1,2] ,遍历判断时 last[1] = last[2] = 1,这样就可以判掉这种情况了。

总时间复杂度为O(m)

思路来源:BrotherCall

#include<bits/stdc++.h>
using namespace std;
int n,m,c,x,last[1000010];
struct node{
	int len,idx;
	vector<int>v;
}a[200010];
bool cmp(node A,node B)
{
	return A.len>B.len;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>c;
		a[i].len=c,a[i].idx=i;
		for(int j=1;j<=c;j++)
		{
			cin>>x;
			a[i].v.push_back(x);
		}
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		int maxx=0,flag=0;
		for(auto j:a[i].v)  maxx=max(maxx,last[j]);
		for(auto j:a[i].v)
			if(last[j]!=maxx)
			{
				flag=1;
				break;
			}
		if(flag)
		{
			cout<<"YES\n"<<a[i].idx<<" "<<a[maxx].idx<<endl;
			return 0;
		}
		else
			for(auto j:a[i].v)  last[j]=i;
	}
	cout<<"NO";
}

G. Scooter(模拟)(*2327)

我们最好不要对已经匹配的教学楼进行变动,由于题目保证一定有解,因此起步最好是从不需要任何教授的教学楼开始接送,然后进行暴力查找匹配,将车上的教授与目的地的教授进行互换。如果一口气匹配到底最好;如果出现了送走教授后无法接到新的教授的情况,即原串当前位置为 '-' 的情况,那么就需要重新寻找起点并重复上面的步骤。同时注意如果接教授前看看场上还需不需要这个专业的教授,不需要就不要再接这个教授了,否则只会徒增麻烦。

用dfs实现就行了,时间复杂度为O(n^2)

思路来源:qwqIdontHaveAgf

#include <bits/stdc++.h>
using namespace std;
const string a="DRIVE",b="PICKUP",c="DROPOFF";
vector<string>ans;
int n,f[2010];
string s,t;
void op1(int u)
{
    ans.push_back(a+" "+to_string(u));
}
void op2()
{
    ans.push_back(b);
}
void op3()
{
    ans.push_back(c);
}
void dfs(int u)
{
    int ne=-1,c=0,m=0;
    for(int i=0;i<n;i++)
	{
        if(f[i])  continue;
        if(s[i]!='-')
		{
            if(s[i]=='C')  c++;
            else if(s[i]=='M')  m++;
        }
    }
    for(int i=0;i<n;i++)
	{
        if(f[i])  continue;
        if(s[i]==t[u]&&t[i]!='-')
		{
            if(s[i]=='C')  c--;
            else if(s[i]=='M')  m--;
            f[i]=1;
            op1(i+1),op3();
            if((t[i]=='M'&&m>0)||(t[i]=='C'&&c>0))  op2();
            ne=i;
            break;
        }
    }
    if(ne==-1)
	{
        for(int i=0;i<n;i++)
		{
            if(f[i])  continue;
            if(s[i]==t[u])
			{
                if(s[i]=='C')  c--;
                else if(s[i]=='M')  m--;
                f[i]=1;
                op1(i+1),op3();
                break;
            }
        }
        if(m+c>=1)
		{
            for(int i=0;i<n;i++)
			{
                if(f[i])  continue;
                if(s[i]=='-')
				{
                    f[i]=1;
                    op1(i+1);
                    if((t[i]=='M'&&m>0)||(t[i]=='C'&&c>0))  op2();
                    ne=i;
                    break;
                }
            }
        }
        if(m+c>=1&&ne!=-1)  dfs(ne);
    }
    else if(m+c>=1)  dfs(ne);
    else  return;
}
int main()
{
    cin>>n;
    cin>>s>>t;
    int c=0,m=0;
    for(int i=0;i<n;i++)
	{
        f[i]=(s[i]==t[i]);
        if(s[i]=='C')  c++;
        else if(s[i]=='M')  m++;
    }
    if(c+m<1)
	{
        cout<<0<<endl;
        return 0;
    }
    int st=-1;
    for(int i=0;i<n;i++)
	{
        if(s[i]==t[i]) continue;
        if(s[i]=='-'&&t[i]!='-')
		{
            f[i]=1;
            st=i;
            break;
        }
    }
    if(st!=-1)
	{ 
        op1(st+1),op2();
        dfs(st);
        cout<<ans.size()<<endl;
        for(auto i:ans)  cout<<i<<endl;
    }
    else  cout<<0<<endl;
}
引用\[1\]中的代码是一个使用Kosaraju算法来求解强连通分量的示例代码。该算法通过两次深度优先搜索来找到图中的所有强连通分量。在代码中,首先进行第一次深度优先搜索,将访问过的节点按照访问的顺序加入到栈S中。然后进行第二次深度优先搜索,从栈S中依次取出节点,并将其所在的强连通分量中的所有节点标记为同一个sccno值。最后,通过统计不同的sccno值的个数,即可得到图中的强连通分量的个数。 引用\[2\]中的内容是关于回文串的性质。假设一个字符串为k级回文串,那么将这个字符串拆成两个字符串,若这两个字符串仍然是回文串,那它们就是k-1级回文串,直到拆分成1级回文串后,再拆分出的字符串就不能再是回文串,否则就会改变最终结果。例如,对于字符串"acaabcbababaaac",可以将其拆分为"acaabcb"和"babaaac"两个回文串,然后再将这两个回文串继续拆分,直到拆分成单个字符,即"aca"、"bcb"、"bab"、"aac",这些都是回文串。但是,如果按照其他方式拆分,如"aca"、"abc"、"bab"、"aa"、"c",则无法得到回文串。因此,可以观察到,对于一个k级回文串,第一个字符、第k/2个字符、第k/2+1个字符、第k个字符等位置上的字符必须相同。 根据以上引用内容,"Komura-Equivalence"是一个未提供具体定义的术语,无法直接回答该问题。请提供更多相关信息或明确问题的要求,以便我能够给出更准确的答案。 #### 引用[.reference_title] - *1* [Kosaraju](https://blog.csdn.net/qq_45673190/article/details/104599315)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Japanese Student Championship 2021](https://blog.csdn.net/weixin_55355427/article/details/115915978)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值