//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

2022.9.24模拟赛解题报告

T1

原题

考试的时候没想出来正解,打了个爆搜,拿了60分,爆搜分给的还是挺足的,后来看了题解发现自己是不可能想出正解的。

首先我们在读入数据的时候要处理一下,把当前任务当作一条边,可以完成任务的人为点,用任务将两个点都链接起来,在 \(a==b\) 的情况的时候把当前点标记一下必须要做当前的任务,因为这个操作会引起一系列的连锁反应,也就是说当当前点必须去做当前任务时,有的任务可以完成的两个人中包含当前人,那么当前的任务就必须由另一个人做,这样就处理出不能改变的匹配的人和任务,然后我们在进行这个过程当中如果有 \(paradox\) 就直接输出 \(0\) 就好了,到了后面,直接 BFS 求边点匹配的方案数即可。

#include<bits/stdc++.h>
#define int long long
#define P 1000000007
#define N 250010
using namespace std;
int n,m,f[N],vis[N],ans=1;//f存放每一个任务被几号做,vis标记当前人是否有任务做,ans存放答案 
vector<int>e[N];//存放联通情况
queue<int>q;//队列bfs
int bfs(int x)//广搜 
{
	int v=0,o=0;//v累加当前的深度 
	q.push(x);//当前点入列 
	vis[x]=1;//标记可以完成
	while(!q.empty())//只要队列不空 
	{
		int u=q.front();//取出队头元素 
		q.pop();//弹出
		v+=1;//加一
		o+=e[u].size();//加上当前点的连边数 
		for(int i=0;i<e[u].size();i++)//枚举 
		{
			int v=e[u][i];//取出终点 
			if(!vis[v])//如果当前任务没完成 
			  q.push(v),//入列
			  vis[v]=1;//标记能完成 
		}
	}
	o>>=1;//o乘二 
	if(v==o)//如果相等的话就是直接乘二即可 
	  return 2;
	else if(v==o+1)
	  return v;
	return 0;
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int a,b;
		cin>>a>>b;
		if(a==b)//相等 
		{
			if(vis[a])//如果当前任务已经完成 
			  ans=0;//无解 
			else
			  q.push(a),vis[a]=1;//否则就放入队列,标记 
		}
		else
		  e[a].push_back(b),//否则就建无向图
		  e[b].push_back(a);//把两个任务链接起来 
	}
	while(!q.empty())//只要队列不空 
	{
		int u=q.front();//取出队头元素 
		q.pop();//弹出 
		for(int i=0;i<e[u].size();i++)//枚举每一个与u相连的边 
		{
			int v=e[u][i];//取出终点 
			if(v==f[u])continue;//如果是父节点就跳过 
			if(vis[v])//如果当前人必须去完成某个任务 
			{
				ans=0;//答案归零 
				break;//退出 
			}
			vis[v]=1;//标记当前点可以被完成 
			f[v]=u;//标记父亲 
			q.push(v);//放入队列 
		}
	}
	for(int i=1;i<=m&&ans;i++)//只要答案不是0
	  if(!vis[i])//当前没有指定的人 
	    ans=ans*bfs(i)%P;//累加答案 
	cout<<ans<<endl;//输出答案 
	return 0;
}

T2

T3

原题

首先应该注意到的是,字符串中,相邻两个字母如果相等,那么相邻的字母可以被删掉而不影响答案
一般人都能想到这个,不过到了这里就容易卡壳,我们不妨换个思路:
考虑 \(A L O\) 三个字母的个数,为 \(a\), \(l\), \(o\),由对称性可以假设 \(a\) 是最小的,即 \(a < l\)\(a < o\)
那么答案应该是和 \(A\) 有关的,我们不妨假设我们要选所有的 \(A\),尝试构造一个解
那么接下来我们要做的其实是把 \(L\)\(O\) 删掉,如果想要不改变 \(A\) 的数量,如何删掉 \(L\)\(O\)
有两种方法:
方法一、删去类似 \(LOLOLOLOL\) 串中的 \(LO\),那么 \(L\)\(O\) 的数量同时降低
方法二、删去类似 \(LOLOLOLOL\) 串中的单边的 \(L\) 或者 \(O\),那么单边的数量降低
我们发现无法删掉的 \(L\)\(O\) 都是“单个”的,即 \(ALA\)\(OLO\)
如果想删掉这些单个字母,就只能删掉一个 \(A\)
那么……算法的流程大概是这样:
判断:能否只通过方法一和方法二构出答案?
可以 \(->\) 输出当前 \(A\) 的数量乘以 \(3\)
不可以 \(->\) 说明通过方法一和方法二之后,\(L\)\(O\) 的数量依旧不平衡
那么只能考虑删掉 \(A\)

#include<bits/stdc++.h>
using namespace std;
char s[2000010],v[2000010];
int tot,a,l,o,ul,uo;
int main()
{
	char A,L,O;//存放字符 
	scanf("%s",s);//输入原字符串 
	int len=strlen(s);//测出字符串的长度 
	for(int i=0;i<len;i++)//遍历每一个字符 
	  if(s[i]!=s[i-1])//如果当前的字母和上一个字母是不一样的 
	    v[tot++]=s[i];//存入v数组 
	int flag=0;//flag用于标记 
	for(int i=0;i<tot;i++)//遍历统计每一个字母的数量 
	  if(v[i]=='A')a++;
	  else if(v[i]=='L')l++;
	  else if(v[i]=='O')o++;
	if(a<=l&&a<=o)//A最少 
	  A='A',L='L',O='O';
	else if(l<=a&&l<=o)//l最少 
	  A='L',L='A',O='O';
	else A='O',L='L',O='A';//o最少 
	a=l=o=0;//清空 
	for(int i=0;i<tot;i++)//遍历每一个字符 
	{
		if(v[i]==A)//如果当前的字母等于当前最少的字母 
		  flag=0,a++;//重置flag,a的数量加一 
		else//否则 
		{
			if(v[i]==L&&(flag&1)==0)//如果当前点是L并且flag&1的结果是0 
			{
				l++;//l数量加一 
				if(i==0&&v[i+1]==A)ul++;//如果当前点是第一个并且下一个就是A就ul加一 
				else if(i==tot-1&&v[i-1]==A)ul++;//如果当前点是最后一个并且上一个就是A就ul加一 
				else if(v[i-1]==A&&v[i+1]==A)ul++;//如果当前点的前后都是A的话就ul加一 
				flag|=1;//flag进行或运算 
			}
			if(v[i]==O&&(flag&2)==0)//如果当前点是O并且flag&2的结果是0 
			{
				o++;//o数量加一 
				if(i==0&&v[i+1]==A)uo++;//如果当前点是第一个并且下一个就是A就uo加一 
				else if(i==tot-1&&v[i-1]==A)uo++;//如果当前点是最后一个并且下一个就是A就uo加一 
				else if(v[i-1]==A&&v[i+1]==A)uo++;//如果当前点的前后都是A的话就uo加一 
				flag|=2;//进行异或运算 
			}
		}
//		cout<<flag<<endl; 
	}
	if(uo>l)a-=uo-l;//如果当前的uo大于l的话就只能减少a 
	if(ul>o)a-=ul-o;//同理 
	cout<<a*3<<endl;//输出 
	return 0;//好习惯 
}
posted @ 2022-09-28 21:22  北烛青澜  阅读(17)  评论(0编辑  收藏  举报