(只挑选了几道可以启发思维的题)

(时间紧迫,写得可能比较简略)

Count on a Treap

一句话题解:李超线段树模拟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));
		}
	}
}

 

Cow Tennis Tournament

一句话题解:区间异或线段树+扫描线维护当前点所能战胜的点数(即它左边的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);
}

 

Prime Flip

一句话题解: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);
	}
}

 

 

Sightseeing Plan

好题

推了我好久的式子,最后一步分拆贡献是真的神了

首先一个点到一个矩形的方案数可以不用推式子(感谢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);
}

 

 

Many Moves

一句话题解:线段树优化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));
}

 

 

CA Loves Substring

好题

让我重新学了一遍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);
	}
}