NOI Online 2022 爆零记
花 10 分钟把题看了一遍,上来开 T1 ,先打了个暴力,想了 40min 左右发现其实可以直接把询问离线下来维护答案,又写了一个 \(O(n^2)\) 的暴力证明了结论正确,然后用树状数组+并查集维护连续段过了这道题的大样例
T2 仔细想了以后看见集合大小的总和是 \(O(n)\) 级别的,以为是那种按集合大小数只有 \(O(\sqrt n)\) 种的结论考虑的题目,但结合数据范围发现不是
同样也通过考虑集合大小,糊了一个保证有交集的判断答案的方法,复杂度成了平方,观察到同大小集合两两不形成答案的话一定不交或相等,想到把桶改成全局的,直接优化成 \(O(n)\)
我好 naive 啊……写代码时没看到数据里有空集 (\(0 \leq k_i \leq n\)) ,大样例也不给个空集,vector
用了 .front()
函数,洛谷民间 100 RE 成 10 分……
T3 感觉是大数据结构拆贡献,打了暴力,结果系统崩了,暴力没交上去
赛后一想 T3 发现是三维偏序,码了 6.5k 后通过了民间数据
下面是简要的题解:
T1: 给一个序列,元素都有颜色,问你一个区间,对这个区间从左到右维护单调栈,栈顶小于等于当前值或者颜色与当前值相同就弹栈,问区间中有多少个位置能把栈弹空
把所有询问离线,按 \(R\) 作为时间依次扫描添加元素,标号为 \(L\) 的桶中存储 \([L,R]\) 中的元素组成的单调栈的栈底 ,如果一个位置记入答案,也就是它弹出了栈底,当前仅当小于等于栈底,或是大于栈底的最小元素,且颜色与栈底相同(可以手动模拟一下)
由于桶中存储的栈底一定是按值单调递减的,我们可以把一段栈底完全相同的区间看作一个等价段,那么相邻两段一定颜色不同
当更新的时候,找出最大的比当前元素的值小的栈底,然后再看这一个栈底的前面一个栈底是否与当前值颜色相同,那么这一段后缀的栈底都会被弹出,把这一段后缀记入答案,并区间推平成当前元素
发现连续段数每次顶多增加一个,那么直接暴力维护连续段复杂度就是对的
用并查集或者直接一个栈维护连续段,树状数组统计答案
#include <cstdio>
#define gec (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<17,stdin)),p1==p2?EOF:*p1++)
using namespace std;
char buf[1<<17],*p1=buf,*p2=buf;
int read(){
char c=gec;int x=0;
while(c<48||c>57) c=gec;
do x=(x<<1)+(x<<3)+(c^48),c=gec;
while(c>=48&&c<=57);
return x;
}
const int N=500003;
int n,q;
int a[N],b[N],c[N];
int hd[N],nxt[N],qry[N],res[N];
int mx[N],col[N],ans[N],pre[N];
int rt(int x){if(pre[x]==x) return x;return pre[x]=rt(pre[x]);}
void upd(int x,int v){for(int i=x;i<=n;i+=i&-i) c[i]+=v;}
int ask(int x){int r=0;for(int i=x;i;i^=i&-i) r+=c[i];return r;}
void extend(int x){
int p=pre[x]=x;
while(rt(p)-1){
if(b[x]>=mx[rt(rt(p)-1)]) {p=pre[rt(p)]=rt(rt(p)-1);continue;}
if(a[x]==col[rt(rt(p)-1)]) {p=pre[rt(p)]=rt(rt(p)-1);continue;}
break;
}
mx[rt(p)]=b[x];col[rt(p)]=a[x];upd(rt(p),1);upd(x+1,-1);
for(int i=hd[x];i;i=nxt[i]) res[i]=ask(qry[i]);
}
int main(){
n=read();q=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) b[i]=read();
for(int i=1;i<=q;++i){
qry[i]=read();
int x=read();
nxt[i]=hd[x];
hd[x]=i;
}
for(int i=1;i<=n;++i) extend(i);
for(int i=1;i<=q;++i) printf("%d\n",res[i]);
return 0;
}
T2: 给定若干个集合,让你随便找出一对有交且互不包含的集合
考虑什么时候无解:当包含关系形成若干条不相交的链时,一定无解,猜测这个条件也是必要的
考场上先想了一个 naive 做法:对于每一个元素,在包含它的所有集合之间进行考虑,就去掉了有交的条件
那么此时对于这些集合,仅当他们包含的关系形成一条链时无解
假设这些集合按大小排序,如果不无解,那么存在相邻两个集合互不包含,这两个集合就是答案
也就是说我们只需要对于按大小排序相邻的两个集合判断合法就可以了,用桶从大到小判断,时间复杂度成了 \(O(n^2)\)
一开始想用比如说哈希的这样那样的方法优化掉判断合法的过程,发现很难做
那么只能优化枚举元素的过程,考虑所有把可能的无解链一起用桶处理,发现这些链的第一个集合(就是最大的那个集合)一定互不相交
那么我们只需要维护一个全局桶存储包含这个元素的大小最小的集合,同样从大到小判断,如果一个集合覆盖的元素的桶中的集合有不同,就说明它一定与当前链与他有交的最后一个集合(就是最小的那个集合)互不包含
具体只需要找到所有覆盖的元素对应桶中的集合的大小最小值,这个桶中的集合与当前集合就是答案
很难说得很清楚,具体看代码吧
#include <cstdio>
#include <vector>
#include <algorithm>
#define gec (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<17,stdin)),p1==p2?EOF:*p1++)
using namespace std;
char buf[1<<17],*p1=buf,*p2=buf;
int read(){
char c=gec;int x=0;
while(c<48||c>57) c=gec;
do x=(x<<1)+(x<<3)+(c^48),c=gec;
while(c>=48&&c<=57);
return x;
}
const int N=2000003;
const int INF=0x3f3f3f3f;
int id[N],c[N];
vector<int> a[N],b[N];
bool check(int x){
if(a[x].empty()) return 0;// 不加这一句就会像我一样挂大分……QwQ
int fl=id[a[x].front()],flag=0;
for(int v:a[x])
if(fl!=id[v]){//如果覆盖了的桶中的值有不同
flag=1;
if(c[fl]>c[id[v]]) fl=id[v];//取大小最小的集合
}
if(flag) puts("YES"),printf("%d %d\n",fl,x);
return flag;
}
void append(int x){for(int v:a[x]) id[v]=x;}
void byebye(int n){for(int i=0;i<=n;++i) id[i]=0,a[i].clear(),b[i].clear();}
void solve(){
int n=read();
for(int i=1;i<=n;++i){
int k=read();
b[c[i]=k].emplace_back(i);
while(k--) a[i].emplace_back(read());
}
for(int i=n;~i;--i){
for(int v:b[i]){
if(check(v)) return byebye(n);
append(v);
}
}
puts("NO");
return byebye(n);
}
int main(){
c[0]=INF;
int T=read();
while(T--) solve();
return 0;
}
时间复杂度 \(O(n)\) ,排序都省了
T3: 一个元素有 m 个属性(\(m \leq 4\)),定义 \(f(i,j)=\min_{k=1}^m a_{i,k}+a_{j,k}+\max_{k=1}^m a_{i,k}+a_{j,k}\) ,求 \(\sum_{i=1}^n\sum_{j=1}^n f(i,j)\)
发现式子可以拆成一个 \(\min\) 和一个 \(\max\) ,两部分处理方法相近,现在考虑 \(\min\)
钦定哪一个属性取到最小值,那么就会对元素对的属性产生三个不等式的限制,这些元素对的钦定属性之和可以直接贡献答案
三个不等式拆一拆,发现是一个元素对每一个元素的属性有三个限制,直接跑三维偏序(KDT/CDQ)就可以了
在洛谷上看,似乎 zhoukangyang 等一众大佬有更高明的做法,复杂度也许更优,而且码量很小(但是看不懂代码)
给出我又慢又长的 6.5k 代码:
#include <cstdio>
#include <algorithm>
#define gec (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<16,stdin)),p1==p2?EOF:*p1++)
using namespace std;
const int N=400003;
char buf[1<<16],*p1=buf,*p2=buf;
int read(){
char c=gec;int x=0;
while(c<48||c>57) c=gec;
do x=(x<<1)+(x<<3)+(c^48),c=gec;
while(c>=48&&c<=57);
return x;
}
typedef long long ll;
int n;
namespace spc{
int main(){
ll sum=0;
for(int i=1;i<=n;++i) sum+=read();
for(int i=1;i<=n;++i) sum+=read();
printf("%lld\n",2ll*n*sum);
return 0;
}
}
namespace bf{
int a[N],b[N],c[N],trc[N];
ll trv[N],res;
int tmp[N],rk,n;
void upd(int x,int v){for(int i=x;i<=rk;i+=i&-i) trv[i]+=v,++trc[i];}
ll qryv(int x){ll r=0;for(int i=x;i;i^=i&-i) r+=trv[i];return r;}
int qryc(int x){ll r=0;for(int i=x;i;i^=i&-i) r+=trc[i];return r;}
struct elem{int a,b,w;bool t;}e[N];
bool operator<(const elem x,const elem y){if(x.a^y.a) return x.a<y.a;return x.b>y.b;}
void PartialOrder(){
sort(e+1,e+n+1);
for(int i=1;i<=n;++i) tmp[i]=e[i].b;
sort(tmp+1,tmp+n+1);
rk=unique(tmp+1,tmp+n+1)-tmp-1;
for(int i=1;i<=n;++i) e[i].b=lower_bound(tmp+1,tmp+rk+1,e[i].b)-tmp;
for(int i=1;i<=n;++i){
if(e[i].t) res+=qryv(e[i].b-1)+1ll*e[i].w*qryc(e[i].b-1);
if(!e[i].t) upd(e[i].b,e[i].w);
}
for(int i=1;i<=rk;++i) trc[i]=trv[i]=0;
}
int main(){
for(int i=1;i<=::n;++i) a[i]=read();
for(int i=1;i<=::n;++i) b[i]=read();
for(int i=1;i<=::n;++i) c[i]=read();
n=::n+::n;
for(int i=1;i<=::n;++i) e[i].a=a[i]-b[i],e[i].b=a[i]-c[i],e[i].t=0,e[i].w=a[i];
for(int i=1;i<=::n;++i) e[i+::n].a=b[i]-a[i]+1,e[i+::n].b=c[i]-a[i]+1,e[i+::n].t=1,e[i+::n].w=a[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=b[i]-a[i],e[i].b=c[i]-a[i],e[i].t=0,e[i].w=a[i];
for(int i=1;i<=::n;++i) e[i+::n].a=a[i]-b[i]+1,e[i+::n].b=a[i]-c[i]+1,e[i+::n].t=1,e[i+::n].w=a[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=b[i]-a[i],e[i].b=b[i]-c[i],e[i].t=0,e[i].w=b[i];
for(int i=1;i<=::n;++i) e[i+::n].a=a[i]-b[i],e[i+::n].b=c[i]-b[i]+1,e[i+::n].t=1,e[i+::n].w=b[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=a[i]-b[i],e[i].b=c[i]-b[i],e[i].t=0,e[i].w=b[i];
for(int i=1;i<=::n;++i) e[i+::n].a=b[i]-a[i],e[i+::n].b=b[i]-c[i]+1,e[i+::n].t=1,e[i+::n].w=b[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=c[i]-a[i],e[i].b=c[i]-b[i],e[i].t=0,e[i].w=c[i];
for(int i=1;i<=::n;++i) e[i+::n].a=a[i]-c[i],e[i+::n].b=b[i]-c[i],e[i+::n].t=1,e[i+::n].w=c[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=a[i]-c[i],e[i].b=b[i]-c[i],e[i].t=0,e[i].w=c[i];
for(int i=1;i<=::n;++i) e[i+::n].a=c[i]-a[i],e[i+::n].b=c[i]-b[i],e[i+::n].t=1,e[i+::n].w=c[i];
PartialOrder();
printf("%lld\n",res);
return 0;
}
}
namespace sol{
int a[N],b[N],c[N],d[N];
struct elem{int a,b,c,w;bool t;}e[N];
bool operator<(const elem x,const elem y){
if(x.a^y.a) return x.a<y.a;
if(x.t^y.t) return x.t;
if(x.b^y.b) return x.b>y.b;
return x.c>y.c;
}
bool cmp(const elem x,const elem y){
if(x.b^y.b) return x.b<y.b;
if(x.t^y.t) return x.t;
if(x.a^y.a) return x.a>y.a;
return x.c>y.c;
}
int trc[N];ll trv[N],res;
int tp,rk,n,tmp[N];
elem stk[N];
void upd(int x,int v){for(int i=x;i<=rk;i+=i&-i) trv[i]+=v,++trc[i];}
ll qryv(int x){ll r=0;for(int i=x;i;i^=i&-i) r+=trv[i];return r;}
int qryc(int x){ll r=0;for(int i=x;i;i^=i&-i) r+=trc[i];return r;}
void clr(int x){for(int i=x;i<=rk;i+=i&-i) if(trc[i]) trc[i]=trv[i]=0;else break;}
void solve(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
solve(l,mid);solve(mid+1,r);
int i=l,j=mid+1;
while(i<=mid&&j<=r){
if(cmp(e[i],e[j])){
if(!e[i].t) upd(e[i].c,e[i].w);
stk[++tp]=e[i++];
}
else{
if(e[j].t) res+=qryv(e[j].c-1)+1ll*qryc(e[j].c-1)*e[j].w;
stk[++tp]=e[j++];
}
}
while(j<=r){
if(e[j].t) res+=qryv(e[j].c-1)+1ll*qryc(e[j].c-1)*e[j].w;
stk[++tp]=e[j++];
}
for(int k=l;k<i;++k) if(!e[k].t) clr(e[k].c);
while(i<=mid) stk[++tp]=e[i++];
for(int k=r;k>=l;--k) e[k]=stk[tp--];
}
void PartialOrder(){
sort(e+1,e+n+1);
for(int i=1;i<=n;++i) tmp[i]=e[i].c;
sort(tmp+1,tmp+n+1);
rk=unique(tmp+1,tmp+n+1)-tmp-1;
for(int i=1;i<=n;++i) e[i].c=lower_bound(tmp+1,tmp+rk+1,e[i].c)-tmp;
solve(1,n);
}
int main(){
for(int i=1;i<=::n;++i) a[i]=read();
for(int i=1;i<=::n;++i) b[i]=read();
for(int i=1;i<=::n;++i) c[i]=read();
for(int i=1;i<=::n;++i) d[i]=read();
n=::n+::n;
for(int i=1;i<=::n;++i) e[i].a=a[i]-b[i],e[i].b=a[i]-c[i],e[i].c=a[i]-d[i],e[i].t=0,e[i].w=a[i];
for(int i=1;i<=::n;++i) e[i+::n].a=b[i]-a[i]+1,e[i+::n].b=c[i]-a[i]+1,e[i+::n].c=d[i]-a[i]+1,e[i+::n].t=1,e[i+::n].w=a[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=b[i]-a[i],e[i].b=c[i]-a[i],e[i].c=d[i]-a[i],e[i].t=0,e[i].w=a[i];
for(int i=1;i<=::n;++i) e[i+::n].a=a[i]-b[i]+1,e[i+::n].b=a[i]-c[i]+1,e[i+::n].c=a[i]-d[i]+1,e[i+::n].t=1,e[i+::n].w=a[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=b[i]-a[i],e[i].b=b[i]-c[i],e[i].c=b[i]-d[i],e[i].t=0,e[i].w=b[i];
for(int i=1;i<=::n;++i) e[i+::n].a=a[i]-b[i],e[i+::n].b=c[i]-b[i]+1,e[i+::n].c=d[i]-b[i]+1,e[i+::n].t=1,e[i+::n].w=b[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=a[i]-b[i],e[i].b=c[i]-b[i],e[i].c=d[i]-b[i],e[i].t=0,e[i].w=b[i];
for(int i=1;i<=::n;++i) e[i+::n].a=b[i]-a[i],e[i+::n].b=b[i]-c[i]+1,e[i+::n].c=b[i]-d[i]+1,e[i+::n].t=1,e[i+::n].w=b[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=c[i]-a[i],e[i].b=c[i]-b[i],e[i].c=c[i]-d[i],e[i].t=0,e[i].w=c[i];
for(int i=1;i<=::n;++i) e[i+::n].a=a[i]-c[i],e[i+::n].b=b[i]-c[i],e[i+::n].c=d[i]-c[i]+1,e[i+::n].t=1,e[i+::n].w=c[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=a[i]-c[i],e[i].b=b[i]-c[i],e[i].c=d[i]-c[i],e[i].t=0,e[i].w=c[i];
for(int i=1;i<=::n;++i) e[i+::n].a=c[i]-a[i],e[i+::n].b=c[i]-b[i],e[i+::n].c=c[i]-d[i]+1,e[i+::n].t=1,e[i+::n].w=c[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=d[i]-a[i],e[i].b=d[i]-b[i],e[i].c=d[i]-c[i],e[i].t=0,e[i].w=d[i];
for(int i=1;i<=::n;++i) e[i+::n].a=a[i]-d[i],e[i+::n].b=b[i]-d[i],e[i+::n].c=c[i]-d[i],e[i+::n].t=1,e[i+::n].w=d[i];
PartialOrder();
for(int i=1;i<=::n;++i) e[i].a=a[i]-d[i],e[i].b=b[i]-d[i],e[i].c=c[i]-d[i],e[i].t=0,e[i].w=d[i];
for(int i=1;i<=::n;++i) e[i+::n].a=d[i]-a[i],e[i+::n].b=d[i]-b[i],e[i+::n].c=d[i]-c[i],e[i+::n].t=1,e[i+::n].w=d[i];
PartialOrder();
printf("%lld\n",res);
return 0;
}
}
int main(){
int m=read();n=read();
if(m==2) return spc::main();
if(m==3) return bf::main();
if(m==4) return sol::main();
return 0;
}
补一个入门 T2:给定 \(x\),\(z\) ,求 \(xy\gcd(x,y)=z\) \(y\) 的最小解
考虑素因子分析,对 \(x,y,z\) 唯一分解为一些素数的乘积,发现本问题对于不同的素因子是独立的,也就是说我们可以分别求出 \(y\) 的素因子的最小指数然后乘起来就是最小解
假设对于某个素数 \(p\) ,它在 \(x\) 的唯一分解中的指数是 \(a\),在 \(y\) 的唯一分解中的指数是 \(b\),在 \(z\) 的唯一分解中的指数是 \(c\)
由于 \(\gcd(x,y)\) 等价于将 \(x\) ,\(y\) 的所有素因子的指数对应取 \(\min\)
那么原题可以表述成 \(\min(a,b)+a+b=c\)
对 \(\min(a,b)\) 分类讨论,得出 \(b\) 的解为:
那么现在用 Pollard Rho
算法分解质因子,我们就可以通过上面那个式子得出答案了
但是复杂度还是不可接受,考虑对所有质因子一起处理
注意到 \(\gcd\) 可以完成一个取 \(\min\) 的操作,我们用这个操作代替分讨
把上面的式子改写成 \(b=c-a-\frac{\min(c-a,2a)}{2}\)
我们就可以用乘除实现指数的加减,\(\sqrt {\dots}\) 实现指数除二,\(\gcd\) 实现指数取 \(\min\)
或者也可以不用质因子指数的形式,上述式子也可以写成 \(\frac{\frac{z}{x}}{\sqrt {\gcd(x^2,\frac{z}{x})}}\)(LionShizi 发给我的形式)
代码就很简单了,注意 \(\sqrt {\dots}\) 可能存在的误差(看讨论区一堆管理说要卡)
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
template<typename T>
T gi(){
char c=getchar();T x=0;
while(c<48||c>57) c=getchar();
do x=(x<<1)+(x<<3)+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
int main(){
int tc=gi<int>();
while(tc--){
int x=gi<int>();
ll z=gi<ll>();
if(z%x){puts("-1");continue;}
z/=x;
ll t=__gcd(1ll*x*x,z);
ll d=sqrt(t);
if((d+1)*(d+1)==t) ++d;
if((d-1)*(d-1)==t) --d; //考虑 sqrt 的误差
if(d*d==t) printf("%lld\n",z/d);
else puts("-1");
}
return 0;
}