2024暑假集训测试9
前言
- 比赛链接。
一点部分分没打感觉要被 D 了。
赛时题面和数据好像有出了点问题,不过我 T3 没做换数据不关我的事,但是 T1 还特殊考虑了 \(a_i<0\) 的情况,实则不用。
T2 理解错题了硬控 \(2\) 个多小时,发现之后改改就过了,但 T3、T4 没时间看了。
但是这次终于不挂分了。
T1 简单的序列
签到题,贪心倒退即可,若出现 \(a_i=0\) 了还不合法即无解。
虽然不存在但还是想说,若 \(a_i<0\) 正推即可,若 \(a_i=-1\) 了还不合法即无解,只可能前面一串负后面全是正,否则无解。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e4+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename Tp> inline void wt(Tp x)
{if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x)
{if(x<0)putchar('-'),x=~x+1;wt(x);}
ll T,n,a[N];
signed main()
{
read(T);
while(T--)
{
read(n);
ll len=0,ans=0;
bool flag=0;
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=n;i++)
{
if(a[i]<0) len++;
else break;
}
for(int i=len+1;i<=n;i++)
if(a[i]<0)
{
puts("-1");
flag=1;
break;
}
if(!flag)
{
for(int i=2;i<=len;i++)
{
while(a[i]<=a[i-1]&&a[i]!=-1&&a[i-1]!=-1)
{
a[i]=floor((double)a[i]/2.0);
ans++;
}
if(a[i]<=a[i-1])
{
puts("-1");
flag=1;
break;
}
}
}
if(!flag)
{
for(int i=n-1;i>=len+1;i--)
{
while(a[i]>=a[i+1]&&a[i]!=0&&a[i+1]!=0)
{
a[i]=floor((double)a[i]/2.0);
ans++;
}
if(a[i]>=a[i+1])
{
puts("-1");
flag=1;
break;
}
}
}
if(!flag) write(ans),puts("");
}
}
T2 简单的字符串
先把题面读明白,即每次从尾部删一个,同时 \(a_i\) 可向左传染,等价于在末尾加一个万能传染字符,只考虑传染。
发现对于字符 \(x\) 上次和本次出现的位置 \(l,r\),那么其需要的次数就是 \(r-l-1\),故此处理出其每个 \(l_i,r_i\),取最大的 \(r_i-l_i-1\) 即可。
特殊的,第一个 \(l=0\),最后一个 \(r=n+1\)。
枚举字符即可,预处理可达到 \(O(n)\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e4+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename Tp> inline void wt(Tp x)
{if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x)
{if(x<0)putchar('-'),x=~x+1;wt(x);}
char s[N];
int n,ans=0x3f3f3f3f;
bool vis[N];
vector<int>pos,e[N];
signed main()
{
cin>>(s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++)
{
if(!vis[s[i]-'a'])
{
vis[s[i]-'a']=1;
pos.push_back(s[i]-'a');
}
e[s[i]-'a'].push_back(i);
}
for(int x:pos)
{
int l=1,maxx=0,vr;
for(int r:e[x])
{
maxx=max(maxx,r-l);
l=r+1;
vr=r;
}
maxx=max(maxx,n-vr);
ans=min(ans,maxx);
}
write(ans);
}
T3 简单的博弈
机房绝大多数人没有学过 \(SG\) 函数,所以只好赛后粗略学习一下。
对于每个节点 \(x\) 存在子节点 \(y\),考虑以 \(y\) 为根的子树对其贡献,因为该子树上所有节点包括 \((x,y)\) 这条边都可能被删除,故该子树上所有节点都连向一个终止节点,结果就是 \(SG(y)+1\),于是有 \(SG(x)=⊕_{y\in son_x}(SG(y)+1)\)。
最后若 \(SG(1)=0\) 则后手赢,反之先手赢。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename Tp> inline void wt(Tp x)
{if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x)
{if(x<0)putchar('-'),x=~x+1;wt(x);}
ll T,n;
vector<ll>e[N];
ll dfs(int x,int fa)
{
ll ans=0;
for(int y:e[x]) if(y!=fa) ans^=(dfs(y,x)+1);
return ans;
}
signed main()
{
read(T);
while(T--)
{
read(n);
for(int i=1;i<=n;i++) e[i].clear();
for(int i=1,x,y;i<=n-1;i++)
{
read(x),read(y);
e[x].push_back(y);
e[y].push_back(x);
}
puts(dfs(1,0)?"gtm1514":"joke3579");
}
}
T4 困难的图论
没学过最短路 \(dag\),不想调了,好像还要手打 \(bitset\)?
挂个官方题解吧
首先把图建出来。这题是最短路能看出来吧。暴力建图可以过第一个点。对每个标签建一个虚点,每个点进虚点的边权为 \(0\),出的边权为 \(1\),再跑可以过三个点。
我们考虑继续发掘一下这些个虚点能干些什么。对于 \(m\le 3000\) 的图,我们可以发现单靠这 \(m\) 条边基本是不连通的。那么我们就完全不需要管没被 \(m\) 条边覆盖的点,他们的答案可以直接通过虚点计算。那么只需要对虚点和 \(O(m)\) 个点建图跑最短路。
考虑将这个思路拓展一下。两个点之间最短路有两种情况,一种是过虚点,一种不过。以起点 \(p\) 为例,他要么过他所连虚点要么不过。如果过,这个点答案就是虚点的答案。如果不过,就要减 \(1\),相当于把第一步走虚点再走回来去掉。
那么什么时候减呢?可以从每个虚点开始跑最短路,建立最短路 dag。如果要减这个 \(1\),那么终点是起点的后继。可以用 bitset 从后往前扫一遍维护每个点前驱有哪些节点来统计前驱后继。
总结一下,我们每次计算一个虚点里边所有点的答案。建立最短路 dag 以后统计虚点内的点到其他点的最短路。枚举所有终点 \(x\),对于自己的所有前驱,答案是 \(dis_x-1\),否则是 \(dis_x\)。这样就可以方便地开桶计算个数了。
但是直接开 bitset 会浪费很多长度 t 掉,需要手写 bitset。就是用 ull 压,一次压 64 个点处理。
总结
好好读题,最好是手摸一遍样例来确定是否读对题。