(只挑选了几道可以启发思维的题)
(时间紧迫,写得可能比较简略)
一句话题解:李超线段树模拟treap
Treap中一个点的深度就是它在dfs序上向左单调上升序列长度与向右单调上升序列长度之和
两个点的LCA就是他们dfs序区间上的最大值(因为是大根堆)
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 200005
#define ui unsigned int
#define lc i<<1
#define rc i<<1|1
const int INF=0x3f3f3f3f;
struct node{
int l,r,pos;ui mx;
int ans1,ans2;
}a[N<<2];
int EF1(int i,ui k)
{
if(a[i].mx<=k)return 0;
if(a[i].l==a[i].r)return 1;
if(a[lc].mx<=k)return EF1(rc,k);
else return a[i].ans1-a[lc].ans1+EF1(lc,k);
}
int EF2(int i,ui k)
{
if(a[i].mx<=k)return 0;
if(a[i].l==a[i].r)return 1;
if(a[rc].mx<=k)return EF2(lc,k);
else return a[i].ans2-a[rc].ans2+EF2(rc,k);
}
void pushup(int i)
{
if(a[lc].mx<a[rc].mx)
a[i].pos=a[rc].pos,a[i].mx=a[rc].mx;
else
a[i].pos=a[lc].pos,a[i].mx=a[lc].mx;
a[i].ans1=a[lc].ans1+EF1(rc,a[lc].mx);
a[i].ans2=a[rc].ans2+EF2(lc,a[rc].mx);
}
void build(int i,int l,int r)
{
a[i].l=l;a[i].r=r;
if(l==r){a[i].pos=l;a[i].ans1=a[i].ans2=1;return;}
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(i);
}
void insert(int i,int x,ui k)
{
if(a[i].l>x||a[i].r<x)return;
if(a[i].l==a[i].r){
a[i].mx=k;
return;
}
insert(lc,x,k);insert(rc,x,k);
pushup(i);
}
pair<ui,int> qans;
void query(int i,int l,int r)
{
if(a[i].l>r||a[i].r<l)return;
if(l<=a[i].l&&a[i].r<=r){
if(a[i].mx>qans.first){
qans.first=a[i].mx;
qans.second=a[i].pos;
}
return;
}
query(lc,l,r);query(rc,l,r);
}
ui qtmp;int ret;
void query1(int i,int l,int r)
{
if(a[i].l>r||a[i].r<l)return;
if(l<=a[i].l&&a[i].r<=r){
ret+=EF1(i,qtmp);
qtmp=max(qtmp,a[i].mx);
return;
}
query1(lc,l,r);query1(rc,l,r);
}
void query2(int i,int l,int r)
{
if(a[i].l>r||a[i].r<l)return;
if(l<=a[i].l&&a[i].r<=r){
ret+=EF2(i,qtmp);
qtmp=max(qtmp,a[i].mx);
return;
}
query2(rc,l,r);query2(lc,l,r);
}
struct qnode{
int op;ui x,y;
}q[N];
int n;ui hh[N],val[N];
int getdep(int i)
{
ret=0;qtmp=val[i];query1(1,i+1,n);
qtmp=val[i];query2(1,1,i-1);
return ret;
}
int main()
{
int Q,i,x,y;
scanf("%d",&Q);
for(i=1;i<=Q;i++){
scanf("%d",&q[i].op);
if(q[i].op==0){
scanf("%u%u",&q[i].x,&q[i].y);
hh[++n]=q[i].x;
}
else if(q[i].op==1)
scanf("%u",&q[i].x);
else
scanf("%u%u",&q[i].x,&q[i].y);
}
sort(hh+1,hh+n+1);
n=unique(hh+1,hh+n+1)-hh-1;
build(1,1,n);
for(i=1;i<=Q;i++){
if(q[i].op==0){
x=lower_bound(hh+1,hh+n+1,q[i].x)-hh;
val[x]=q[i].y;
insert(1,x,q[i].y);
}
else if(q[i].op==1){
x=lower_bound(hh+1,hh+n+1,q[i].x)-hh;
val[x]=0;
insert(1,x,0);
}
else{
x=lower_bound(hh+1,hh+n+1,q[i].x)-hh;
y=lower_bound(hh+1,hh+n+1,q[i].y)-hh;
if(x>y)swap(x,y);
qans=make_pair(0,0);query(1,x,y);
printf("%d\n",getdep(x)+getdep(y)-2*getdep(qans.second));
}
}
}
一句话题解:区间异或线段树+扫描线维护当前点所能战胜的点数(即它左边的0个数与右边的1的个数)
离线线段树的经典套路
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 100005
#define LL long long
#define lc i<<1
#define rc i<<1|1
struct node{
int l,r,len,x;
bool rev;
}a[N<<2];
void rever(int i){a[i].rev^=1;a[i].x=a[i].len-a[i].x;}
void pushdown(int i)
{
if(a[i].rev&&a[i].l<a[i].r){
rever(lc);rever(rc);
a[i].rev=0;
}
}
void build(int i,int l,int r)
{
a[i].l=l;a[i].r=r;a[i].len=r-l+1;
if(l==r)return;
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
}
void insert(int i,int l,int r)
{
if(a[i].l>r||a[i].r<l)return;
pushdown(i);
if(l<=a[i].l&&a[i].r<=r){rever(i);return;}
insert(lc,l,r);insert(rc,l,r);
a[i].x=a[lc].x+a[rc].x;
}
int query(int i,int l,int r)
{
if(a[i].l>r||a[i].r<l)return 0;
pushdown(i);
if(l<=a[i].l&&a[i].r<=r)return a[i].x;
return query(lc,l,r)+query(rc,l,r);
}
int val[N];
struct qnode{
int x,l,r,op;
bool operator < (const qnode &t)const{
return x<t.x||(x==t.x&&op>t.op);
}
}q[N*2];
int qcnt;
int main()
{
int n,Q,i,j,l,r;
n=gi();Q=gi();
for(i=1;i<=n;i++)val[i]=gi();sort(val+1,val+n+1);
for(i=1;i<=Q;i++){
l=gi();r=gi();
l=lower_bound(val+1,val+n+1,l)-val;
r=upper_bound(val+1,val+n+1,r)-val-1;
if(l>r)continue;
q[++qcnt].x=l;q[qcnt].l=l;q[qcnt].r=r;q[qcnt].op=1;
q[++qcnt].x=r;q[qcnt].l=l;q[qcnt].r=r;q[qcnt].op=-1;
}
sort(q+1,q+qcnt+1);
build(1,1,n);
LL ans=0;int sum;
for(i=j=1;i<=n;i++){
while(q[j].x==i&&q[j].op==1&&j<=qcnt){
insert(1,q[j].l,q[j].r);
j++;
}
sum=(i-1)-query(1,1,i-1)+query(1,i+1,n);
ans+=1ll*sum*(sum-1)/2;
while(q[j].x==i&&q[j].op==-1&&j<=qcnt){
insert(1,q[j].l,q[j].r);
j++;
}
}
printf("%lld\n",1ll*n*(n-1)*(n-2)/6-ans);
}
一句话题解:01差分,距离为奇质数的1优先匹配,剩下的1同奇偶的优先匹配,最后匹配异奇偶的1(最多一次)
代码:(好久没写匈牙利了,忘记判vis调了我好久)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 205
#define M 10000002
bool cf[M+5],vis[M+5];
int prime[M+5],tot;
int cy[N],a[N],b[N],cntx,cnty;
bool vs[N],c[N][N];
void shai()
{
vis[1]=1;int i,j;
for(i=1;i<=M;i++){
if(!vis[i])prime[++tot]=i;
for(j=1;j<=tot;j++){
int tmp=i*prime[j];
if(tmp>M)break;
vis[tmp]=1;
if(i%prime[j]==0)break;
}
if(cf[i]){
if(i&1)a[++cntx]=i;
else b[++cnty]=i;
}
}
}
//pay attention to vs
bool dfs(int i)
{
for(int j=1;j<=cnty;j++){
if(c[i][j]&&!vs[j]){
vs[j]=1;
if(!cy[j]||dfs(cy[j])){
cy[j]=i;
return 1;
}
}
}
return 0;
}
int maxmatch()
{
memset(cy,0,sizeof(cy));
int ret=0;
for(int i=1;i<=cntx;i++){
memset(vs,0,sizeof(vs));
if(dfs(i))ret++;
}
return ret;
}
int ab(int x){return x<0?-x:x;}
int main()
{
int n,i,j,x,ans;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&x);
cf[x]^=1;cf[x+1]^=1;
}
shai();
for(i=1;i<=cntx;i++)
for(j=1;j<=cnty;j++)
if(!vis[ab(b[j]-a[i])])c[i][j]=1;
ans=maxmatch();
cntx-=ans;cnty-=ans;
ans+=(cntx/2)*2+(cnty/2)*2;
if(cntx%2==1&&cnty%2==1)ans+=3;
printf("%d",ans);
}
Yet Another String Matching Problem
一句话题解:利用FFT优化乘法求出两种不同的颜色是否在同一位置,利用并查集求出最少合并的次数(即并查集的合并次数)
代码:(555555写了我好久,不作死就不会死,FFT千万别封装啊啊,能写NTT就别写FFT)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 500005
namespace FFT{
const double PI=3.14159265358979323846264338;
struct cp{
double r,i;
cp(){}
cp(double x,double y){r=x;i=y;}
cp operator + (const cp &t)const{return cp(r+t.r,i+t.i);}
cp operator - (const cp &t)const{return cp(r-t.r,i-t.i);}
cp operator * (const cp &t)const{return cp(r*t.r-i*t.i,r*t.i+i*t.r);}
}w,wn,X[N],Y[N];
int n,l,r[N];
void init(int k){
for(l=0,n=1;n<=k;n<<=1)l++;
for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}
void fft(cp *a,int flg){
int i,j,k;
for(i=0;i<n;i++)if(i<r[i])swap(a[i],a[r[i]]);
for(i=2;i<=n;i<<=1){
wn=cp(cos(2.0*PI/i*flg),sin(2.0*PI/i*flg));
for(j=0;j<n;j+=i){
w=cp(1,0);
for(k=j;k<j+(i>>1);k++){
cp u=a[k];
cp v=a[k+(i>>1)]*w;
a[k]=u+v;
a[k+(i>>1)]=u-v;
w=w*wn;
}
}
}
if(flg==1)return;
for(i=0;i<n;i++)a[i].r/=n;
}
}//----FFT----
int pd[N][6][6];
int fa[6];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
char s[N],t[N];
int main()
{
int n,m,i,j,k,p,q;
scanf("%s%s",s,t);
n=strlen(s);m=strlen(t);
FFT::init(n+m);
for(i=0;i<6;i++){
for(j=0;j<6;j++){
for(k=0;k<FFT::n;k++)FFT::X[k]=FFT::Y[k]=FFT::cp(0,0);
for(k=0;k<n;k++)if(i+97==s[k])FFT::X[k]=FFT::cp(1,0);
for(k=0;k<m;k++)if(j+97==t[k])FFT::Y[m-k-1]=FFT::cp(1,0);
FFT::fft(FFT::X,1);FFT::fft(FFT::Y,1);
for(k=0;k<FFT::n;k++)FFT::X[k]=FFT::X[k]*FFT::Y[k];
FFT::fft(FFT::X,-1);
for(k=0;k<FFT::n;k++)pd[k][i][j]=int(FFT::X[k].r+0.5);
}
}
for(i=m-1;i<n;i++){
for(j=0;j<6;j++)fa[j]=j;
int ans=0;
for(j=0;j<6;j++)
for(k=0;k<6;k++){
if(!pd[i][j][k])continue;
p=find(j);q=find(k);
if(p!=q)fa[q]=p,ans++;
}
printf("%d ",ans);
}
}
好题
推了我好久的式子,最后一步分拆贡献是真的神了
首先一个点到一个矩形的方案数可以不用推式子(感谢Freopen大佬的点拨)
aa3aa1
aabbba
aabbb2
xaaaaa
x想要到b矩形中,我们可以先让它到1号点
发现如果经过了2、3号点,这样的路径就可能不合法,就先减掉到2、3号点的路径
而我们多减的部分就是x到最左下角的b的路径数,把它加上即可
三个矩形的话,我们先考虑中间的矩形(2号矩形)
枚举矩形边界上的点,则这个点贡献的方案数就是它(在矩形外部的邻接点到1/3号矩形的方案数*它本身到矩形3/1的方案数)
由于这个点的贡献是与它在矩形内部的路径长度有关
而我们并不知道它到底走了多远才走出2号矩形
但是我们知道,它一定会出2号矩形
假设一条路径在2号矩形中的长度是len,它在2号矩形上的邻接点分别为x1,y1,x2,y2
则len=x2-x1+y2-y1+1
我们发现一个右上边界点的路径贡献总是x2+y2+1,而一个左下边界点的路径贡献总是-x1-y1
所以我们可以把每个点的贡献拆分开来分别计算
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 2000005
int fac[N],inv[N];
const int mod=1000000007;
void shai()
{
fac[0]=fac[1]=inv[0]=inv[1]=1;
for(int i=2;i<=2000000;i++){
fac[i]=1ll*i*fac[i-1]%mod;
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
}
for(int i=2;i<=2000000;i++)
inv[i]=1ll*inv[i]*inv[i-1]%mod;
}
int C(int x,int y){return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;}
int ab(int x){return x<0?-x:x;}
int G(int x1,int y1,int x2,int y2){return C(ab(x1-x2)+ab(y1-y2),ab(x1-x2));}
int X[7],Y[7];
int F(int x,int y,int flg)// A point to a rectangle
{
if(flg==1)
return 1ll*(1ll*G(x,y,X[2],Y[2])+1ll*mod-1ll*G(x,y,X[1]-1,Y[2])+1ll*mod-1ll*G(x,y,X[2],Y[1]-1)+1ll*G(x,y,X[1]-1,Y[1]-1))%mod;
else
return 1ll*(1ll*G(x,y,X[5],Y[5])+1ll*mod-1ll*G(x,y,X[6]+1,Y[5])+1ll*mod-1ll*G(x,y,X[5],Y[6]+1)+1ll*G(x,y,X[6]+1,Y[6]+1))%mod;
}
int main()
{
int i,ans=0,x,y;shai();
for(i=1;i<=6;i++)scanf("%d",&X[i]);
for(i=1;i<=6;i++)scanf("%d",&Y[i]);
for(x=X[3];x<=X[4];x++){
ans=(1ll*ans+1ll*F(x,Y[4],1)*F(x,Y[4]+1,3)%mod*(x+Y[4]+1)%mod)%mod;
ans=(1ll*ans-1ll*F(x,Y[3]-1,1)*F(x,Y[3],3)%mod*(x+Y[3])%mod+mod)%mod;
}
for(y=Y[3];y<=Y[4];y++){
ans=(1ll*ans+1ll*F(X[4],y,1)*F(X[4]+1,y,3)%mod*(y+X[4]+1)%mod)%mod;
ans=(1ll*ans-1ll*F(X[3]-1,y,1)*F(X[3],y,3)%mod*(y+X[3])%mod+mod)%mod;
}
printf("%d",ans);
}
一句话题解:线段树优化DP,绝对值分类讨论
代码:(注意DP的初始化)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 200005
#define LL long long
#define lc i<<1
#define rc i<<1|1
const LL INF=0x3f3f3f3f3f3f3f3fll;
int pos[N],A,B,n,Q;
struct node{
int l,r;
LL mi1,mi2,la;
}a[N<<2];
void cal(int i,LL k){a[i].mi1+=k;a[i].mi2+=k;a[i].la+=k;}
void pushdown(int i)
{
if(a[i].la&&a[i].l<a[i].r){
cal(lc,a[i].la);cal(rc,a[i].la);
a[i].la=0;
}
}
void pushup(int i)
{
a[i].mi1=min(a[lc].mi1,a[rc].mi1);
a[i].mi2=min(a[lc].mi2,a[rc].mi2);
}
void build(int i,int l,int r)
{
a[i].l=l;a[i].r=r;
if(l==r){
a[i].mi1=INF;a[i].mi2=INF;
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(i);
}
void insert(int i,int l,int r,LL k)
{
if(a[i].l>r||a[i].r<l)return;
pushdown(i);
if(l<=a[i].l&&a[i].r<=r){cal(i,k);return;}
insert(lc,l,r,k);insert(rc,l,r,k);
pushup(i);
}
void modify(int i,int x,LL k)
{
if(a[i].l>x||a[i].r<x)return;
pushdown(i);
if(x==a[i].l&&a[i].r==x){a[i].mi1=min(a[i].mi1,k-x);a[i].mi2=min(a[i].mi2,k+x);return;}
modify(lc,x,k);modify(rc,x,k);
pushup(i);
}
LL query1(int i,int l,int r)
{
if(a[i].l>r||a[i].r<l)return INF;
pushdown(i);
if(l<=a[i].l&&a[i].r<=r)return a[i].mi1;
return min(query1(lc,l,r),query1(rc,l,r));
}
LL query2(int i,int l,int r)
{
if(a[i].l>r||a[i].r<l)return INF;
pushdown(i);
if(l<=a[i].l&&a[i].r<=r)return a[i].mi2;
return min(query2(lc,l,r),query2(rc,l,r));
}
LL query(int i)
{
if(a[i].l==a[i].r)return a[i].mi1+a[i].l;
pushdown(i);
return min(query(lc),query(rc));
}
int ab(int x){return x<0?-x:x;}
int main()
{
int i;LL f;
n=gi();Q=gi();A=gi();B=gi();
build(1,1,n);
pos[0]=A;modify(1,B,0);
for(i=1;i<=Q;i++){
pos[i]=gi();
f=min(query1(1,1,pos[i])+pos[i],query2(1,pos[i]+1,n)-pos[i]);
insert(1,1,n,ab(pos[i]-pos[i-1]));
modify(1,pos[i-1],f);
}
printf("%lld\n",query(1));
}
好题
让我重新学了一遍SAM(啊啊啊我到底学了多少遍SAM了)
考虑正难则反,用整个字符串中本质不同的字串个数-被切开之后损失的本质不同的子串个数 来计算答案
我们按照right集合来计算每一组子串的贡献
(配上一张极其丑陋的图)
其中L表示该种子串最小的endpos值,R表示最大的endpos值
mx表示该right集合中最长的子串长度,mi表示最短长度
发现在mx到mi之间我们会增加一个等差数列,而到了mi到L之间该种right集合里的字符串已经没有了,所以就是一个常数数列
我们只需要维护一下二阶差分与一阶差分再做一个前缀和即可
代码:(WA了一次是因为读错题了把\sum看成了\prod)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 200005
#define LL long long
const int mod=1000000007;
int ch[N<<1][10],fa[N<<1],len[N<<1],L[N<<1],R[N<<1],id[N<<1],tot,last;
LL f[N],g[N];
void extend(int x,int pos)
{
int p,np,q,nq;
p=last;np=++tot;
len[np]=len[p]+1;L[np]=R[np]=pos;
for(;p!=-1&&!ch[p][x];p=fa[p])
ch[p][x]=np;
if(p==-1)fa[np]=0;
else{
q=ch[p][x];
if(len[q]==len[p]+1)fa[np]=q;
else{
nq=++tot;L[nq]=R[nq]=pos;
len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];
for(;p!=-1&&ch[p][x]==q;p=fa[p])ch[p][x]=nq;
fa[q]=fa[np]=nq;
}
}
last=np;
}
bool cmp(int x,int y){return len[x]<len[y];}
char s[N];
int main()
{
int T,n,i;LL sum;
scanf("%d",&T);
while(T--){
memset(ch,0,sizeof(ch));
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
tot=last=0;fa[0]=-1;
sum=0;
scanf("%d%s",&n,s+1);
for(i=1;i<=n;i++)extend(s[i]-'0',i);
for(i=1;i<=tot;i++){
sum=sum+1ll*(len[i]-len[fa[i]]);
id[i]=i;
}
sort(id+1,id+tot+1,cmp);
for(i=tot;i>=1;i--){
int u=id[i];
L[fa[u]]=min(L[fa[u]],L[u]);
R[fa[u]]=max(R[fa[u]],R[u]);
}
for(i=1;i<=tot;i++){
if(R[i]-len[i]+1<=L[i]-1){
int k=min(L[i]-1,R[i]-len[fa[i]]);
g[R[i]-len[i]+1]++;
g[k+1]--;
f[L[i]]-=k-(R[i]-len[i]);
}
}
for(i=1;i<=n;i++){
g[i]=(g[i]+g[i-1])%mod;
f[i]=(f[i]+f[i-1]+g[i])%mod;
}
int ans=0;
for(i=1;i<n;i++)
ans=(100013ll*ans+(sum-f[i]))%mod;
printf("%d\n",ans);
}
}