丹钓战
solution
考虑模拟题中所描述的过程:首先第一个元素 \(a\) 入栈,它是“成功的”;之后其他不成功的元素入栈;最后某个元素 \(b\) 入栈后弹出所有元素;之后循环往复。注意到 \(b\) 成功后之后的变化和加入 \(b\) 前的元素完全没有关系了,这启示我们对每个元素找到它后面第一个会将它弹出的元素,那么每次询问只需从 \(l\) 处往后跳直到 \(r\) 即可。使用倍增即可做到单次 \(\mathcal O(\log n)\) 。
至于如何找到后面第一个会将其弹出的元素?只需要按照题意从左到右模拟一遍弹栈过程即可。
总复杂度为 \(\mathcal O(n\log n)\) 。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,q,tot,a[N],b[N],sta[N],top,id[N],go[20][N];
int main()
{
scan(n),scan(q);
for(int i=1;i<=n;++i)scan(a[i]);
for(int i=1;i<=n;++i)scan(b[i]);
for(int i=1;i<=n;++i)
{
while(top)
{
if(a[i]!=a[sta[top]]&&b[i]<b[sta[top]])break;
go[0][sta[top]]=i;--top;
}
sta[++top]=i;
}
while(top)go[0][sta[top]]=n+1,--top;
go[0][n+1]=n+1;
for(int i=1;(1<<i)<=n;++i)
for(int j=n+1;j;--j)
go[i][j]=go[i-1][go[i-1][j]];
int lim=__lg(n);
while(q--)
{
int l,r;scan(l),scan(r);
int res=1;
for(int i=lim;~i;--i)
if(go[i][l]<=r)
res+=1<<i,l=go[i][l];
putint(res,'\n');
}iobuff::flush();
return 0;
}
讨论
solution
首先有一个暴力的想法:枚举每道题,然后将所有包含该题的集合按照集合大小从小到大排序,之后依次check相邻的两个是否满足包含关系。如果不满足则直接找到解,否则无解。
该算法的缺点在于虽然总比较次数是 \(\mathcal O(m)\) 的了,但每次比较时的复杂度仍然可能达到 \(\mathcal O(n)\) 。然而注意到如果无解,那么集合之间按照包含关系会形成一棵树。如果我们找到了这棵树,那么就只需要 \(\mathcal O(\sum k)=\mathcal O(m)\) 的复杂度对所有包含关系check从而得到答案。对于暴力中排序后相邻的集合(设为 \(a,b\) ),我们知道 \(b\) 一定是 \(a\) 的祖先。而 \(a\) 的所有祖先中集合大小最小的就是它的父亲。据此建出树来,最后check即可。
注意初始时需要去重。
code
#include<bits/stdc++.h>
namespace iobuff{
const int LEN=1000000;
char in[LEN+5], out[LEN+5];
char *pin=in, *pout=out, *ed=in, *eout=out+LEN;
inline char gc(void)
{
return pin==ed&&(ed=(pin=in)+fread(in, 1, LEN, stdin), ed==in)?EOF:*pin++;
}
inline void pc(char c)
{
pout==eout&&(fwrite(out, 1, LEN, stdout), pout=out);
(*pout++)=c;
}
inline void flush()
{ fwrite(out, 1, pout-out, stdout), pout=out; }
template<typename T> inline void scan(T &x)
{
static int f;
static char c;
c=gc(), f=1, x=0;
while(c<'0'||c>'9') f=(c=='-'?-1:1), c=gc();
while(c>='0'&&c<='9') x=10*x+c-'0', c=gc();
x*=f;
}
}
using iobuff::scan;
using namespace std;
const int N=1e6+5;
const int B=3,mod=998244353;
map<int,bool>mp[N];
vector<int>p[N],q[N];
int n,pw[N],id[N],pa[N];bool pd[N];
inline bool ok(int x,int y)
{
for(int u:p[x])
{
auto pi=lower_bound(p[y].begin(),p[y].end(),u);
if(pi!=p[y].end()&&(*pi)==u)continue;
return false;
}
return true;
}
inline void clear()
{
for(int i=1;i<=n;++i)q[i].clear(),mp[i].clear();
}
int main()
{
int T;scan(T);
int lim=1e6;
pw[0]=1;for(int i=1;i<=lim;++i)pw[i]=1ll*pw[i-1]*B%mod;
while(T-->0)
{
scan(n);int tot=0;
for(int i=1;i<=n;++i)
{
id[++tot]=i;p[tot].clear();
int k,d,kk;scan(k);kk=k;
int h=0;
while(k--)
{
scan(d),p[tot].push_back(d),h+=pw[d];
h>=mod?h-=mod:0;
}
if(mp[kk].count(h)||kk==0)--tot;
else
{
mp[kk][h]=1;
for(int u:p[tot])q[u].push_back(tot);
}
}
for(int i=1;i<=tot;++i)pa[i]=tot+1;bool fl=0;
for(int i=1;i<=tot;++i)sort(p[i].begin(),p[i].end());
for(int i=1;i<=n;++i)
{
if(q[i].empty())continue;
sort(q[i].begin(),q[i].end(),[&](const int&x,const int&y){return p[x].size()<p[y].size();});
for(int t=0;t<q[i].size()-1;++t)
{
int u=q[i][t],v=q[i][t+1];
if(pa[u]==tot+1||p[pa[u]].size()>p[v].size())pa[u]=v;
else if(pa[u]!=v&&p[pa[u]].size()==p[v].size())
{
puts("YES");
if(!ok(u,pa[u]))printf("%d %d\n",id[u],id[pa[u]]);
else if(!ok(u,v))printf("%d %d\n",id[u],id[v]);
else if(!ok(pa[u],v))printf("%d %d\n",id[pa[u]],id[v]);
fl=1;break;
}
}
if(fl)break;
}
if(fl){clear();continue;}
for(int i=1;i<=tot;++i)if(pa[i]!=tot+1)
{
if(ok(i,pa[i]))continue;
puts("YES");printf("%d %d\n",id[pa[i]],id[i]);
fl=1;break;
}
if(!fl)puts("NO");
clear();
}
return 0;
}
如何正确地排序
solution
\(m=1,2\) 的情况都是平凡的,不再赘述。
对于 \(m=3\) ,考虑 \(\min\) 怎么算, \(\max\) 同理。多变量问题从来都是困难的,于是我们转换思路,考虑对每个元素计算它会贡献多少次。以第一层的元素来说,加入我们在考虑 \(a_{1,i}\) ,其贡献次数是满足如下条件的 \(j\) 的个数:
将变量和变量分开:
这便是一个经典的二维数点问题:平面上有 \(n\) 个点,第 \(i\) 个点坐标为 \((a_{1,i}-a_{2,i},a_{1,i}-a_{3,i})\) ,若干次询问以左下角为 \((-\inf,-\inf)\) ,右上角为 \((a_{2,j}-a_{1,j},a_{3,j}-a_{1,j})\) 的矩形区域内有多少个点。直接排序后+树状数组即可解决。第二层和第三层的贡献类似处理。
对于 \(m=4\) ,固然可以类似上面的方法转化为三维偏序,不过还有更有趣的做法,即使用min-max反演将max反演掉,可得:
于是又转化为 \(m\le3\) 的情况,照着之前的方法做即可。
code
巨丑无比的代码。
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
using ll=long long;
int m,n,a[5][N];
namespace sub2
{
ll ans;int p[N];
inline ll main(bool tp=0)
{
ans=0;
if(!tp)
{
for(int i=1;i<=m;++i)
for(int j=1;j<=n;++j)
ans+=a[i][j];
ans*=2*n;
}
else
{
for(int i=1;i<=n;++i)p[i]=a[1][i]-a[2][i];
sort(p+1,p+n+1);
for(int i=1;i<=n;++i)
ans+=1ll*a[1][i]*(upper_bound(p+1,p+n+1,a[2][i]-a[1][i])-1-p);
reverse(p+1,p+n+1);
for(int i=1;i<=n;++i)p[i]=-p[i];
for(int i=1;i<=n;++i)
ans+=1ll*a[2][i]*(lower_bound(p+1,p+n+1,a[1][i]-a[2][i])-1-p);
ans*=2;
}
return ans;
}
}
namespace sub3
{
int totx,toty,p[N],q[N],tmpx[N],tmpy[N];ll ans;
namespace Fenwick
{
#define lb(x) (x&(-x))
int c[N];
inline void upd(int x,int v){for(;x<=toty;x+=lb(x))c[x]+=v;}
inline int query(int x){int ret=0;for(;x;x-=lb(x))ret+=c[x];return ret;}
}
using namespace Fenwick;
inline void lsh()
{
totx=toty=0;
for(int i=1;i<=n;++i)
tmpx[++totx]=p[i],tmpx[++totx]=-p[i],tmpy[++toty]=q[i],tmpy[++toty]=-q[i];
sort(tmpx+1,tmpx+totx+1);
sort(tmpy+1,tmpy+toty+1);
totx=unique(tmpx+1,tmpx+totx+1)-tmpx-1;
toty=unique(tmpy+1,tmpy+toty+1)-tmpy-1;
}
inline int askx(int x){return lower_bound(tmpx+1,tmpx+totx+1,x)-tmpx;}
inline int asky(int x){return lower_bound(tmpy+1,tmpy+toty+1,x)-tmpy;}
vector<int>up[N],qp[N];
inline ll work(int tp)
{
ll res=0;
for(int i=1;i<=n;++i)
up[askx(p[i])].push_back(i),qp[askx(-p[i])].push_back(i);
for(int i=1;i<=totx;++i)
{
if(tp==1)
{
for(int u:up[i])upd(asky(q[u]),1);
for(int u:qp[i])res+=1ll*a[tp][u]*query(asky(-q[u]));
}
else if(tp==2)
{
for(int u:qp[i])res+=1ll*a[tp][u]*query(asky(-q[u]));
for(int u:up[i])upd(asky(q[u]),1);
}
else
{
for(int u:qp[i])res+=1ll*a[tp][u]*query(asky(-q[u])-1);
for(int u:up[i])upd(asky(q[u]),1);
}
}
fill(c+1,c+toty+1,0);
for(int i=1;i<=totx;++i)up[i].clear(),qp[i].clear();
return res*2;
}
inline ll main(bool tp=0)
{
ans=0;
for(int i=1;i<=n;++i)
p[i]=a[1][i]-a[2][i],q[i]=a[1][i]-a[3][i];
lsh();ans+=work(1);
for(int i=1;i<=n;++i)p[i]=-p[i],q[i]=-q[i];
if(!tp)ans+=work(1);
for(int i=1;i<=n;++i)
p[i]=a[2][i]-a[1][i],q[i]=a[2][i]-a[3][i];
lsh();ans+=work(2);
for(int i=1;i<=n;++i)p[i]=-p[i],q[i]=-q[i];
if(!tp)ans+=work(2);
for(int i=1;i<=n;++i)
p[i]=a[3][i]-a[1][i],q[i]=a[3][i]-a[2][i];
lsh();ans+=work(3);
for(int i=1;i<=n;++i)p[i]=-p[i],q[i]=-q[i];
if(!tp)ans+=work(3);
return ans;
}
}
namespace sub4
{
ll ans;int b[5][N];
inline ll main()
{
ans=0;
for(int i=1;i<=m;++i)
for(int j=1;j<=n;++j)
b[i][j]=a[i][j];
m=1;
for(int i=1;i<=4;++i)
{
for(int j=1;j<=n;++j)a[1][j]=b[i][j];
ans+=sub2::main();
}
m=2;
for(int i=1;i<=4;++i)
for(int j=i+1;j<=4;++j)
{
for(int t=1;t<=n;++t)
a[1][t]=b[i][t],a[2][t]=b[j][t];
ans-=sub2::main(1);
}
m=3;
for(int i=1;i<=4;++i)
for(int j=i+1;j<=4;++j)
for(int k=j+1;k<=4;++k)
{
for(int t=1;t<=n;++t)
a[1][t]=b[i][t],a[2][t]=b[j][t],a[3][t]=b[k][t];
ans+=sub3::main(1);
}
return ans;
}
}
int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=m;++i)
for(int j=1;j<=n;++j)
scanf("%d",a[i]+j);
if(m==2)return printf("%lld\n",sub2::main()),0;
if(m==3)return printf("%lld\n",sub3::main()),0;
printf("%lld\n",sub4::main());
return 0;
}