牛客练习赛88游记
牛客练习赛88游记
A - 活着的证据
题目大意:
将一个各数位都为 \(1\sim 8\) 的数逐位写成罗马数字,可以得到一个仅包含字符 V
和 I
的串。
其中阿拉伯数字 \(1\sim 8\) 对应的罗马数字如下:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
I |
II |
III |
IV |
V |
VI |
VII |
VIII |
求使用不超过 \(S_V\) 个 V ,不超过 \(S_I\) 个 I ,能表示出不超过 \(N\) 位的最大数是多少? |
|||||||
\(\sum N\le 5\times 10^6\) |
思路:
贪心。
显然 \(4\) 不可能出现,因为用 \(6\) 来取代更加划算。
将 V
尽量安排在高位。若 \(S_V < N\),则再将剩下的空位先用一个 I
来填上。
如果还有多余的 I
,则尽量往前放,能放满 \(3\) 个就放 \(3\) 个,否则就放 \(2\) 个或者 \(1\) 个,直到 I
用完或者放不下更多 I
为止。
时间复杂度 \(\mathcal O(N)\)。
源代码:
#include<cstdio>
#include<cctype>
#include<algorithm>
inline int getint() {
char ch;
while(!isdigit(ch=getchar()));
int x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return x;
}
const int N=5e6+1;
int v[N],i[N];
int main() {
for(int T=getint();T;T--) {
int V=getint(),I=getint();
int n=std::min(V+I,getint());
std::fill(&v[1],&v[n]+1,0);
std::fill(&i[1],&i[n]+1,0);
for(int j=1;j<=std::min(V,n);j++) v[j]++;
for(int j=V+1;j<=n;j++) i[j]++;
I-=std::max(n-V,0);
for(int j=1;j<=n;j++) {
if(I==0) break;
if(I==1) {
i[j]+=1;
I-=1;
} else if(I==2||i[j]==1) {
i[j]+=2;
I-=2;
} else {
i[j]+=3;
I-=3;
}
}
for(int j=1;j<=n;j++) {
putchar('0'+v[j]*5+i[j]);
}
puts("");
}
}
B - 寻寻觅觅寻不到
题目大意:
给定两个串 \(M\)、\(C\) 和一个整数 \(K\)。
问是否可以从 \(M\) 中选取某个长度为 \(K\) 的子串并将其位置移到末尾,使得到的新串与 \(C\) 相等。
\(\sum|M|,\sum|C|\le 3\times 10^5; 1\le K\le 6\)。
思路:
基础的字符串哈希题。
显然若 \(|M|\ne |C|\) 则答案肯定是 NO
。
当 \(|M|=|C|\) 时可对两串分别进行哈希。由于 \(C\) 确定,则长度为 \(K\) 的子串确定。枚举该子串出现的位置,对两部分分别比较哈希值即可。
\(K\) 的范围限制似乎是不必要的。
时间复杂度 \(\mathcal O(\sum |M|+\sum |C|)\)。
源代码:
#include<cstdio>
#include<cctype>
#include<cstring>
inline int getint() {
char ch;
while(!isdigit(ch=getchar()));
int x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return x;
}
using uint64=unsigned long long;
constexpr int N=5e6+2;
constexpr uint64 base=233;
char s[N],t[N];
uint64 pwr[N],hashs1[N],hashs2[N];
inline uint64 gethash(const int &l,const int &r) {
uint64 ret=0;
for(int i=l;i<=r;i++) {
ret=ret*base+s[i];
}
return ret;
}
int main() {
pwr[0]=1;
for(int i=1;i<=N-2;i++) {
pwr[i]=pwr[i-1]*base;
}
for(int T=getint();T;T--) {
scanf("%s %s",&s[1],&t[1]);
int k=getint();
const int n=strlen(&s[1]);
if(n!=strlen(&t[1])) {
puts("NO");
continue;
}
uint64 hash1=0,hash2=0;
for(int i=1;i<=n-k;i++) {
hash1=hash1*base+t[i];
}
for(int i=n-k+1;i<=n;i++) {
hash2=hash2*base+t[i];
}
for(int i=1;i<=n;i++) {
hashs1[i]=hashs1[i-1]*base+s[i];
}
hashs2[n+1]=0;
for(int i=n;i>=1;i--) {
hashs2[i]=hashs2[i+1]+s[i]*pwr[n-i];
}
for(int i=1;i<=n-k+1;i++) {
if(gethash(i,i+k-1)==hash2) {
if(hashs1[i-1]*pwr[n-i-k+1]+hashs2[i+k]==hash1) {
puts("YES");
goto Next;
}
}
}
puts("NO");
Next:;
}
}
C - 踩不出足迹
题目大意:
有 \(n\) 个 \(k\) 位二进制数。在相邻两数之间插入异或、同或运算符,使得最终结果最大。
\(n\le 10^6; k\le 64\)。
思路:
同或相当于对异或结果逐位取反。
对于仅含取反、异或操作的算式,局部取反等于全局取反。
因此我们只需无脑将这些数全部异或起来,最后决定是否需要取反即可。
另外注意 \(k=64\) 时会爆 unsigned long long
,需特判。
时间复杂度 \(\mathcal O(n)\)。
源代码:
#include<cstdio>
#include<cctype>
#include<algorithm>
using uint64=unsigned long long;
inline uint64 getint() {
char ch;
while(!isdigit(ch=getchar()));
uint64 x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return x;
}
int main() {
const int n=getint(),k=getint();
uint64 ans=0;
for(int i=1;i<=n;i++) {
ans^=getint();
}
if(k!=64) {
printf("%llu\n",std::max(ans,(1llu<<k)-1-ans));
} else {
printf("%llu\n",std::max(ans,~ans));
}
}
D - 都市的柏油路太硬
题目大意:
一张 \(n\) 个点、\(m\) 条边的连通双向图,定义一条路径的权值为该路径最长边的长度,点对的权值为两点间所有路径权值的最小值。
以所有点对间的权值为边权,建立 \(n\) 个点、\(\frac{n(n-1)}{2}\) 条边的新图,并由该图建立最小生成树。
\(q\) 次询问,每次询问最小生成树上两点间最大边权。
\(n\le 10^5; m\le 5\times 10^5; q\le 10^7\)。
三倍经验:
思路:
仔细分析题意后不难发现所求即是原图所对应 Kruskal 重构树中两点 LCA 的权值。注意到 \(q\le 10^7\) 因此使用倍增或树剖求 LCA 容易 TLE,需要用 Tarjan 离线求 LCA。
时间复杂度 \(\mathcal O(m\log m+n\cdot\alpha(n)+n+q)\)。
源代码:
#include<cstdio>
#include<cctype>
#include<vector>
#include<numeric>
#include<algorithm>
using uint64=unsigned long long;
inline uint64 getint() {
char ch;
while(!isdigit(ch=getchar()));
uint64 x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return x;
}
typedef unsigned long long ull;
ull myRand(ull &k1, ull &k2){
ull k3 = k1, k4 = k2;
k1 = k4;
k3 ^= (k3 <<23);
k2 = k3 ^ k4 ^ (k3 >>17) ^ (k4 >>26);
return k2 + k4;
}
std::pair<int,int>myRanq(ull&k1,ull&k2,int MAXN){
int x=myRand(k1,k2)%MAXN+1,y=myRand(k1,k2)%MAXN+1;
if(x>y)return std::make_pair(y,x);
else return std::make_pair(x,y);
}
constexpr int N=2e5+1,logN=18,M=5e5+1;
struct Edge {
int u,v,w;
bool operator < (const Edge &rhs) const {
return w<rhs.w;
}
};
Edge edge[M];
int n,m;
int val[N];
int par[N],dep[N]={-1},anc[N][logN];
std::vector<int> e[N];
inline void add_edge(const int &u,const int &v) {
e[u].emplace_back(v);
anc[v][0]=u;
}
struct DisjointSet {
int anc[N];
int find(const int &x) {
return x==anc[x]?x:anc[x]=find(anc[x]);
}
inline void init(const int &n) {
std::iota(&anc[1],&anc[n]+1,1);
}
inline bool same(const int &x,const int &y) {
return find(x)==find(y);
}
inline void merge(const int &x,const int &y) {
anc[find(x)]=find(y);
}
};
DisjointSet djs;
std::vector<int> q[N];
bool vis[N];
int ans=0;
void tarjan(const int &x) {
for(int &y:e[x]) {
tarjan(y);
djs.merge(y,x);
}
for(int &y:q[x]) {
if(!vis[y]) continue;
ans^=val[djs.find(y)];
}
vis[x]=true;
}
int main() {
n=getint(),m=getint();
for(int i=1;i<=m;i++) {
edge[i].u=getint();
edge[i].v=getint();
edge[i].w=getint();
}
std::sort(&edge[1],&edge[m]+1);
djs.init(n*2-1);
int t=n;
for(int i=1;i<=m;i++) {
const int u=djs.find(edge[i].u);
const int v=djs.find(edge[i].v);
const int &w=edge[i].w;
if(u==v) continue;
val[++t]=w;
add_edge(t,u);
add_edge(t,v);
djs.merge(u,t);
djs.merge(v,t);
}
const int q=getint();
uint64 a1=getint(),a2=getint();
for(int i=0;i<q;i++) {
const auto q=myRanq(a1,a2,n);
::q[q.first].emplace_back(q.second);
::q[q.second].emplace_back(q.first);
}
djs.init(t);
tarjan(t);
printf("%d\n",ans);
}