不得不说,牛客多校题目质量确实是不错的
这套牛客多校训练的题目应该是我做过所有训练题里面质量算相当高的了。
非常推荐给大家做一做。
题目链接
https://ac.nowcoder.com/acm/contest/883
Problem A
题意:定义 S(x) 表示与结点 x 相邻的点集,给出一列边。要求支持下面两个操作:
- 将一个区间的边状态置反(本来存在的删除,本来删除的加上);
- 判断 S(x) 和 S(y) 是否相同。
题解:一个我不太知道的套路:边集 S(x) 可以用一个整数来表示,我们通过给每个点一个随机的权值,然后将点集里面的点权值异或起来得到 S(x) 。然后维护的话,考虑把边序列分块。
每次修改操作,可以变成两边块单点修改,中间的块,记录一下当前整个块的状态。
查询的时候,每个点的 S(x) ,就是单点的状态和相关块状态的异或,判断两个 S 是否相等即可。
所以需要预处理每个块与相关联点的抑或。
#include<bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
using namespace std;
const int maxn=210010;
int T,n,m;
ULL key[maxn],s[maxn],block[maxn],bs[510][maxn],tag[510];
struct edge
{
int x,y;
}e[maxn];
int main()
{
scanf("%d",&T);
for (int i=1;i<=100000;i++)
key[i]=(ULL)rand()*4000000+rand()+1;
while(T--)
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
s[i]=0;
int limit=(int)sqrt((double)m);
int blocks=(m-1)/limit+1;
for (int i=1;i<=blocks;i++)
{
tag[i]=1;
for (int j=1;j<=n;j++)
bs[i][j]=0;
}
int num=0;blocks=1;
for (int i=1;i<=m;i++)
{
scanf("%d%d",&e[i].x,&e[i].y);
num++;
if (num>limit) num-=limit,blocks++;
block[i]=blocks;
bs[block[i]][e[i].x]^=key[e[i].y];
bs[block[i]][e[i].y]^=key[e[i].x];
}
// for (int i=1;i<=limit+1;i++)
// for (int j=1;j<=n;j++)
// printf("bs[%d][%d]=%llu
",i,j,bs[i][j]);
for (int i=1;i<=blocks;i++)
tag[i]=1;
int q,x,y,z;
scanf("%d",&q);
while(q--)
{
scanf("%d%d%d",&x,&y,&z);
if (x==1)
{
int l=block[y],r=block[z];
for (int i=l+1;i<=r-1;i++)
tag[i]^=1;
if (l==r)
{
for (int i=y;i<=z;i++)
s[e[i].x]^=key[e[i].y],
s[e[i].y]^=key[e[i].x];
}
else
{
l=l*limit;r=(r-1)*limit+1;
for (int i=y;i<=l;i++)
s[e[i].x]^=key[e[i].y],
s[e[i].y]^=key[e[i].x];
for (int i=r;i<=z;i++)
s[e[i].x]^=key[e[i].y],
s[e[i].y]^=key[e[i].x];
}
}
else
{
ULL ansx=s[y],ansy=s[z];
for (int i=1;i<=blocks;i++)
if (tag[i])
ansx^=bs[i][y],ansy^=bs[i][z];
if (ansx==ansy) putchar('1');else putchar('0');
}
}
puts("");
}
return 0;
}
Problem B
题意:求 0/1 数列中包含 0/1 数量相等的最长子串和子序列。
题意:子序列,显然就是尽可能多得找 0/1 ,那么答案就是 0/1 数量 min 的两倍。对于子串,如果一个子串的 0/1 数量相等的话,那么他们前缀和相等,前缀和以后,处理相同数的最远位置就好了。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n,m,sum[100010];
char s[100010];
map<int,int> Min,Max;
int main()
{
scanf("%d%s",&n,s+1);
memset(sum,0,sizeof(sum));
int zero=0,one=0;Min[0]=0;Max[0]=0;
for (int i=1;i<=n;i++)
{
if (s[i]=='0') sum[i]=sum[i-1]-1,zero++;
else sum[i]=sum[i-1]+1,one++;
if (Min.find(sum[i])==Min.end()) Min[sum[i]]=i;
Max[sum[i]]=i;
//cout<<i<<" "<<sum[i]<<endl;
}
int ans=0;
for (auto i:Min)
{
int x=i.first;
ans=max(Max[x]-Min[x],ans);
}
printf("%d ",ans);
ans=min(zero,one)*2;
printf("%d
",ans);
return 0;
}
Problem C
题意:给定一个首尾为 1,部分位置被删除的 ETT 序列 {a_n} ,将其还原。
题解:合法的 ETT 序列中,若 a_l=a_r ,则 a_{l+1 dots r-1} 之间出现的值不能在 a_{1 dots l-1} 和 a_{r+1 dots n} 中出现,同时可以将 a_{l dots r} 替换成 a_l 而不影响 ETT 的性质。
考虑每次将现 ETT 序列中 a_l=a_r 且 a_{l+1 dots r-1} 互不相同的段落 a_{l cdots r} 。对于其中所有被删除的位 a_p=-1 ,我们可以证明只要 a_{p-2},a_{p-1} eq -1,a[l] ,就可以将 a_p 替换成 a_{p-2} 而不影响 ETT 的性质。这个性质对 k+2 也同样生效。
在将所有满足这种条件的位置全部删除之后,我们把 a_{l+2*k} 的所有被删除的位置都填上 a_l ,再用没有出现过的元素填满剩下的空隙,就能将 a_{ldots r} 缩成一个数 a_l 。最后将 a_{1dots n} 缩点之后所有的删除位置都应该填上了值,此时的序列就是答案。
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=5e5+5;
int n,m;
int a[maxn],sa,b[maxn],c[maxn];
int q[maxn],sq,e[maxn],se;
void solve(int l,int r){
int i,j,cl,t;
bool flg;
sq=-1; cl=c[a[l]];
for (i=l+1;i<r;i++){
q[++sq]=a[i];
flg=true;
while (sq>1&&flg){
flg=false;
if (c[q[sq]]==-1){
if (c[q[sq-1]]!=-1&&c[q[sq-2]]!=-1){
c[q[sq]]=c[q[sq-2]]; sq-=2;
flg=true;
}
}
else{
if (c[q[sq-1]]!=-1&&c[q[sq-2]]==-1){
c[q[sq-2]]=c[q[sq]]; sq-=2;
flg=true;
}
}
}
}
for (i=1;i<=sq;i+=2) if (c[q[i]]==-1) c[q[i]]=cl;
for (i=0;i<=sq;i+=2) if (c[q[i]]==-1){
t=e[se--]; c[q[i]]=t; j=i+2;
while (j<=sq&&c[q[j-1]]!=cl){
c[q[j]]=t; j+=2;
}
}
}
int main(){
int tt;
int i;
scanf("%d",&tt);
while (tt--){
scanf("%d",&n); m=(n<<1)-1;
for (i=1;i<=n;i++) b[i]=1;
for (i=0;i<m;i++) scanf("%d",&c[i]);
c[0]=c[m-1]=1;
for (i=m-1;i>=0;i--) b[c[i]]=0;
se=-1;
for (i=1;i<=n;i++) if (b[i]) e[++se]=i;
sa=-1;
for (i=1;i<=n;i++) b[i]=-1;
for (i=0;i<m;i++){
a[++sa]=i;
if (c[i]!=-1){
if (b[c[i]]==-1) b[c[i]]=sa;
else{
solve(b[c[i]],sa); sa=b[c[i]];
}
}
}
for (i=0;i<m;i++) printf("%d ",c[i]);
puts("");
}
return 0;
}
Problem D
题意:求出二元组的个数 (i,j) 使得 A(i^j) equiv 0 pmod{p}, (i le n, j le m) ,其中 A(i) 为 i 个数字 1 链接起来的数
题解:把数字用等比数列加起来得 frac{10^{i^j}-1}{9} equiv 0 pmod{p} ,特判 p=2,3,5 的情况,剩下的转化为求 i^jequiv 0 pmod{x} ,其中 x 是最小的正整数满足 10^x equiv 0pmod{p} 。然后再用上欧拉定理。如何计数呢,枚举 x 的素因子组成,i 必须也包含素因子。分成两种情况,一种是 x|i ,这样 j 怎么着都行,另一种考虑 dfs i 中包含 x 的素因子组成,j=cnt(n/cur) imes (m-minj+1) ,其中 cur 是 x 素因子乘起来的结果,minj 是最小的 j 使得 i^j 能整除以 x ,这个 dfs 的时候都能维护。实际上 dfs 复杂度玄学,但是能过。
由于场上分解质因数写挂了,这题没过。
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define int long long
// #define zerol
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
#ifdef zerol
#define dbg(x...) do { cout << #x << " -> "; err(x); } while (0)
void err() { cout << endl; }
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) { for (auto v: a) cout << v << ' '; err(x...); }
template<typename T, typename... A>
void err(T a, A... x) { cout << a << ' '; err(x...); }
#else
#define dbg(...)
#endif
//--------------------------------------------------------------------------------------------
const int MAXN = 1e6+5;
int prime[MAXN], pcnt;
int minp[MAXN];
struct Frac {
LL a, b;
Frac(LL a=0, LL b=0){
this->a = a;
this->b = b;
LL g = __gcd(a, b);
a /= g;
b /= g;
if (b < 0) {
a = -a;
b = -b;
}
this->a = a;
this->b = b;
}
Frac operator + (const Frac &other) {
LL newb = b * other.b;
LL newa = a * other.b + b * other.a;
return Frac(newa, newb);
}
};
void init() {
for (int i=2; i<MAXN; ++i) {
if (!minp[i]) {
prime[pcnt++] = i;
minp[i] = i;
}
for (int j=0; j<pcnt; ++j) {
LL nextp = 1LL*i*prime[j];
if (nextp >= MAXN) break;
if (!minp[nextp]) minp[nextp] = prime[j];
if (i % prime[j] == 0) {
break;
}
}
}
}
int n, p, m;
typedef pair<int, int> pii;
vector<pii> primed(int n) {
vector<pii> res;
if (n <MAXN) {
while (n > 1) {
int p = minp[n], cnt = 0;
for (; n%p==0; n/=p,++cnt);
res.push_back(make_pair(p, cnt));
}
} else {
for (int i=0; i<pcnt && 1LL*prime[i]*prime[i]<=n; ++i) {
if (n % prime[i] == 0) {
int cnt = 0;
for (; n%prime[i]==0; n/=prime[i], ++cnt);
res.push_back(make_pair(prime[i], cnt));
}
}
if (n > 1)
res.push_back(make_pair(n, 1));
}
return res;
}
vector<int> disect(int n) {
vector<int> res;
for (int i=1; 1LL*i*i<=n; ++i) {
if (n % i == 0) {
res.push_back(i);
int ano = n / i;
if (ano != i)
res.push_back(ano);
}
}
sort(res.begin(), res.end());
res.resize(unique(res.begin(), res.end()) - res.begin());
return res;
}
LL bin(LL a, LL b, LL p) {
LL res = 1;
for (; b; b>>=1, a=a*a%p)
if (b & 1) res = res * a % p;
return res;
}
int findsmallest(int n) {
vector<int> v = disect(n);
// dbg(v.size(), n);
for (int x:v) {
// dbg(x);
if (bin(10, x, p) == 1) {
return x;
}
}
return -1;
}
int check1(int p) {
for (int i=1; i<=p; ++i) {
if (bin(10, i, p) ==1) return i;
}
return p;
}
namespace stage2 {
int n, m, p, lim, mlim;
LL ans, dat2[1<<10];
vector<pii> pinfo;
set<int> S;
Frac dat[1<<21];
int solve(LL cur) {
LL res = 0;
for (int i=1; i<mlim; ++i) {
// if (dat[i].a > 0)
// res += cur/dat[i].b;
// else res -= cur/dat[i].b;
res += cur / dat2[i];
}
res = cur - res;
return res;
}
void dfs(int now, LL cur, int minj, bool flag) {
// dbg(now, cur, minj, flag);
if (now == lim) {
if (!flag) {
dbg(solve(n / cur), m-minj+1);
ans += solve(n / cur) * (m - minj + 1);
}
return;
}
const pii &info = pinfo[now];
int p = info.first, jlim = info.second;
cur *= p;
for (int i=1; cur<=n; cur*=p, ++i) {
int newminj = minj;
// if (i < jlim) {
newminj = max(newminj, (jlim + (i-1))/ i);
// }
dfs(now+1, cur, newminj, flag && (i >= jlim));
}
}
LL solve(int _n, int _m, int _p) {
S.clear();
n = _n;
m = _m;
p = _p;
ans = 0;
pinfo = primed(p);
for (const pii &p:pinfo)
S.insert(p.first);
lim = pinfo.size();
mlim = (1<<lim);
dat[0] = Frac(0, 1);
for (int mask = 1; mask < mlim; ++ mask) {
int bitcnt = 0, cb = 1;
for (int j=0; j<lim; ++j) {
if ((mask >> j) & 1) {
++bitcnt;
cb *= pinfo[j].first;
}
}
if (bitcnt % 2 == 0) cb = -cb;
dat[mask] = Frac(1, cb);
dat2[mask] = cb;
}
dfs(0, 1, 1, true);
dbg(ans);
ans += (n / p) * m;
return ans;
}
int check() {
int ans = 0;
for (int i=1; i<=n; ++i)
for (int j=1; j<=m; ++j) {
if (bin(i, j, p) == 0) {
// dbg(i);
++ans;
}
}
return ans;
}
}
LL calc(int n, int m, int p) {
return stage2::solve(n, m, p);
}
int ccheck() {
LL ans = 0;
for (int i=1; i<=n; ++i)
for (int j=1; j<=m; ++j) {
LL x = bin(i, j, p-1);
x = bin(10, x, p);
if (x == 1) ++ans;
}
return ans;
}
void solve() {
if (p == 2 || p == 5) {
puts("0");
return;
}
int x = -1;
if (p == 3) {
x = 3;
} else {
// stage 1
x = findsmallest(p-1);
}
// int ax = check1(p);
// dbg(x, ax);
dbg(x);
LL ans = calc(n, m, x);
printf("%lld
", ans);
}
signed main(int argc, char const *argv[])
{
// Frac a(2, 5), b(3, 6);
// a = a + b;
// printf("%lld %lld
", a.a, a.b);
// return 0;
// freopen("out.txt", "w", stdout);
init();
int t;
scanf("%lld", &t);
for (int kk=0; kk<t; ++kk) {
scanf("%lld%lld%lld", &p, &n, &m);
solve();
}
return 0;
}
Problem F
题意:求最大的子矩阵,保证子矩阵中的任意两个元素的差 le m 。
题解:考虑枚举上下行边界,把每一列的 max 信息和 min 信息压在一起,然后题目就变成了找一个最长的子区间,使得 max - min le m 。
这个可以用单调队列优化, max 信息维护单调递减数列, min 信息维护单调递增数列,每次从队尾加入元素,将超过 m 的信息从队头移出。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline char nc(){
// /*
static char buf[100000],*p1=buf,*p2=buf;
if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
return *p1++;
//*/return getchar();
}
inline void read(int &x){
char c=nc();int b=1;
for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
inline void read(LL &x){
char c=nc();LL b=1;
for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
inline void read(char &x){
for (x=nc();!(x=='('||x==')');x=nc());
}
inline int read(char *s)
{
char c=nc();int len=1;
for(;!(c=='('||c==')');c=nc()) if (c==EOF) return 0;
for(;(c=='('||c==')');s[len++]=c,c=nc());
s[len++]='