1.14
数列分块
1.数列分块3:区间加减+区间询问x的前驱(严格小于)
思路:同2,询问时边角暴力,块内需要保持有序,进行二分查找第一个小于c的数。注意两点:
1.边角暴力的时候是对v操作,块内二分是对blk操作。
2.块内二分时找到之后更新答案记得加上lazy[i]。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
#define db(x) cout<<#x<<":"<<x<<endl;
#define lf(x) ((x)-1)*bl_size+1
#define rt(x,n) min((x)*bl_size,(n))
const int M=2e5+20,P=1e9+7;
int n,bl_size,blnum[M],lazy[M],v[M];
vector<int> blk[M];
inline void reset(int x){
blk[x].clear();
for(int i=lf(x);i<=rt(x,n);++i)
blk[x].push_back(v[i]);
sort(blk[x].begin(),blk[x].end());
}
inline void bl_init(int n){
bl_size=sqrt(n);
for(int i=1;i<=n;++i)
blnum[i]=(i-1)/bl_size+1,blk[blnum[i]].push_back(v[i]);
for(int i=blnum[1];i<=blnum[n];++i)
sort(blk[i].begin(),blk[i].end());
}
inline void bl_add(int l,int r,int c){
for(int i=l;i<=rt(blnum[l],r);++i)
v[i]+=c;
reset(blnum[l]);
if (blnum[l]!=blnum[r]){
for(int i=lf(blnum[r]);i<=r;++i)
v[i]+=c;
reset(blnum[r]);
}
for(int i=blnum[l]+1;i<=blnum[r]-1;++i)
lazy[i]+=c;
}
inline int query(int l,int r,int c){
int ans=-1;
for(int i=l;i<=rt(blnum[l],r);++i)
if (v[i]+lazy[blnum[l]]<c)
ans=max(ans,v[i]+lazy[blnum[l]]);
if (blnum[l]!=blnum[r])
for(int i=lf(blnum[r]);i<=r;++i)
if (v[i]+lazy[blnum[r]]<c)
ans=max(ans,v[i]+lazy[blnum[r]]);
for(int i=blnum[l]+1;i<=blnum[r]-1;++i){
vector<int>::iterator pos=lower_bound(blk[i].begin(),blk[i].end(),c-lazy[i]);
if (pos!=blk[i].begin())
--pos,ans=max(ans,(*pos)+lazy[i]);//注意
}
return ans;
}
struct TTTT{
void init(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&v[i]);
bl_init(n);
}
void run(){
init();
for(int i=1;i<=n;++i){
int op,l,r,c;
scanf("%d%d%d%d",&op,&l,&r,&c);
if(op==0)
bl_add(l,r,c);
else
printf("%d
",query(l,r,c));
}
}
}TTT;
int main(){
TTT.run();
return 0;
}
2.数列分块4:区间加减+区间求和
要注意lazy标记的含义,表示懒得加,区间修改并不直接加到sum里面。另外记得开LL。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define lf(x) ((x)-1)*bl_size+1
#define rt(x,n) min((x)*bl_size,(n))
#define Blforl(l,r) for(int i=(l);i<=rt(blnum[(l)],(r));++i)
#define Blforr(l,r) for(int i=lf(blnum[(r)]);i<=(r);++i)
#define Blforbl(l,r) for(int i=blnum[(l)]+1;i<=blnum[(r)]-1;++i)
const int M=1e5+20;
int n,bl_size;//整体信息
int blnum[M];
LL v[M];//单点信息
LL lazy[M],sum[M];//整块信息
inline void bl_init(){
bl_size=sqrt(n);
for(int i=1;i<=n;++i)
blnum[i]=(i-1)/bl_size+1,sum[blnum[i]]+=v[i];
}
inline void add(int l,int r,int c){
Blforl(l,r)
v[i]+=c,sum[blnum[i]]+=c;
if (blnum[l]!=blnum[r])
Blforr(l,r)
v[i]+=c,sum[blnum[i]]+=c;
Blforbl(l,r)
lazy[i]+=c;
}
inline LL query(int l,int r){
LL ans=0;
Blforl(l,r)
ans+=v[i]+lazy[blnum[i]];
if (blnum[l]!=blnum[r])
Blforr(l,r)
ans+=v[i]+lazy[blnum[i]];
Blforbl(l,r)
ans+=sum[i]+lazy[i]*bl_size;
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&v[i]);
bl_init();
for(int i=1;i<=n;++i){
int op,l,r,c;
scanf("%d%d%d%d",&op,&l,&r,&c);
if (op==0)
add(l,r,c);
else
printf("%lld
",query(l,r)%(c+1));
}
return 0;
}
3.数列分块5:区间开方+区间求和。
对于区间开方的问题,要注意到开logn次之后就会都变成0和1,因此直接暴力区间修改也是ok的,这是因为每个点最多开32次根就变成0或1的。但是为了防止每次判断是否已经是0和1的时候每个元素都访问,所以可以采用分块优化的策略。边角暴力,块内先看flag,如果已经全是01就跳过,否则就暴力修改。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define lf(x) ((x)-1)*bl_size+1
#define rt(x,n) min((x)*bl_size,(n))
#define Blforl(l,r) for(int i=(l);i<=rt(blnum[(l)],(r));++i)
#define Blforr(l,r) for(int i=lf(blnum[(r)]);i<=(r);++i)
#define Blforx(x) for (int i=lf(x);i<=rt(x);++i)
#define Blforbl(l,r) for(int i=blnum[(l)]+1;i<=blnum[(r)]-1;++i)
const int M=1e5+20;
int n,bl_size;//整体信息
int blnum[M],v[M];//单点信息
int flag[M],sum[M];//整块信息
inline void bl_init(){
bl_size=sqrt(n);
for(int i=1;i<=n;++i)
blnum[i]=(i-1)/bl_size+1,sum[blnum[i]]+=v[i];
for(int i=1;i<=blnum[n];++i)
flag[i]=1;
}
inline void blsqrt(int l,int r){
Blforl(l,r)
sum[blnum[i]]-=v[i],v[i]=sqrt(v[i]),sum[blnum[i]]+=v[i];
if (blnum[l]!=blnum[r])
Blforr(l,r)
sum[blnum[i]]-=v[i],v[i]=sqrt(v[i]),sum[blnum[i]]+=v[i];
Blforbl(l,r){
if (!flag[i])
continue;
flag[i]=sum[i]=0;
for(int j=lf(i);j<=rt(i,r);++j){
v[j]=sqrt(v[j]),sum[i]+=v[j];
if(v[j]>1)
flag[i]=1;
}
}
}
inline LL query(int l,int r){
LL ans=0;
Blforl(l,r)
ans+=v[i];
if (blnum[l]!=blnum[r])
Blforr(l,r)
ans+=v[i];
Blforbl(l,r)
ans+=sum[i];
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&v[i]);
bl_init();
for(int i=1;i<=n;++i){
int op,l,r,c;
scanf("%d%d%d%d",&op,&l,&r,&c);
if (op==0)
blsqrt(l,r);
else
printf("%lld
",query(l,r));
}
return 0;
}
2020WannaflyD3
A. 黑色气球
思路:水题,上三角矩阵和是n*(sigma),之后减去第一行得a1,再用第一行求解即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
int a[2000][2000];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
scanf("%d",&a[i][j]);
if (n==2){
printf("1 1
");
return 0;
}
LL sum=0;
for(int i=1;i<=n-1;++i)
for(int j=i+1;j<=n;++j)
sum+=a[i][j];
sum/=n-1;
LL sum2=0;
for(int i=1;i<=n;++i)
sum2+=a[1][i];
sum2=(sum2-sum)/(n-2);
printf("%lld",sum2);
for(int i=2;i<=n;++i)
printf(" %lld",a[1][i]-sum2);
cout<<endl;
return 0;
}
E. 棋技哥
C. 无向图定向
思路:答案=最小染色数-1
D. 求和
思路:推式子
G. 火山哥周游世界
思路:虚树和最远点?
分两类讨论
F. 社团管理
思路:决策单调。整体二分。
I. N门问题
思路:无论打开了哪扇门,A选择的门在打开之后都会变成全场唯一概率最小的门。
H. 火山哥的序列
思路:保证数据互不相同,是用来枚举倍数的,这样就可以保证复杂度是nlogn。
J. 简单字符串
Lyndon串:s的最小后缀是本身。
Lyndon分解:任意串可以划分为一堆Lyndon串,v1q1,v2q1...
且v1>v2>...>vm
存在性和唯一性
若q1%k!=0
v1 q1/k
q1/k!=0
v1 q1/k v2q2 v3 q3 ……
只用算Lyndon分解的v1和q1即可。
Edu 80
A.Deadline
题意:给定d和n,问是否存在x,使得x+(lfloorfrac{d}{x+1} floor)<=n
思路:容易想到,在x+1>(sqrt{d})之后(或者多几个数),就都一样了,所以只需要暴力判(1simsqrt{d}+100)即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
#define db(x) cout<<#x<<":"<<x<<endl;
const int M=2e5+20,P=1e9+7;
struct TTTT{
int n,d;
void init(){
scanf("%d%d",&n,&d);
}
void run(){
init();
int x=0,lim=min((int)sqrt(d)+100,n);
while(x<=lim){
if (x+(d+x)/(x+1)<=n){
printf("YES
");
return;
}
++x;
}
printf("NO
");
}
}TTT;
int main(){
int T;
scanf("%d",&T);
for(int i=1;i<=T;++i)
TTT.run();
return 0;
}
B.Yet Another Meme Problem
题意:给定A和B,统计(1leq aleq A)且(1leq bleq B)a⋅b+a+b=conc(a,b)的对数。
思路:b+1=10^|b|,直接统计即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
#define db(x) cout<<#x<<":"<<x<<endl;
const int M=2e5+20,P=1e9+7;
struct TTTT{
int a,b;
void init(){
scanf("%d%d",&a,&b);
}
int dig(int x){
int ans=0;
while(x)
x/=10,++ans;
return ans;
}
void run(){
init();
printf("%lld
",1LL*a*(dig(b+1)-1));
}
}TTT;
int main(){
int T;
scanf("%d",&T);
for(int i=1;i<=T;++i)
TTT.run();
return 0;
}
C.Two Arrays
题意:给定n和m,统计数列(a,b)的个数,满足长度均为m,元素范围1-n,(a_ileq b_i),a不降,b不升。
思路:我是dp做法,但实际上看题解其实很简单,考虑(a_1,a_2,...,a_m,b_m,b_{m-1},...,b_1),这个序列就是不降序列,所以直接统计这个序列的个数就可以,算就行。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
#define db(x) cout<<#x<<":"<<x<<endl;
const int M=2e5+20,P=1e9+7;
struct TTTT{
int n,m,dp[15][3000];
void init(){
scanf("%d%d",&n,&m);
}
void run(){
init();
for(int i=0;i<=n-1;++i)
dp[1][i]=n-i;
for(int i=2;i<=m;++i)
for(int j=0;j<=n-1;++j)
for(int k=0;j+k<=n-1;++k)
dp[i][j]=(dp[i][j]+1LL*dp[i-1][j+k]*(k+1)%P)%P;
int ans=0;
for(int i=0;i<=n-1;++i)
ans=(ans+dp[m][i])%P;
printf("%d
",ans);
}
}TTT;
int main(){
TTT.run();
return 0;
}
D.Minimax Problem
题意:给定n个长度为m的数组,可以选择两个数组合成一个新的数组,新的数组的每个数都是原来两个数组的对应位置的最大值,现求新的数组最小值最大的情况下,要选的两个数组的位置。
思路:二分最小值,设为k,将矩阵中(geq k)设为1,(<k)的设为0,那么就会得到n个字节长度为m的数,现在只需要查看是否存在两个数,使得|操作之后为全1。由于不同数个数最多为(2^m),因此直接(n^2)暴力判即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r+1)>>1)
#define db(x) cout<<#x<<":"<<x<<endl;
const int M=3e5+20,P=1e9+7;
struct TTTT{
int n,m;
int a[M][10];
void init(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&a[i][j]);
}
void run(){
init();
int l=0,r=1e9;
int num[300],lim=(1<<m)-1;
while(l!=r){
for(int i=0;i<=lim;++i)
num[i]=0;
for(int i=1;i<=n;++i){
int ans=0;
for(int j=1;j<=m;++j)
ans=(ans<<1)+((a[i][j]>=mid)?1:0);
/*db(mid);
db(i);
db(ans);*/
++num[ans];
}
bool v=false;
for(int i=lim;i>=0;--i){
for(int j=lim;j>=0;--j)
if (num[i]&&num[j]&&((i|j)==lim)){
v=true;
break;
}
if (v)
break;
}
if (v)
l=mid;
else
r=mid-1;
}
//db(l);
vector<int> v[300];
for(int i=1;i<=n;++i){
int ans=0;
for(int j=1;j<=m;++j)
ans=(ans<<1)+((a[i][j]>=l)?1:0);
v[ans].push_back(i);
}
for(int i=lim;i>=0;--i)
for(int j=lim;j>=0;--j)
if (v[i].size()&&v[j].size()&&((i|j)==lim)){
printf("%d %d
",v[i][0],v[j][0]);
return;
}
}
}TTT;
int main(){
TTT.run();
return 0;
}
E.Messenger Simulator
题意:现有一个1-n的序列,给了m个数,每次将这个数提到序列的最前面,问每个数所能达到的最靠前位置和最靠后位置。
思路:设原序列为空,那么操作序列可以等价为:(n,n-1,...,2,1,a_1,a_2,...,a_m),观察这个序列,考虑数k,如果在后m个数中出现了k,那么k的最小值就是1,否则就是k本身(因为移动其他数一定不会让k的位置更靠前)。至于最大值,则是序列中相邻k构成区间的区间不同数的个数,相当于两次之间有多少个数被提到了k的前面,除了所有k出现的位置构成的相邻区间,k的最后一次出现位置和(a_m)构成的区间的不同数个数也要统计进来(感性写个例子理解一下)。那么就变成了区间不同数问题,而且可以离线做,因此树状数组即可。我傻不拉几写了个主席树,最后还因为数组没有开大挂了……
注意:主席树一定要预估好数组大小。如果数组越界,只要没有访问到系统内存,那么是不会报RE的,所以有可能访问到已经用过的内存,造成错误,会出现各种各样的WA,TLE,MLE等等奇怪的错误。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define db(x) cout<<#x<<":"<<x<<endl;
const int M=2e7+10,P=998244353,MM=6e5+20;//96M,不用怕MLE
int cnt;
int a[MM],root[MM*2],L[M],R[M],v[M],last[MM],rtn[MM],nxt[MM],vis[MM],ansmn[MM],ansmx[MM],num[MM];
int build(int l,int r){
int rt=++cnt,mid=(l+r)/2;
v[rt]=0;
if (l==r)
return rt;
L[rt]=build(l,mid);
R[rt]=build(mid+1,r);
return rt;
}
int update(int idp,int l,int r,int pos,int x){
int rt=++cnt,mid=(l+r)/2;
L[rt]=L[idp],R[rt]=R[idp];
if (x)
v[rt]=v[idp]+1;
else
v[rt]=v[idp]-1;
if (l==r)
return rt;
if (pos<=mid)
L[rt]=update(L[idp],l,mid,pos,x);
if (mid<pos)
R[rt]=update(R[idp],mid+1,r,pos,x);
return rt;
}
int query1(int idp,int l,int r,int ql,int qr){
if (ql<=l&&r<=qr)
return v[idp];
int ans=0,mid=(l+r)/2;
if (ql<=mid)
ans+=query1(L[idp],l,mid,ql,qr);
if (mid<qr)
ans+=query1(R[idp],mid+1,r,ql,qr);
return ans;
}
int main(){
int n,m;
//freopen("out.txt","w",stdout);
//freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
a[i]=n-i+1;
for(int i=1;i<=m;++i)
scanf("%d",&a[i+n]),++num[a[i+n]];
root[0]=build(1,n+m);
int ccnt=0;
for (int i=1;i<=n+m;++i){
if (last[a[i]])
root[ccnt+1]=update(root[ccnt],1,n+m,last[a[i]],0),++ccnt;
root[ccnt+1]=update(root[ccnt],1,n+m,i,1),++ccnt;
nxt[last[a[i]]]=i,last[a[i]]=i;
rtn[i]=ccnt;
}
for (int i=1;i<=n+m;++i){
if (vis[a[i]])
continue;
vis[a[i]]=1;
if (num[a[i]]>0)
ansmn[a[i]]=1;
int p=i;
while(nxt[p])
ansmx[a[i]]=max(ansmx[a[i]],query1(root[rtn[nxt[p]]],1,n+m,p,nxt[p])),p=nxt[p];
ansmx[a[i]]=max(ansmx[a[i]],query1(root[rtn[n+m]],1,n+m,p,n+m));
}
for(int i=1;i<=n;++i)
printf("%d %d
",ansmn[i]?ansmn[i]:i,ansmx[i]);
return 0;
}