NOI Online 2022 提高组
丹钓战
题意:
有\(n\)个二元组\((a_i,b_i)\),初始时栈\(S\)为空。
当向其中加入元素\((a_i,b_i)\)时,先不断弹出栈顶元素,直到栈顶元素\((a_j,b_j)\)满足\(a_i\neq a_j,b_i<b_j\)时,再将\((a_i,b_i)\)加入栈中。
如果某个元素入栈后仅有这一个元素在栈中,则称这个元素是成功的。
有\(q\)个询问,每个询问形如\([l,r]\),表示询问当把\([l,r]\)的二元组按顺序入栈时,有多少个元素是成功的?
\(n,q\leq 5*10^5\)
题解:
考虑对每个元素预处理一个数组\(pre[i]\),表示该元素入栈时,栈顶的上个元素的标号。
那么询问转化为\([l,r]\)内,有多少个\(k\)满足\(pre[k]<l\)
预处理出\(pre[i]\)数组,然后主席树二维数点。
\(pre[i]\)怎么预处理呢,递推模拟就可以
#include<bits/stdc++.h>
using namespace std;
namespace red{
//#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=5e5+10,mod=998244353,inf=2e9;
int n,m;
int a[N],b[N];
int st[N],top;
int pre[N];
int rt[N];
struct zxtree
{
int ans[N*20],son[N*20][2];
int tot=0;
inline void update(int pos,int l,int r,int pre,int &p)
{
p=++tot;
ans[p]=ans[pre]+1;
son[p][0]=son[pre][0],son[p][1]=son[pre][1];
if(l==r) return;
if(pos<=mid) update(pos,l,mid,son[pre][0],son[p][0]);
else update(pos,mid+1,r,son[pre][1],son[p][1]);
}
inline int query(int tl,int tr,int l,int r,int pre,int p)
{
if(tl<=l&&r<=tr) return ans[p]-ans[pre];
int sum=0;
if(tl<=mid) sum+=query(tl,tr,l,mid,son[pre][0],son[p][0]);
if(tr>mid) sum+=query(tl,tr,mid+1,r,son[pre][1],son[p][1]);
return sum;
}
}T;
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>a[i];
}
for(int i=1;i<=n;++i)
{
cin>>b[i];
while(top>0)
{
if(b[st[top]]>b[i]&&a[st[top]]!=a[i]) break;
--top;
}
pre[i]=st[top];
st[++top]=i;
T.update(pre[i]+1,1,n+1,rt[i-1],rt[i]);
}
for(int i=1;i<=m;++i)
{
int l,r;cin>>l>>r;
cout<<T.query(1,l,1,n+1,rt[l-1],rt[r])<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
5 5 7 2
5 0 7 2
1 3 1 2 5
1 2 3 1 4
*/
讨论
题意:
有\(n\)个人正在打模拟赛,模拟赛有\(n\)道题目。
如果两个人会的题目有交集,并且不存在包含关系,两个人就会讨论。
请你找出一对会讨论的人,或打印不存在。
\(n\leq 10^6\),所有人会的题目总数\(\leq 2*10^6\)
题解:
我们先将人按会的题目数从小到大排序。
对于当前处理到的第\(i\)个人,假如他会题目\(x\)。
只要让他和上一个会题目\(x\)的人做一次比较即可。
简略证明:
假如当前人\(i\)会题目\(x\),上一个会题目\(x\)的人是\(j\),再之前会题目\(x\)的人是\(k\)
因为处理到了\(i\),所以说明\(j,k\)不会进行讨论。
\(j\)会的题目更多,说明\(j\)包含\(k\)
那么,如果\(i,j\)不会讨论,说明\(i\)包含\(j\),也就包含了\(k\),没有必要比较。
如果\(i,j\)会讨论,就可以结束比较了耶~
被比较过的人就可以删除了,之后不再理会。
所以总复杂度\(O(m)\)
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=1e6+10,inf=2e9;
int n,m;
struct node
{
vector<int> q;
int id;
inline bool operator < (const node &t) const
{
return q.size()<t.q.size();
}
}a[N];
bool vis[N],del[N];
int pre[N];
inline void init()
{
for(int i=1;i<=n;++i)
{
a[i].q.clear();
pre[i]=0;
del[i]=0;
}
}
inline bool check(int x)
{
for(int t:a[x].q)
{
if(!vis[t]) return 1;
}
return 0;
}
inline void main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int skx;cin>>skx;
while(skx--)
{
cin>>n;
init();
for(int i=1;i<=n;++i)
{
int k;cin>>k;
while(k--)
{
int x;cin>>x;
a[i].q.emplace_back(x);
}
a[i].id=i;
}
sort(a+1,a+n+1);
bool flag=0;
for(int i=1;i<=n&&!flag;++i)
{
for(int t:a[i].q)
{
vis[t]=1;
}
for(int t:a[i].q)
{
if(del[pre[t]]||pre[t]==0);
else
{
if(check(pre[t]))
{
cout<<"YES\n";
cout<<a[pre[t]].id<<' '<<a[i].id<<'\n';
flag=1;
break;
}
else del[pre[t]]=1;
}
pre[t]=i;
}
for(int t:a[i].q)
{
vis[t]=0;
}
}
if(!flag)
{
cout<<"NO\n";
}
}
}
}
signed main()
{
red::main();
return 0;
}
/*
3
5
4 1 2 3 5
3 1 2 3
2 1 2
1 1
1 4
4
3 1 2 3
3 2 3 4
0
4 1 2 3 4
2
3 1 3 5
3 2 3 4
*/
如何正确地排序
题意:
有一个\(m*n\)的数组\(a_{i,j}\)
定义\(f(i,j)=\min_{k=1}^n(a_{k,i}+a_{k,j})+max_{k=1}^n(a_{k,i}+a_{k,j})\)
求\(\sum_{i=1}^n\sum_{j=1}^nf(i,j)\)
\(n\leq *10^5,2\leq m\leq 4\)
题解:
当\(m=2\)时:
\(ans=2*n*a_{i,j}\)
当\(m=3\)时:
考虑哪些位置有贡献:
这是一个二维偏序,可以直接草
当然还有作为最大值的位置。
当\(m=4\)时:
哈哈,当然可以三维偏序直接日,不过不优雅(不会写)
考虑正难则反,先让\(ans=2*n*a_{i,j}\)
然后去掉不合法的位置
变形一下
这样也就是二维偏序了,只不过要枚举哪些行要作为数组\(A,B,C\)
注意,这里如果\(m=3\)的时候用这个,每个\(B_i\)要减去\(2\)倍的贡献。
如果\(m=4\),则不用,因为本来是要减去两倍的,但是每个数字,如果它不是最值,它会被夹在中间两次,刚好减去两倍。!!
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=5e5+10,inf=2e9;
int n,m,ans;
int a[4][N];
struct BIT
{
int tr[N];
inline void clear()
{
memset(tr,0,sizeof(tr));
}
inline void update(int x,int k)
{
for(int i=x;i<=4e5;i+=lowbit(i)) tr[i]+=k;
}
inline int query(int x)
{
int sum=0;
for(int i=x;i>=1;i-=lowbit(i)) sum+=tr[i];
return sum;
}
}T;
struct node
{
int x,y,k;
inline bool operator < (const node &t) const
{
if(x!=t.x) return x<t.x;
return k<t.k;
}
}q[N];
inline void work(int t1,int t2,int t3,int val)
{
//cout<<t1<<' '<<t2<<' '<<t3<<"!!"<<endl;
T.clear();
//a[t1][i]-a[t2][i]<=a[t2][j]-a[t1][j]
//a[t2][i]-a[t3][i]<=a[t3][j]-a[t2][j]
int num=0;
for(int i=1;i<=n;++i)
{
++num;
q[num].x=a[t1][i]-a[t2][i]+(t1>t2);
q[num].y=a[t2][i]-a[t3][i]+(t2>t3);
q[num].k=0;
++num;
q[num].x=a[t2][i]-a[t1][i];
q[num].y=a[t3][i]-a[t2][i];
q[num].k=a[t2][i];
}
int pp=2e5;
sort(q+1,q+num+1);
for(int i=1;i<=num;++i)
{
//cout<<i<<' '<<num<<"!!"<<endl;
if(q[i].k!=0)
{
//cout<<q[i].y<<"???"<<endl;
ans-=val*T.query(q[i].y+pp)*q[i].k;
}
else
{
//cout<<q[i].y<<"!!"<<endl;
T.update(q[i].y+pp,1);
}
}
//cout<<"--------------------"<<endl;
}
inline void main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>m>>n;
for(int i=0;i<m;++i)
{
for(int j=1;j<=n;++j)
{
cin>>a[i][j];
ans+=2*n*a[i][j];
}
}
for(int i=0;i<m;++i)
for(int j=0;j<m;++j)
for(int k=0;k<m;++k)
if(i!=j&&i!=k&&j!=k) work(i,j,k,5-m);
cout<<ans<<'\n';
}
}
signed main()
{
red::main();
return 0;
}
/*
2 3
1 4 5
6 5 4
4 5
1 7 2 2 7
9 10 4 10 3
7 7 8 10 2
1 8 7 9 5
*/