心得
组合计数、计算几何、模拟、dp,堪称我的四大垃圾领域
B.Crazy Binary String(思维题)
给你一个长度不超过N(N<=1e5)的01串,
问你最长01个数相等的子串,最长01个数相等的子序列,分别是多长
01子串,把1视为单点+1,0视为单点-1,作一遍前缀和,
那么,当前点r可以在上一个前缀和与之相同的点l-1之后续上一段[l,r],这一段[l,r]和为0也就是01数量相等
01子序列,显然能取到2*min(0,1)的个数
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=1e5;
char s[N+10];
int len,n0,n1;
int ans;
int last[N*2+10],now[N*2+10],sum[N+10];
//0 -1 1 -1 -1 1 -1 -1 1
//0 -1 0 -1 -2 -1 -2 -3 -2
//sum[r]==sum[l-1][l,r]==0 r-(l-1)+1==r-l
int main()
{
while(~scanf("%d",&len))
{
scanf("%s",s+1);
n0=0;n1=0;
for(int i=1;i<=len;++i)
{
if(s[i]=='0')n0++,sum[i]=sum[i-1]-1;
if(s[i]=='1')n1++,sum[i]=sum[i-1]+1;
last[N+sum[i]]=-1;
now[N+sum[i]]=0;
}
last[N]=0;now[N]=0;
ans=0;
for(int i=1;i<=len;++i)
{
if(~last[N+sum[i]])
{
now[N+sum[i]]+=i-(last[N+sum[i]]);
ans=max(ans,now[N+sum[i]]);
}
last[N+sum[i]]=i;
}
printf("%d %d\n",ans,2*min(n0,n1));
}
return 0;
}
H.Magic Line(计算几何)
给你N(N为偶数且N<=1e3)个不同的点(xi,yi),|xi|,|yi|<=1e3
要求你输出两个整点,使之确定的直线,将平面划成两部分后,一边恰有一半
考虑按y增序排,y相同按x增序,也就是处理二维偏序的排序方式,
如果过n/2和n/2+1两个点的中点,画一条斜率为负,但近似水平的线,是可以将上下剖开的
但这个中点可能不是整点,所以直接用n/2和n/2+1两个点平移,
一个左移1e8单位,一个右移1e8单位,从而将直线拉平
但n/2和n/2+1两个点可能位于同一水平线上,这个时候,
左边的点再上移1单位,右边的点下移1单位,就可以使原来的两个点不重合了
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
const int off=1e8;
int t,n;
struct node
{
int x,y;
}e[N],c,d;
bool operator<(node a,node b)
{
return a.y<b.y||(a.y==b.y&&a.x<b.x);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d%d",&e[i].x,&e[i].y);
sort(e+1,e+n+1);
c=e[n/2];d=e[n/2+1];
if(c.y==d.y)printf("%d %d %d %d\n",c.x+off,c.y-1,d.x-off,d.y+1);
else printf("%d %d %d %d\n",c.x+off,c.y,d.x-off,d.y);
}
return 0;
}
J.LRU management(模拟/双向链表)
模拟题,让你实现LRU替换算法的原理,
即如果不在块中就加进去,如果在,就更新它的最后访问时间
如果块满,就删掉块内最早一个访问的元素,
还有,询问按插入顺序的前驱和后继的值
模拟,先hash,注意,把字符串压成10位整数的ans不能从0乘,
不然区分不了0和00这种前导0字符串,应从res=1乘
我太菜了,也许只有我犯这个错误
双向链表,map维护是对应的第几个块,相当于id号,
而链表维护这个块在什么位置,更新位置时,将其按索引访问,把前驱后继一改,挪到最后
由于按时间戳维护的这条双向链表,在最前面的就是被轮到前面去的,
开始建两个虚节点head和tail放INF,便于查前驱和后继不用特判NULL
小学期,当时缓存置换算法用的LRU,那个时候仔细想了想,想用Splay实现
后来觉得Java实现起来太复杂,就用的两个map,今天下午搞了一发c++Splay然后血wa,当然最后调过了
Splay放进去的也是时间戳的标号,i从1到q的编号,更新时取出last删掉,然后改成i再塞回去
Splay也得放一个-INF和INF,便于查前驱和后继
双向链表+map
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
const int INF=0x3f3f3f3f;
int T,q,m,op,v,last,tot,cnt;
//带索引的双向链表
//删除时只删除头结点的后一个的位置
//复杂度O(nlogn)
char s[15];
unordered_map<ll,int>pos;
struct node
{
node* pre;
node* nex;
int v;
ll rk;
}head,tail,arr[maxn];
void cut()
{
node* x=head.nex;
node* xx=x->nex;
xx->pre=&head;
head.nex=xx;
pos.erase(x->rk);
cnt--;
}
void add(node *x)
{
node* xx=tail.pre;
xx->nex=x;
x->pre=xx;
x->nex=&tail;
tail.pre=x;
cnt++;//代表链表中有几个值
if(cnt>m)cut();//有两个虚节点 故超过m+2才删
}
void del(int x)
{
node* xx=arr[x].pre;
node* xxx=arr[x].nex;
xx->nex=xxx;
xxx->pre=xx;
cnt--;
}
void debug()
{
node* A = &head;
while(A != NULL)
{
printf("->%d ", A->v);
A = A->nex;
}
printf("\n");
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&q,&m);
cnt=0;tot=0;
pos.clear();
head.pre=NULL;head.nex=&tail;head.v=INF;
tail.pre=&head;tail.nex=NULL;tail.v=INF;
for(int i=1;i<=q;++i)
{
scanf("%d%s%d",&op,s,&v);
ll rk=1;
int len=strlen(s);
for(int j=0;j<len;j++)
rk=rk*10+(s[j]-'0');
if(op==0)
{
if(!pos.count(rk))
{
pos[rk]=tot;//第几个hash值 映射唯一
arr[tot].v=v;
arr[tot].rk=rk;
tot++;
add(&arr[pos[rk]]);
printf("%d\n",v);
//debug();
}
else
{
int p=pos[rk];
printf("%d\n",arr[p].v);
del(p);
add(&arr[p]);
//debug();
}
}
else if(op==1)
{
if(!pos.count(rk))puts("Invalid");
else
{
int p=pos[rk];
if(v==0)printf("%d\n",arr[p].v);
else if(v==-1)
{
int res=(arr[p].pre)->v;
if(res==INF)puts("Invalid");
else printf("%d\n",res);
}
else if(v==1)
{
int res=(arr[p].nex)->v;
if(res==INF)puts("Invalid");
else printf("%d\n",res);
}
}
}
}
}
return 0;
}
/*
1
8 3
0 0101010 1
0 0101011 2
1 0101010 1
0 1100000 3
0 0101011 -1
0 1111111 4
1 0101011 -1
1 0101010 0
*/
Splay+map
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF=2147483647;
const int maxn=5e5+10;
inline int read()
{
register int x=0,t=1;
register char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-'){t=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*t;
}
int root,tot;
struct query
{
int op;
char s[11];
ll Hash;//hash值
int rk;//rank
int v;
}e[maxn];
ll hs[maxn],num;
struct Node
{
int ch[2];//左右儿子
int val;//值
int ff;//父节点
int size;//子树大小
int cnt;//数字的数量
}t[maxn];
void pushup(int u)//下放操作
{
t[u].size=t[t[u].ch[0]].size+t[t[u].ch[1]].size+t[u].cnt;
//当前子树的大小是左子树大小加上右子树大小当前当前节点个数
}
void rotate(int x)//旋转操作
{
int y=t[x].ff;//y是x的父节点
int z=t[y].ff;//z是y的父节点
int k=(t[y].ch[1]==x);//x是y的左儿子(0)还是右儿子(1)
t[z].ch[t[z].ch[1]==y]=x;//把x旋转为z的儿子
t[x].ff=z;//x的父亲更新为z
t[y].ch[k]=t[x].ch[k^1];//把x的儿子给y
t[t[x].ch[k^1]].ff=y;//更新父节点
t[x].ch[k^1]=y;//y变为x的
t[y].ff=x;//y的父亲更新为x
pushup(y);pushup(x);//更新子节点数量
}
void splay(int x,int goal)//旋转操作,将x旋转为goal的儿子
{
while(t[x].ff!=goal)
{
int y=t[x].ff;//x的父亲节点
int z=t[y].ff;//x的祖父节点
if(z!=goal)//如果z不是goal
(t[y].ch[0]==x)^(t[z].ch[0]==y)?rotate(x):rotate(y);
//如果x和y同为左儿子或者右儿子先旋转y
//如果x和y不同为左儿子或者右儿子先旋转x
//如果不双旋的话,旋转完成之后树的结构不会变化
rotate(x);//再次旋转x,将x旋转到z的位置
}
if(goal==0)//如果目标位置是0,则是将x旋转到根节点的位置
root=x;//更新根节点
}
void insert(int x)//插入x
{
int u=root,ff=0;//当前位置u,u的父节点ff
while(u&&t[u].val!=x)//当u存在并且没有移动到当前的值
{
ff=u;//向下u的儿子,父节点变为u
u=t[u].ch[x>t[u].val];//大于当前位置则向右找,否则向左找
}
if(u)//存在这个值的位置
t[u].cnt++;//增加一个数
else//不存在这个数字,要新建一个节点来存放
{
u=++tot;//新节点的位置
if(ff)//如果父节点非根
t[ff].ch[x>t[ff].val]=u;
t[u].ch[0]=t[u].ch[1]=0;//不存在儿子
t[tot].ff=ff;//父节点
t[tot].val=x;//值
t[tot].cnt=1;//数量
t[tot].size=1;//大小
}
splay(u,0);//把当前位置移到根,保证结构的平衡
}
bool find(int x)//查找x的位置,并将其旋转到根节点
{
int u=root;
if(!u)return false;//树空
while(t[u].ch[x>t[u].val]&&x!=t[u].val)//当存在儿子并且当前位置的值不等于x
u=t[u].ch[x>t[u].val];//跳转到儿子,查找x的父节点
splay(u,0);//把当前位置旋转到根节点
return t[u].val==x;
}
int Next(int x,int f)//查找x的前驱(0)或者后继(1) 返回的是节点号
{
find(x);
int u=root;//根节点,此时x的父节点(存在的话)就是根节点
if(t[u].val>x&&f)return u;//如果当前节点的值大于x并且要查找的是后继
if(t[u].val<x&&!f)return u;//如果当前节点的值小于x并且要查找的是前驱
u=t[u].ch[f];//查找后继的话在右儿子上找,前驱在左儿子上找
while(t[u].ch[f^1])u=t[u].ch[f^1];//要反着跳转,否则会越来越大(越来越小)
return u;//返回位置
}
void Delete(int x)//删除x
{
int last=Next(x,0);//查找x的前驱
int next=Next(x,1);//查找x的后继
splay(last,0);splay(next,last);
//将前驱旋转到根节点,后继旋转到根节点下面
//很明显,此时后继是前驱的右儿子,x是后继的左儿子,并且x是叶子节点
int del=t[next].ch[0];//后继的左儿子
if(t[del].cnt>1)//如果超过一个
{
t[del].cnt--;//直接减少一个
splay(del,0);//旋转
}
else
t[next].ch[0]=0;//这个节点直接丢掉(不存在了)
}
int kth(int x)//查找排名为x的数 从小到大
{
int u=root;//当前根节点
if(t[u].size<x)//如果当前树上没有这么多数
return 0;//不存在
while(1)
{
int y=t[u].ch[0];//左儿子
if(x>t[y].size+t[u].cnt)
//如果排名比左儿子的大小和当前节点的数量要大
{
x-=t[y].size+t[u].cnt;//数量减少
u=t[u].ch[1];//那么当前排名的数一定在右儿子上找
}
else//否则的话在当前节点或者左儿子上查找
if(t[y].size>=x)//左儿子的节点数足够
u=y;//在左儿子上继续找
else//否则就是在当前根节点上
return t[u].val;
}
}
int T,q,m,op,rk,v,last;
int cao[maxn],vis[maxn];
int main()
{
scanf("%d",&T);
while(T--)
{
root=0;
tot=0;//清空splay
insert(INF);
insert(-INF);
scanf("%d%d",&q,&m);
for(int i=1;i<=q;++i)
{
scanf("%d%s%d",&e[i].op,e[i].s,&e[i].v);
ll res=1;
int len=strlen(e[i].s);
for(int j=0;j<len;j++)
res=res*10+(e[i].s[j]-'0');
hs[i]=e[i].Hash=res;
}
sort(hs+1,hs+q+1);
num=unique(hs+1,hs+q+1)-(hs+1);
for(int i=1;i<=q;++i)
{
e[i].rk=lower_bound(hs+1,hs+num+1,e[i].Hash)-hs;
vis[e[i].rk]=0;
}
for(int i=1;i<=q;++i)
{
op=e[i].op;
rk=e[i].rk;
v=e[i].v;
if(op==0)
{
if(vis[rk]&&find(last=vis[rk]))//确定在块中
{
Delete(last);//最后一次访问时间 更新为i
vis[rk]=i;
insert(i);
cao[i]=cao[last];
printf("%d\n",cao[i]);
}
else
{
//判块满 删第一个
if(t[root].size==m+2)
{
//由于有个-INF 所以查第二
int z=kth(2);//不更改Hash[s]==x的s的值了 只删掉最后一次访问时间
Delete(z);
}
vis[rk]=i;
insert(i);
cao[i]=v;
printf("%d\n",cao[i]);
}
}
else if(op==1)
{
if(vis[rk]&&find(last=vis[rk]))
{
if(v==0)printf("%d\n",cao[last]);
else if(v==-1)
{
int z=t[Next(last,0)].val;
if(z==-INF)puts("Invalid");
else printf("%d\n",cao[z]);
}
else if(v==1)
{
int z=t[Next(last,1)].val;
if(z==INF)puts("Invalid");
else printf("%d\n",cao[z]);
}
else puts("Invalid");//???
}
else puts("Invalid");
}
}
}
return 0;
}
F.Planting Trees(单调队列)
N*N(N<=500)的矩阵,求一个最大矩阵面积,使得矩阵内的最大值-最小值<=M(0<=M<=1e5)
矩阵内的元素1<=ai<=1e5,保证<25e7,所以可以用做法
枚举上限行top,枚举下限行down,对于固定的top,
down向下枚举过程中,最大值只增不减,最小值只减不增,
有继承的性质,那只需要O(n)求出左右扩充的距离即可,
这里参考hdu3530,枚举右端点,可以单调队列求出左端点到什么位置
维护的是一个开口向左的喇叭口形状,最大值单调递减队列,最小值单调递增队列,
队列首就是这一段区间的最大值和最小值,只需要保证队列首的差值在[0,M]间即可,
每次剔除不合法位置时,从左边开始剔
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=505;
typedef long long ll;
int T,n,m;
int a[maxn][maxn],b[maxn][maxn];
int q1[maxn],head1,rear1;//最大值递减单调队列
int q2[maxn],head2,rear2;//最小值递增单调队列
int L[maxn],R[maxn];
int mx[maxn],mn[maxn];
ll ans;
ll solve(int p[maxn][maxn])
{
ll res=0;
for(int top=1;top<=n;++top)//枚举悬线上限行
{
for(int i=1;i<=n;++i)
mx[i]=-1e9,mn[i]=1e9;//每一条悬线的最大值
for(int down=top;down<=n;++down)//枚举悬线下限行
{
int now=1;
head1=rear1=0;
head2=rear2=0;
for(int i=1;i<=n;++i)
{
mx[i]=max(mx[i],p[down][i]);
mn[i]=min(mn[i],p[down][i]);
}
for(int i=1;i<=n;++i)
{
while(head1<rear1&&mx[q1[rear1-1]]<mx[i])rear1--;
while(head2<rear2&&mn[q2[rear2-1]]>mn[i])rear2--;
q1[rear1++]=i;
q2[rear2++]=i;
while(head1<rear1&&head2<rear2&&mx[q1[head1]]-mn[q2[head2]]>m)
{
if(q1[head1]<q2[head2])now=q1[head1++]+1;
else now=q2[head2++]+1;
}
if(head1<rear1&&head2<rear2&&mx[q1[head1]]-mn[q2[head2]]>=0)
res=max(res,1ll*(i-now+1)*(down-top+1));
}
}
}
return res;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
scanf("%d",&a[i][j]);
printf("%lld\n",solve(a));
}
return 0;
}
D.Big Integer(数论)
一个数列,第一项是1,第二项是11,第n项是n个1的字符串表示的数,
现在,令A(q)代表第q项,现在给定n,m,素数p,均<=1e9,
求有多少项i(1<=i<=n),j(1<=j<=m),满足
p=2或5时,11111显然mod 2或mod 5不能为0,
去掉了这两种情况之后,就可以保证底数和模数互质了,从而欧拉定理一波
9e9*9e9,一开始没用快速乘,WA自闭了槽
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5;
bool ok[maxn];
ll prime[maxn],cnt;
ll fac[40],num[40];//循环节的素因子 及其出现次数
int t;
ll p,n,m;
void sieve()
{
for(ll i=2;i<maxn;++i)
{
if(!ok[i])prime[cnt++]=i;
for(int j=0;j<cnt;++j)
{
if(i*prime[j]>=maxn)break;
ok[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
ll mul(ll x,ll n,ll mod)
{
ll res=0;
for(;n;n>>=1,x=(x+x)%mod)
if(n&1)res=(res+x)%mod;
return res;
}
ll modpow(ll x,ll n,ll mod)//mod最大9p 9e9
{
ll res=1;
for(;n;n>>=1,x=mul(x,x,mod))//9e9*9e9 注意快速乘
if(n&1)res=mul(res,x,mod);//
return res;
}
ll calphi(ll x)//x 9e9
{
ll ans=x;
for(int i=0;i<cnt;++i)
{
if(prime[i]*prime[i]>x)break;
if(x%prime[i]==0)
{
ans=ans/prime[i]*(prime[i]-1);
while(x%prime[i]==0)x/=prime[i];
}
}
if(x>1)ans=ans/x*(x-1);
return ans;
}
int main()
{
sieve();
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld%lld",&p,&n,&m);
if(p==2||p==5){puts("0");continue;}//1111%2不可能为0 1111%5不可能为0
//10^n==1(mod 9p) 循环节必为phi(9*p)的约数
ll mx=calphi(9*p);//最大循环节9p
ll d=mx;//循环节的值
for(ll i=1;i*i<=mx;++i)
{
if(mx%i==0)
{
if(i<d&&modpow(10,i,9*p)==1)
{
d=i;//找到最小解
break;
}
}
}
for(ll i=1;i*i<=mx;++i)
{
if(mx%i==0)
{
if(mx/i<d&&modpow(10,mx/i,9*p)==1)
{
d=mx/i;//找到最小解
}
}
}
int tot=0;
for(int i=0;i<cnt;++i)
{
if(prime[i]*prime[i]>d)break;
if(d%prime[i]==0)
{
fac[++tot]=prime[i];
num[tot]=1;d/=prime[i];//省去初始化
while(d%prime[i]==0)num[tot]++,d/=prime[i];
}
}
if(d>1)fac[++tot]=d,num[tot]=1;
ll res=0;
for(ll j=1;j<=min(30ll,m);++j)
{
ll g=1;
for(int k=1;k<=tot;++k)
{
ll up=(num[k]+j-1)/j;
g*=modpow(fac[k],up,1e10);//g<9p 所以相当于没模
}
res=res+n/g;
if(j==30)res=res+n/g*(m-30);
}
printf("%lld\n",res);
}
return 0;
}
/*
1
998244353 247394239 247924723 开快速乘!9e9*9e9
*/
G.Removing Stones(分治)
N(N<=3e5)堆石子,第i堆个数1到1e9,每次Mary可以选择一个区间[L,R]
在这些堆石子上玩以下游戏:
①如果石子总和为奇数,从最少堆删去一个,然后进行步骤②,否则直接步骤②
②每次选择两个非空堆,各取一个石子
若最后石子可取完,则称Mary胜,否则败,
问有多少[l,r]符合条件
考虑对于每一个最大值mx,包含它的区间其余数的和得大于等于mx
也就是区间的数的总和,至少为2*mx,
ST预处理最大值位置,便于RMQ,预处理前缀和和后缀和
分治时,统计两个端点同区间和跨区间的贡献,
将当前[l,r]的最大值当做必取的值,在短的区间里枚举端点,在另一个区间里二分
由于后缀和是倒序的,所以实际上从后往前第pos的值得用从前往后第n+1-pos的位置推导
代码比较简洁,然而调下标相当繁琐
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
typedef long long ll;
int t,n;
int dp[maxn][20];
int dep[maxn];
ll pre[maxn],suf[maxn];
void ST(int tot)
{
for(int i=1;i<=tot;++i)
dp[i][0]=i;//存的是最小值的下标
for(int len=1;(1<<len)<=tot;++len)
{
for(int l=1;l+(1<<len)-1<=tot;++l)
{
if(dep[dp[l][len-1]]>dep[dp[l+(1<<(len-1))][len-1]])dp[l][len]=dp[l][len-1];
else dp[l][len]=dp[l+(1<<(len-1))][len-1];
}
}
}
int RMQ(int l,int r)//返回最大值下标
{
int len=log(r-l+1)/log(2);
if(dep[dp[l][len]]>dep[dp[r-(1<<len)+1][len]])return dp[l][len];
else return dp[r-(1<<len)+1][len];
}
ll cdq(int l,int r)
{
if(l>=r)return 0;
int pos=RMQ(l,r);//枚举最大值点
ll ans=0;
//处理短的一半 保证复杂度为log
if(pos-l<r-pos)//枚举左端点
{
for(int i=l;i<=pos;++i)//pre[lb]-pre[i-1] >=2*dep[pos]的最左位置
ans+=r-(lower_bound(pre+pos,pre+r+1,pre[i-1]+2*dep[pos])-pre)+1;
}
else
{
for(int i=pos;i<=r;++i)
ans+=(n+1-(lower_bound(suf+(n+1-pos),suf+(n+1-l)+1,suf[(n+1-(i+1))]+2*dep[pos])-suf))-l+1;
}
return ans+cdq(l,pos-1)+cdq(pos+1,r);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&dep[i]);
for(int i=1;i<=n;++i)
pre[i]=pre[i-1]+dep[i];
for(int i=1;i<=n;++i)//便于正向二分 逆序处理
suf[i]=suf[i-1]+dep[n+1-i];
ST(n);
printf("%lld\n",cdq(1,n));
}
return 0;
}