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;//好习惯
}
本文来自博客园,作者:北烛青澜,转载请注明原文链接:https://www.cnblogs.com/Multitree/articles/16739639.html
The heart is higher than the sky, and life is thinner than paper.