国庆suprise
T1[数论+裴蜀定理]给出[a,b],可以对序列进行2种操作,(a-b,b * 2)/(a * 2,b-a)(mod p意义下)。求把二元组变成[c,d]最少变换次数。a,b<=1e9+7
发现操作实际意义,a和b的系数是不变的,而且系数绝对值是操作次数的2次幂。设k次操作之后a的系数是x
\([x*a-(2^{k}-x)*b,(1-x)*a+(2^k-x+1)*b]\)就是操作后的数对,因为[a,b]本质不变,所以可以知道[a,b]%p=[c,d]%p,否则不合法。
把[X,Y]同余[c,d]列出来,我们想要求出x的对应限制,因为枚举k再枚举x不现实,
发现x同余\((c-d+2^{k+1}*b+c+d ) / 2*(a+b)\)(%p)
这是合法的情况。所以我们枚举k,只要能求出x在[1,2 ^ k]内的一个解,说明k'次操作合法。当2^k>=p一定合法,因为无论x是多少一定在p内,所以一定可以落在合法区间,这时如果x=0代表=p,也合法。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define rint register int
#define ll long long
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
#define pii pair<ll,ll>
map<pii,ll>mp;
deque<pii>q;
ll mod,qq,a,b,c,d,cf2[40],_cf2[40];
inline ll qpow(ll x,ll y)
{
ll op=1;
while(y)
{
if(y&1)op=op*x%mod;
x=x*x%mod;
y>>=1;
}
return op;
}
inline void deal_()
{
_f(i,1,qq)
{
ll a=re(),b=re(),c=re(),d=re();
if((a+b)%mod==(c+d)%mod)//这样才会一定有解
{
//开始找解
// chu("in;%lld %lld\n",a,b);
mp.clear();q.clear();
mp[make_pair(a,b)]=0;//需要多少回凑出来
q.push_front(make_pair(a,b));
ll ans=0;
while(!q.empty())
{
pii tmp=q.front();q.pop_front();
if(tmp.first==c&&tmp.second==d)
{
ans=mp[tmp];break;
}
// chu("here:%lld %lld %lld\n",tmp.first,tmp.second,mp[tmp]);
ll a1=tmp.first*2%mod,a2=(tmp.second-tmp.first+mod)%mod;
if(mp.find(make_pair(a1,a2))==mp.end())
q.push_back(make_pair(a1,a2)),
mp[make_pair(a1,a2)]=mp[tmp]+1;
a1=(tmp.first-tmp.second+mod)%mod,a2=tmp.second*2%mod;
if(mp.find(make_pair(a1,a2))==mp.end())
q.push_back(make_pair(a1,a2)),
mp[make_pair(a1,a2)]=mp[tmp]+1;
}
chu("%lld\n",ans);
}
else
{
chu("-1\n");
}
}
}
int main()
{
// freopen("rubyonly.in","r",stdin);
//freopen("1.out","w",stdout);
mod=re(),qq=re();
if(qq<=3000)
{
deal_();return 0;
}
ll bj=log2(mod)+1;
// chu("bj:%lld\n",bj);
cf2[0]=1;bool chao=0;
_cf2[0]=1;
_f(i,1,30)
{
if(!chao)
{
cf2[i]=cf2[i-1]*2;
if(cf2[i]>=mod)chao=1;
}
else cf2[i]=mod;
_cf2[i]=_cf2[i-1]*2%mod;
}
_f(i,1,qq)
{
// chu("\n\nnew\n");
a=re(),b=re(),c=re(),d=re();
if((a+b)%mod!=(c+d)%mod)
{
chu("-1\n");continue;
}
if(a==c&&b==d)
{
chu("0\n");continue;
}
ll ny=qpow(a+b,mod-2);
_f(k,1,bj)//枚举边界,指数
{
ll my=(c+_cf2[k]*b%mod)%mod*ny%mod;
//chu("now my:%lld cf2[%d]:%lld\n",my,k,cf2[k]);
if(my<=cf2[k]&&my>=1)
{
chu("%d\n",k);break;
}
}
}
return 0;
}
/*
5 10
2 1 3 0
2 1 4 4
1 3 4 0
0 2 0 4
3 3 1 2
0 1 0 1
0 3 0 3
0 1 0 1
1 2 4 4
1 0 1 1
2
1
2
-1
-1
0
0
0
1
-1
*/
T2[数据结构]给出颜色序列,q次询问[l,r]区间内颜色出现次数>=K的颜色数量,强制在线。(n,q<=5e5)
对于离线
出现次数就是值域分块。把值域按照sqrt分块,维护cnt[x]表示出现次数在x块对应区间的颜色个数,buc[x]维护散块出现次数是x的数个数,因为整块<=sqrt,散块大小<=sqrt所以复杂度对。查询就莫队移动,insert和delete对应更新buc和cnt。tot记录总的颜色出现个数。
点击查看代码
/*
出现次数值域分块
*/
#include <bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
char buf[1<<21], *p1 = buf, *p2 = buf; inline char getc() { return (p1 == p2 and (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++); }
#define getchar getc
#endif
template <typename T> inline void get(T & x){
x = 0; char ch = getchar(); bool f = 0; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
while (ch >= '0' and ch <= '9') x = (x<<1) + (x<<3) + (ch^48), ch = getchar(); f and (x = -x);
} template <typename T, typename ... Args> inline void get(T & x, Args & ... _Args) { get(x); get(_Args...); }
#define rep(i,a,b) for (register int (i) = (a); (i) <= (b); ++(i))
#define pre(i,a,b) for (register int (i) = (a); (i) >= (b); --(i))
const int N = 1e5 + 10;
int n, a[N], q, k, ans[N], opt, buk[N], siz, sn, bl[N], lp[N], rp[N];
struct queries {
int l, r, k, id;
bool operator < (const queries & b) const {
if (bl[l] != bl[b.l]) return l < b.l;
if (bl[l] & 1) return r < b.r;
return r > b.r;
}
} qr[N];
struct BLO {
int tot, cntpts[N], cntblk[N];
void add(int pos, int v) {
cntpts[pos] += v;
cntblk[bl[pos]] += v;
tot += v;
}
int qry(int k) {
if (k == 1) return tot;
k--;
int bl_k = bl[k], ret = 0;
rep(i,1,bl_k-1) ret += cntblk[i];
rep(i,lp[bl_k],k) ret += cntpts[i];
return tot - ret;
}
} b;
signed main() {
get(n, q, opt); siz = sqrt(n);
rep(i,1,n) get(a[i]);
lp[1] = 1;
rep(i,1,n) {
bl[i] = (i-1) / siz + 1;
if (bl[i] != bl[i-1]) lp[bl[i]] = i, rp[bl[i-1]] = i-1;
}
sn = bl[n], rp[sn] = n;
if (opt == 0) {
rep(i,1,q) {
get(qr[i].l, qr[i].r, qr[i].k), qr[i].id = i;
int mn = min((qr[i].l - 1) % n + 1, (qr[i].r - 1) % n + 1), mx = max((qr[i].l - 1) % n + 1, (qr[i].r - 1) % n + 1);
qr[i].l = mn;
qr[i].r = mx;
qr[i].k = (qr[i].k - 1) % n + 1;
}
sort(qr+1, qr+1+q);
for (register int i = 1, l = 1, r = 0; i <= q; i++) {
while (l > qr[i].l) {
-- l;
if (buk[a[l]] != 0) b.add(buk[a[l]], -1);
buk[a[l]]++;
b.add(buk[a[l]], 1);
}
while (r < qr[i].r) {
++ r;
if (buk[a[r]] != 0) b.add(buk[a[r]], -1);
buk[a[r]]++;
b.add(buk[a[r]], 1);
}
while (r > qr[i].r) {
b.add(buk[a[r]], -1);
buk[a[r]]--;
if (buk[a[r]] != 0) b.add(buk[a[r]], 1);
-- r;
}
while (l < qr[i].l) {
b.add(buk[a[l]], -1);
buk[a[l]]--;
if (buk[a[l]] != 0) b.add(buk[a[l]], 1);
++ l;
}
ans[qr[i].id] = b.qry(qr[i].k);
} rep(i,1,q) cout << ans[i] << '\n';
}
}
对于在线
出现次数>sqrt的个数<sqrt,出现次数<sqrt的我们可以统计个缀直接块暴力。
具体的,维护
cor[i][j]:表示在1~i块中出现次数是j的总和
pol[i][j][k]表示(i,j)块中出现次数在k~S的后缀和
chac[i][j]:维护在i~j块中的出现次数>S的颜色是谁。
询问的时候,如果K<=S,那么chac的大小直接加上整块的,对于pol也加上后缀,在散块加的时候为了不重复限制次数是恰好。
如果K>S,直接在chac里面暴力硬扫出块中cnt>K的就可以
如果不单独分出S,pol会炸。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int M=320,N=1e5+100;
int cor[M][N],buc[N],tot,pol[M][M][M],boun[M],S,Code,a[N],n,opt,q,belon[N];
int rem[N];
vector<int>chac[M][M];
int main()
{
//freopen("suzipei.in","r",stdin);
// freopen("1.out","w",stdout);
n=re(),q=re(),opt=re();S=sqrt(n);Code=n/S;
_f(i,1,n)a[i]=re();
_f(i,1,Code)boun[i]=S*i;
boun[Code]=n;
_f(i,1,Code)//块,然后长度
{
_f(j,boun[i-1]+1,boun[i])
cor[i][a[j]]++,belon[j]=i;
_f(j,1,n)cor[i][j]+=cor[i-1][j];
}
_f(i,1,Code)
{
fill(buc+1,buc+1+n,0);
_f(j,i,Code)
{
_f(k,1,S)pol[i][j][k]=pol[i][j-1][k];//出现次数是k的个数
//把它加进去
chac[i][j]=chac[i][j-1];
_f(k,boun[j-1]+1,boun[j])
{
if(buc[a[k]]==S)
{
pol[i][j][buc[a[k]]++]--;
chac[i][j].push_back(a[k]);//颜色放进去,就行了吧?
}
else if(buc[a[k]]<S)
{
pol[i][j][buc[a[k]]++]--;
pol[i][j][buc[a[k]]]++;
}
}
}
}
_f(i,1,Code)
_f(j,1,Code)
f_(k,S,1)
pol[i][j][k]+=pol[i][j][k+1];
//然后查询就行,pol是次数的前缀和(k就是),cor是1~i块中某种颜色的出现次数
int ans=0;int l=0,r=0,lim=0;
memset(buc,0,sizeof(buc));
// for(int i=1;i<=Code;++i)
// {
// for(int j=1;j<=Code;++j)
// {
// for(int k=1;k<=S;++k)
// printf("%d ",pol[i][j][k]);
// printf("\n");
// }
// printf("\n");
// }
_f(i,1,q)
{
l=re(),r=re(),lim=re();
int x=min((l+ans*opt-1)%n+1,(r+ans*opt-1)%n+1);
int y=max((l+ans*opt-1)%n+1,(r+ans*opt-1)%n+1);
l=min(x,y);r=max(x,y);
lim=(lim+ans*opt-1)%n+1;
ans=0;
int lc=belon[l],rc=belon[r];
//chu("Ask:%d %d %d %d %d %d\n",l,r,lim,lc,rc,S);
if(lc==rc)//一个块直接求解
{
// chu("here\n");
_f(j,1,tot)buc[rem[j]]=0;tot=0;
_f(j,l,r)
{
// chu("buc[%d]:%d\n",a[j],buc[a[j]]);
buc[a[j]]++;
if(buc[a[j]]==lim)++ans;
if(buc[a[j]]==1)rem[++tot]=a[j];
}
}
else
{
if(lim>S)//先处理整块,如果限制本身大,直接从处理好的里面找符合要求的,一定在chac里面
{
for(rint ele:chac[lc+1][rc-1])
{
if(cor[rc-1][ele]-cor[lc][ele]>=lim)ans++;
}
}
else//否则直接累加上
{
ans=chac[lc+1][rc-1].size()+pol[lc+1][rc-1][lim];
}
//开始散块的
// chu("nowans:%d\n",ans);
_f(j,1,tot)buc[rem[j]]=0;tot=0;
_f(j,l,boun[lc])
{
// chu("insert:%d\n",a[j]);
buc[a[j]]++;
if(buc[a[j]]==1)rem[++tot]=a[j];
if(buc[a[j]]+cor[rc-1][a[j]]-cor[lc][a[j]]==lim)ans++;//chu("has\n");//这里还需要
}
_f(j,boun[rc-1]+1,r)
{
// chu("you insert:%d\n",a[j]);
buc[a[j]]++;
if(buc[a[j]]==1)rem[++tot]=a[j];
if(buc[a[j]]+cor[rc-1][a[j]]-cor[lc][a[j]]==lim)++ans;//chu("yes\n");
}
}
chu("%d\n",ans);
}
return 0;
}
/*
10 3 0
3 4 3 2 4 1 2 1 3 1
2 1 1
4 6 1
1 5 2
*/
T3[Mex]询问【1】多段[l,r]区间的mex【2】整个区间mex的mex
对于多段,线段树直接维护;
对于整个区间,我们考虑怎么算出一个x可不可以成为mex:last_mexmex的位置出现了1mex-1的数,也就是1~mex-1的数最近出现位置>last_mex,线段树维护
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1e6+100;
int Mi[N<<2],tag[N<<2];
int n,a[N],lst[N],can[N];//维护目前每个值出现最晚位置,区间最小值,如果没出现?min>lst[pos],就是不合法,所以应该赋值-无穷
int ls[N<<2],rs[N<<2],tot,root;
struct Que
{
int l,r,id;
bool operator<(const Que&U)const
{
return l<U.l;
}
}e[N];
inline void Update(int&rt,int l,int r,int pos,int val)
{
if(!rt)rt=++tot;
if(l==r)
{
Mi[rt]=val;//一定是更新
return;
}
int mid=(l+r)>>1;
if(pos<=mid)Update(ls[rt],l,mid,pos,val);
else Update(rs[rt],mid+1,r,pos,val);
Mi[rt]=min(Mi[ls[rt]],Mi[rs[rt]]);
// else if(ls[rt])Mi[rt]=Mi[ls[rt]];
// else if(rs[rt])Mi[rt]=Mi[rs[rt]];
}
inline int Query(int rt,int l,int r,int L,int R)
{
//if(R<L)return 1e9;
//if(!R)return 1e9;
// chu("now find:%d--%d\n",l,r);
if(!rt)return -1e9;
if(L<=l&&r<=R)return Mi[rt];
int mid=(l+r)>>1;int ans=1e9;//取最小值
if(L<=mid)ans=Query(ls[rt],l,mid,L,R);
if(R>mid)ans=min(ans,Query(rs[rt],mid+1,r,L,R));
return ans;
}
#define lson (rt<<1)
#define rson (rt<<1|1)
//支持单点查询和区间修改
inline void _build(int rt,int l,int r)
{
if(l==r)
{
Mi[rt]=lst[l];return;
}
int mid=(l+r)>>1;
_build(lson,l,mid);
_build(rson,mid+1,r);
//wait? I need?只需要单点查询...
Mi[rt]=min(Mi[lson],Mi[rson]);
}
inline void _pushdown(int rt)
{
if(tag[rt])
{
Mi[lson]=Mi[rson]=tag[lson]=tag[rson]=tag[rt];
tag[rt]=0;
}
}
inline void _pushup(int rt)
{
Mi[rt]=min(Mi[lson],Mi[rson]);
}
inline void _update(int rt,int l,int r,int L,int R,int val)
{
if(L<=l&&r<=R)
{
Mi[rt]=tag[rt]=val;return;
}
int mid=(l+r)>>1;
_pushdown(rt);
if(L<=mid)_update(lson,l,mid,L,R,val);
if(R>mid)_update(rson,mid+1,r,L,R,val);
_pushup(rt);
}
inline int _query(int rt,int l,int r,int pos)
{
if(l==r)
{
return Mi[rt];
}
_pushdown(rt);
int mid=(l+r)>>1;
if(pos<=mid)return _query(lson,l,mid,pos);
else return _query(rson,mid+1,r,pos);
}
int main()
{
// freopen("kaiser_kell.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();
_f(i,1,n)a[i]=re();
_f(i,0,(n<<2))Mi[i]=-1e9;
_f(i,1,n)
{
Update(root,1,n,a[i],i);
if(can[a[i]])
{
lst[a[i]]=i;continue;
}
if(a[i]==1)
{
if(lst[a[i]]==i-1&&i!=1){}
else can[1]=1;
}
else
{
int Minp=Query(root,1,n,1,a[i]-1);
if(Minp>lst[a[i]])can[a[i]]=1;
}
lst[a[i]]=i;
}
_f(i,1,n)//按照值最后check一遍
{
if(can[i])continue;
int Minp=Query(root,1,n,1,i-1);
if(Minp>lst[i])can[i]=1;
}
if(Query(root,1,n,1,n)>0)//都出现过
can[n+1]=1;
int mex=1;
while(can[mex])++mex;
chu("%d\n",mex);
int Mex=1;
fill(lst+1,lst+1+n+1,0);
fill(can+1,can+1+n+1,0);
fill(Mi+1,Mi+1+(n<<2),0);
fill(ls+1,ls+2+n,0);
fill(rs+1,rs+2+n,0);
_f(i,1,n)
{
can[a[i]]++;
while(can[Mex])Mex++;
lst[i]=Mex;//lst存区间mex
rs[i]=n+1;//rs存值nxt
}
f_(i,n,1)
{
ls[i]=rs[a[i]];
rs[a[i]]=i;
}
//ls维护位置下一个出现位置
_build(1,1,n);
// chu("out\n");
int Q=re();
_f(i,1,Q){
e[i].l=re(),e[i].r=re();e[i].id=i;
}
sort(e+1,e+1+Q);
e[0].l=1;
_f(i,1,Q)
{
if(e[i].l!=e[i-1].l)
{
_f(j,e[i-1].l,e[i].l-1)//需要删除的数
{
// chu("del:%d\n",j);
int l=j+1,r=ls[j]-1;//这些区间里找
if(l>r)continue;//没有影响
int ans=r+1;
while(l<=r)
{
int mid=(l+r)>>1;
//chu("query(%d):%d a[j];%d\n",mid,_query(1,1,n,mid),a[j]);
if(_query(1,1,n,mid)>a[j])
{
ans=mid;r=mid-1;
}
else l=mid+1;
}
// chu("let %d--%d down to:%d\n",ans,ls[j]-1,a[j]);
_update(1,1,n,ans,ls[j]-1,a[j]);
// chu("query(%d):%d\n",3,_query(1,1,n,3));
}
}
// chu("no\n");
can[e[i].id]=_query(1,1,n,e[i].r);
// chu("%d--%d:ans:%d\n",e[i].l,e[i].r,can[e[i].id]);
// chu("op\n");
}
_f(i,1,Q)chu("%d ",can[i]);
return 0;
}
/*
10
1 2 3 4 5 6 7 8 9 10
5
1 6
5 8
3 3
7 10
5 9
*/