2024.11.14&2024.11.15模拟赛( — _ — )
14日:
四个小时的模拟赛,四道小清新计数题。前面时间状态还是不太好,一个多小时把T1搞出来了,T2T3T4都不会,暴力也没打成。
T1又顺便挂了20分,所以最后总分只有80。是得好好调整状态了。
T1【字符串】
题目大意:
给出两个字符串\(s,t\),要求求出所有\(x\)的个数,使得\(x=p+q\)(\(p,q\)分别是\(s\)串的非空前缀、\(t\)串的非空后缀)
设\(n=|s|,m=|t|,1\leqslant n,m\leqslant 10^{6}\)。
解题思路:
所有\(x\)的个数即为\(n\times m\),但由于\(s,t\)串中重复的字符拼接时会导致相同的\(x\)被统计两次,所以需要统计字符串中相同的字符后去重,总数减去一个字符在\(s\)串出现的次数乘在\(t\)串中出现的次数即可。
但!注意到前后缀非空,所以统计字符时要跳过\(s_{1}\)与\(t_{m}\)。
挂分小技巧:取模出错。
坏代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
const int MOD=998244353;
char s1[N],s2[N];
int a1[N],a2[N];
int cnt1,cnt2;
int bx1[N],bx2[N];
int ans;
signed main()
{
scanf("%s%s",s1,s2);
for (int i=0;s1[i]!='\0';i++) a1[++cnt1]=(int)(s1[i]-'a');//闲的
for (int i=0;s2[i]!='\0';i++) a2[++cnt2]=(int)(s2[i]-'a');
ans=(cnt1*cnt2)%MOD;
for (int i=2;i<=cnt1;i++) bx1[a1[i]]++;
for (int i=1;i<cnt2;i++) bx2[a2[i]]++;
for (int i=0;i<26;i++) ans=(ans-(bx1[i]*bx2[i])%MOD+MOD)%MOD;//注!意!取!模!
printf("%lld",ans);
return 0;
}
T2【艾特扣德】
待补。
15日:
抽象的题目。不多做评价了。
打了T1的正解,T2的“暴力”,预计总分140。但T1没注意到\(\Sigma n\)为\(10^{6}\),多测初始化初始炸了;T2数据太水,导致暴力完全能过,但是因特判特殊数据特判的不对所以炸了,实际上T1 60分,T2 80分,总分140分。一切的一切都说得过去,但是……
挂了整整60分!!再次与第二失之交臂!!!
T1【最优排序】
题目大意:
给出一个值域为\([1,n]\)的排列\(p\),每次操作可以选取至多四个互不相同的位置,并任意交换他们的位置。求最少操作几次可以使\(p\)升序排列。\((1\leqslant T,n,\Sigma n\leqslant 10^{6})\)
解题思路:
看到这道题又手模了一会儿,突然联想到之前模拟赛的T3,于是就开始非常激动地建图……统计……然后炸了。
我们可以将不好的\(p_{i}\)指向\(i\),我们可以想到一定存在若干个\(p_{i}\),交换他们之间的位置后一定合法,手模后发现这些\(p_{i}\)会形成一个环。于是乎,我们可以建图后统计出所有环的节点个数以计算答案。
对于一个节点数量为\(t_{i}\)的环,我们可以先取到相邻4个节点并进行交换,我们容易发现一次操作会产生一个不合法的\(p_{i}\)与三个合法的\(p_{j}\),那么下一次操作包含上次不合法的\(p_{i}\)与3个新的\(p_{j}\)即可,重复以上操作就可以使整个环合法。
所以,对于该环就要操作\(\lceil\frac {t_{i}}{3}\rceil\)次。但是!可以想到,要是最后只用操作两个点的话,那么这次操作可以和其他环一起操作,最后答案即\(\Sigma^{m}_{i=1}{\lfloor\frac{t_{i}}{3}\rfloor}+\lceil\frac{\Sigma^{m}_{i=1}[t_{i} mod 3 =2]}{2}\rceil\)。
因为这题情况非常简单,所以根本不用跑\(Tarjan\),跑个\(dfs\)就行。且,初始化补药\(memset\),重复到\(n\)就行,不然会炸。
坏代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int T;
int n;
int p[N];
vector <int> e[N];
int vis[N];
int ans,tol;
void init()
{
for (int i=0;i<=n;i++) e[i].clear();
for (int i=0;i<=n;i++) vis[i]=0;
ans=tol=0;
}
int dfs(int x)
{
vis[x]=1;
int res=1;
int _size=e[x].size();
for (int i=0;i<_size;i++)
{
int v=e[x][i];
if (vis[v]) continue;
res+=dfs(v);
}
return res;
}
signed main()
{
scanf("%lld",&T);
while (T--)
{
init();
scanf("%lld",&n);
for (int i=1;i<=n;i++)
{
scanf("%lld",&p[i]);
if (p[i]==i) continue;
e[i].push_back(p[i]);
}
for (int i=1;i<=n;i++)
{
if (!vis[i]&&p[i]!=i)
{
int cnt=dfs(i);
ans+=cnt/3;
if (cnt%3==2) tol++;
}
}
printf("%lld\n",ans+(tol+1)/2);
}
return 0;
}
T2【图上移动】
题目大意:
给出一个\(n\)个节点、\(m\)条边的无向图,可以给每个点赋值,要求找到一条最长的路径,使得每次移动都是向当前节点相邻权值最小的节点移动。\((1\leqslant n\leqslant 40)\)
解题思路:
爆搜。期望得分100,特判失败,实际得分80。
容易发现,我们的路径上不能走完一个环,否则无法达到要求。所以直接枚举起点\(dfs\),若与当前节点相连的节点曾经走过的话,那么当前节点不可走,直接return回去即可。
神奇代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=50;
int n,m;
vector <int> e[N];
int ans;
int vis[N];
void dfs(int x,int _fa,int tol)
{
vis[x]=vis[_fa]+1;
int _size=e[x].size();
for (int i=0;i<_size;i++)
{
int v=e[x][i];
if (v!=_fa&&vis[v]) return ;
}
for (int i=0;i<_size;i++)
{
int v=e[x][i];
if (v==_fa) continue;
dfs(v,x,tol+1);
vis[v]=0;
}
ans=max(ans,tol);
return;
}
signed main()
{
scanf("%lld%lld",&n,&m);
for (int i=1,u,v;i<=m;i++)
{
scanf("%lld%lld",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
for (int i=1;i<=n;i++)
{
memset(vis,0,sizeof vis);
dfs(i,0,1);
}
printf("%lld",ans);
return 0;
}