训练补题集1.0

2019 NWERC(最短路+凸包、线段树二分、智慧题)

D. Disposable Switches

显然最终的答案只与经过的边数和经过的最短路径长度和有关

\(dis[x][k]\)表示到\(x\)点经过\(k\)条边的最短路(因为记录了边数,这个东西可以直接\(n^2\)预处理

完全不可能的点不容易求,考虑求哪些点有可能在\(1\rightarrow n\)的最短路上

考虑所有\(dis[n][x]\),对应的答案即为\(res=c\cdot x+\frac{dis[n][x]}{v}\)

相当于求对所有\(<c,v>\)的可能取值,有哪些\(x\)对应的\(res\)有可能对应最小值

不妨固定\(v\),此时令\(y=\frac{dis[n][x]}{v}\),变形得:\(y=-c\cdot x+res\),是一个直线的式子

只需要考虑对所有\(c\ge 0\),哪些点\((x,y)\)可能对应\(res_{min}\)

显然只需要求一个下凸壳,同时由于\(c\ge 0\)只需要这个下凸壳中斜率小于0的部分

求出所有可能的\((x,y)\)后,所有能转移出\((x,dis[n][x])\)的点都是有用的

倒着\(dfs\)一遍并打标记即可

#include<bits/stdc++.h>
#define ll long long
#define db double
#define MAXN 100100
#define pii pair<int,int>
#define fi first
#define se second
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pb push_back
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0',ch=getchar();}
	return x*f;
}
ll inf=1e18;
int n,m,nxt[20010],fst[2020],to[20010],cnt,val[20010];
ll dis[2020][2020];
int st[2020],tp,ok[2020];
pair<int,ll> g[2020];
void add(int u,int v,int w){nxt[++cnt]=fst[u],fst[u]=cnt,to[cnt]=v,val[cnt]=w;}
void getdis()
{
	rep(i,1,n) rep(j,0,n) dis[i][j]=inf;dis[1][0]=0;
	rep(k,0,n) rep(x,1,n-1) if(dis[x][k]!=inf)
		ren if(dis[to[i]][k+1]>dis[x][k]+val[i])
			dis[to[i]][k+1]=dis[x][k]+val[i];
}
inline ll Y(int a,int b){return g[b].se-g[a].se;}
inline ll X(int a,int b){return g[b].fi-g[a].fi;}
int tag[2020],ans;
void dfs(int x,int num)
{
	if(tag[x]) return ;tag[x]=1;
	ok[x]=1;if(!num) return ;
	ren if(dis[to[i]][num-1]+val[i]==dis[x][num]) dfs(to[i],num-1);
}
int main()
{
	n=read(),m=read();int x,a,b,c;
	rep(i,1,m) a=read(),b=read(),c=read(),add(a,b,c),add(b,a,c);
	getdis();m=0;
	rep(i,1,n) if(dis[n][i]!=inf) g[++m]={i,dis[n][i]};
	tp=0;rep(i,1,m)
	{
		while(tp>=2&&Y(st[tp],i)*X(st[tp-1],st[tp])<Y(st[tp-1],st[tp])*X(st[tp],i))
			tp--;
		st[++tp]=i;
	}
	while(tp>=2&&Y(st[tp-1],st[tp])>0) tp--;
	rep(i,1,tp) 
	{
		rep(j,1,n) tag[j]=0;
		dfs(n,g[st[i]].first);
	}
	rep(i,1,n) if(!ok[i]) ans++;
	printf("%d\n",ans);
	rep(i,1,n) if(!ok[i]) printf("%d ",i);
}

H. Height Profile

最终答案区间显然至少有一个端点是整点,否则可以通过向斜率更大的区间平移得到更大答案

则枚举左端点的整点\(l\),找到右侧满足条件的最远整点\(r\),即\(h_r-h_l\ge(r-l)g\)

移项得:\(h_r-r\cdot g\ge h_l-l\cdot g\),令\(w_i=h_i-i\cdot g\),在线段树上二分容易得到\(r\)

对于这个\([l,r]\)分别计算向左延伸和向右延伸的答案即可

#include<bits/stdc++.h>
#define ll long long
#define db double
#define inf 2139062143
#define MAXN 100100
#define pii pair<int,int>
#define fi first
#define se second
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define pb push_back
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0',ch=getchar();}
	return x*f;
}
int n,m,res,h[MAXN],g,mx[MAXN<<2],w[MAXN];
db ans;
void build(int k,int l,int r)
{
	if(l==r) {w[l]=mx[k]=h[l]-g*l;return ;}int mid=l+r>>1;
	build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	mx[k]=max(mx[k<<1],mx[k<<1|1]);
}
void find(int k,int l,int r,int a,int b,db w)
{
	if(a<=l&&r<=b)
	{
		if(mx[k]<w) return ;
		if(l==r) {res=max(l,res);return ;}
		int mid=l+r>>1;
		if(mx[k<<1|1]>=w) find(k<<1|1,mid+1,r,a,b,w);
		else find(k<<1,l,mid,a,b,w);
		return ;
	}
	int mid=l+r>>1;
	if(a<=mid) find(k<<1,l,mid,a,b,w);
	if(b>mid) find(k<<1|1,mid+1,r,a,b,w);
}
int main()
{
	n=read(),m=read();rep(i,0,n) h[i]=read();
	int l,r;db tmp;
	while(m--)
	{
		scanf("%lf",&tmp);g=floor(tmp*10+0.1);
		build(1,0,n);ans=-1;
		rep(i,0,n-1)
		{
			res=0;find(1,0,n,i+1,n,w[i]);
			if(!res) continue;
			l=i,r=res;ans=max(ans,(db)res-i);
			if(l&&w[l-1]>w[r])
			{
				tmp=(1.0*w[r]-w[l])/(1.0*w[l-1]-w[l]);
				ans=max(ans,res-i+tmp);
			}
			if(r<n)
			{
				tmp=(1.0*w[r]-w[l])/(1.0*w[r]-w[r+1]);
				ans=max(ans,res-i+tmp);
			}
		}
		printf("%.8lf\n",ans);
	}
}

J. Jackdaws And Crows

枚举\(fake\ accounts\)的个数\(x\),则所有绝对值小于\(x\)的位置的取值可正可负,视为是自由点

显然只有当\(x\)为原序列中某数绝对值\(+1\)时,会影响自由点

将原序列按照绝对值排序,枚举时可以用链表快速维护剩余的非自由点

问题转化为如何快速求在当前这个状态需要删除多少个数使满足条件

  • 显然删除非自由点比删除自由点更优
  • 最后需要删除的个数即为在链表中相邻的非自由点中的矛盾对数,可以简单证明:令非自由点序列为\(a_i\),当\(<a_i,a_{i+1}>\)矛盾时,显然\(<a_i,a_{k}>\)\(<a_{i+1},a_k>\)一定只有一个矛盾,其中\(k> i+1\)。当选择删除\(a_{i+1}\)时,\(<a_i,a_k>\)的矛盾变化即变化后与原先的\(<a_{i+1},a_k>\)相同。即:每次删除一个非自由点之后只能使相邻的矛盾对数减一,因此最终答案为链表中相邻的非自由点中的矛盾对数。

这个答案在初始\(O(n)\)统计一次答案之后,每次删除一个数的时候可以\(O(1)\)快速维护

(初始存在0的时候,需要考虑将0全部删掉的情况

#include<bits/stdc++.h>
#define ll long long
#define db double
#define MAXN 500100
#define pii pair<int,int>
#define fi first
#define se second
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pb push_back
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0',ch=getchar();}
	return x*f;
}
int n,res,las,l[MAXN],r[MAXN];
ll p,q,a[MAXN],ans;
pii g[MAXN];
inline int calc(int x,int y)
{
	if(!(x*y)) return 0;
	return (a[x]*a[y]>0)^((y-x+1)&1);
}
int main()
{
	n=read(),p=read(),q=read();rep(i,1,n) a[i]=read(),g[i]={abs(a[i]),i};
	rep(i,1,n)
	{
		if(a[i]==0) {res++;continue;}
		if(las&&a[las]*a[i]>0) res++;
		las=i;
	}
	ans=res*q;sort(g+1,g+n+1);
	las=res=0;int x;
	rep(i,1,n) if(a[i])
		{if(las) r[las]=i,res+=calc(las,i);l[i]=las,las=i;}
	ans=min(ans,q*res+p);
	rep(i,1,n) if(g[i].fi)
	{
		x=g[i].se;res-=calc(l[x],x)+calc(x,r[x])-calc(l[x],r[x]);
		if(r[x]) l[r[x]]=l[x];if(l[x]) r[l[x]]=r[x];
		ans=min(ans,p*(g[i].fi+1)+q*res);
	}
	printf("%lld\n",ans);
}

icpc 2020 Shenyang(图上模拟)

C. Mean Streets of Gadgetzan

记录有哪些点的值已经确定,同时由于推出的条件一定是成立,记录每个点在哪些推出语句中

若该点被确定成立后使整个推出语句成立,则将被推出的点也被确定

\(bfs\)模拟一下过程,中间出现矛盾直接退出

最终答案中没有被赋值的点可以赋为0不影响答案

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 1001001
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
int n,m,num[MAXN],to[MAXN],cur[MAXN],len,st[MAXN],tp,q[MAXN],hd=1,tl;
vector<int> in[MAXN];
char s[MAXN*6];
void mdf(int x,int w)
{
	if(cur[x]>=0&&cur[x]+w==1) {puts("conflict");exit(0);}
	if(w>0&&cur[x]<0) q[++tl]=x;cur[x]=w;
}
int main()
{
	m=read(),n=read();int flg=1,x=0;Fill(cur,0xff);
	rep(i,1,m)
	{
		gets(s+1);len=strlen(s+1);s[++len]='\n';
		tp=x=0;flg=1;
		rep(j,1,len) 
		{
			if(isdigit(s[j])) x=x*10+s[j]-'0';
			else if(s[j]=='!') flg=-1,x=0;
			else if(isdigit(s[j-1])) st[++tp]=x*flg,flg=1,x=0;
		}
		if(tp==1) {mdf(abs(st[1]),st[1]>0);continue;}
		num[i]=tp-1;
		rep(j,1,tp-1) in[st[j]].pb(i);
		to[i]=st[tp];
	}
	while(hd<=tl)
	{
		x=q[hd++];
		for(auto v:in[x]) if(!(--num[v]))
			mdf(abs(to[v]),to[v]>0);
	}
	rep(i,1,n) if(cur[i]==1) putchar('T');else putchar('F');
}

2021 校赛预选(妙妙分块+预处理、长链剖分+基环树)

D. Hard Nim

本题需要求出每个数是必胜还是必败

首先二进制有奇数个\(1\)的数是必胜态,即上一个人留下了奇数个\(1\)

只需关心二进制有偶数个\(1\)的数,考虑如何转移

\(x\)为必败,\([x+1,x+m]\)内的数显然均为必胜,则第一个\(>x+m\)且二进制有偶数个\(1\)的数为必败

如此即得到了一个\(O(\frac{n}{m})\)的做法

考虑分块,每\(K=2^{20}\)个数为一个块,这样的块只有两种,即位数\(20+\)\(1\)的个数的奇偶性

若能\(O(1)\)地从一个块转移到下一个块,复杂度降为\(O(\frac{n}{K}+K)\)

这一部分可以预处理得到,每个块内只需预处理前\(m+2\)个数

枚举该块与下一块的奇偶性,暴力向后跳即可得到,该部分复杂度为\(O(m\times \frac{K}{m})=O(K)\)

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define vi vector<int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
const int tot=1<<20,MAXN=tot+100;
int m,nxt[4][MAXN];
ll n;
inline int calc(ll x,int res=0){rep(i,0,19) if((x>>i)&1) res^=1;return res;}
int main()
{
	n=read(),m=read();int x,y;ll p,tmp=0;
	rep(i,0,3) rep(j,0,m+3) 
	{
		p=j;x=i>>1,y=i&1;
		if(x^calc(j)) continue;
		while(p<tot)
		{
			p+=m+1;
			while((p<tot?x:y)^calc(p)) ++p;
		}
		nxt[i][j]=p-tot;
	}
	for(ll i=p=0;i+tot<=n;i+=tot,tmp=i)
		p=nxt[(calc(i>>20LL)<<1)|calc(i+tot>>20LL)][p];
	n-=tmp;tmp=calc(tmp>>20LL);
	while(p<n)
	{
		p+=m+1;
		while(tmp^calc(p)) ++p;
	}
	puts(p==n?"zyw":"lyw");
}

I. Broken routers

首先将所有询问离线下来按时间排序

当询问点在基环树上的树部分时,需要每个点子树内深度为\(x\)的点权和,此部分可以用长链剖分维护

使用长链剖分需要将询问挂到点上,在\(dfs\)过程中统计答案

而对于环来说普通的循环容易维护,只需要考虑每个环外点的进入环的影响

根据该点的深度计算出进入环的位置,动态维护一个环上相对顺序的权值数组

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 100100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
int n,m,to[MAXN],tag[MAXN],vis[MAXN],tot;
int id[MAXN],mxd[MAXN],frm[MAXN],lgs[MAXN],dep[MAXN],bl[MAXN],w[MAXN],p[MAXN];
ll ans[MAXN],*f[MAXN],tmp[MAXN<<2],*cur=tmp;
vector<int> G[MAXN],cir[MAXN];
vector<ll> now[MAXN];
vector<pii> vq[MAXN];
struct Ask{int t,x,id;}q[MAXN];
bool operator < (const Ask &x,const Ask &y){return x.t<y.t;}
bool cmp(int x,int y){return dep[x]<dep[y];}
void find(int x,int anc)
{
	if(vis[x]==anc)
	{
		cir[++tot].pb(x),now[tot].pb(w[x]);frm[x]=tot,tag[x]=1,id[x]=0;
		for(int tmp=x;to[tmp]!=x;tmp=to[tmp])
			id[to[tmp]]=id[tmp]+1,cir[tot].pb(to[tmp]),
			tag[to[tmp]]=1,frm[to[tmp]]=tot,now[tot].pb(w[to[tmp]]);
		return ;
	}
	else if(vis[x]) return ;
	vis[x]=anc;find(to[x],anc);
}
void dfs(int x,int anc)
{
	for(auto v:G[x])
	{
		dep[v]=dep[x]+1;dfs(v,anc);
		if(mxd[v]>mxd[lgs[x]]) lgs[x]=v;
	}
	bl[x]=anc,mxd[x]=mxd[lgs[x]]+1;
}
inline void New(int x){f[x]=cur,cur+=mxd[x]<<1;}
void getf(int x)
{
	if(lgs[x]){f[lgs[x]]=f[x]+1;getf(lgs[x]);}
	f[x][0]=w[x];
	for(auto v:G[x]) if(v^lgs[x])
		{New(v);getf(v);rep(i,0,mxd[v]) f[x][i+1]+=f[v][i];}
	for(auto t:vq[x])
		ans[t.se]=t.fi>mxd[x]?0:f[x][t.fi];
}
void mdf(int x)
{
	if(!dep[x]) return ;int y=bl[x],len=cir[frm[y]].size();
	now[frm[y]][((id[y]-dep[x])%len+len)%len]+=w[x];
}
int main()
{
	n=read();rep(i,1,n) w[i]=read();rep(i,1,n) to[i]=read();
	m=read();rep(i,1,m) q[i].x=read(),q[i].t=read(),q[i].id=i;
	sort(q+1,q+m+1);
	rep(i,1,n) if(!vis[i]) find(i,i);
	rep(i,1,m) if(!tag[q[i].x]) vq[q[i].x].pb({q[i].t,q[i].id});
	rep(i,1,n) if(!tag[i]) G[to[i]].pb(i);
	rep(i,1,n) if(tag[i]) dfs(i,i),New(i),getf(i);
	rep(i,1,n) p[i]=i;int pos=1,x,len;
	sort(p+1,p+n+1,cmp);
	rep(i,1,m) if(tag[q[i].x])
	{
		while(dep[p[pos]]<=q[i].t&&pos<=n) mdf(p[pos]),pos++;
		x=q[i].x,len=cir[frm[x]].size();
		ans[q[i].id]=now[frm[x]][((id[x]-q[i].t)%len+len)%len];
	}
	rep(i,1,m) printf("%lld\n",ans[i]);
}

icpc 2020 Macau(分治+ntt、最小异或生成树、set+线段树二分)

A. Accelerator

考虑一个长度为\(i\)的段,容易得到这一段的贡献为:\(i!(n-i)!\prod b_j\)

\(F_i=\sum\limits_{j\subsetneq S,|j|=i} \prod_{x\in j}a_x\),则答案为\(\frac{1}{n!}\sum\limits_{i=1}^ni!(n-i)!F_i\)

根据\(F_i\)的意义,考虑生成函数,\(F_i\)即为\([x^i]\left[(1+a_1x)(1+a_2x)\cdots (1+a_nx)\right]\)

分治+\(ntt\)即可

(本题并不困难,mark一下卡常技巧:当卷积的两个多项式其中一个长度较小时,直接相乘优于卷积

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 200100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define vi vector<int>
#define fi first
#define se second
#define pb push_back
#define Clear(x) {x.clear();vector<int> (x).swap(x);}
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:a+b;}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:a-b;}
	inline int mul(int a,int b){return 1LL*a*b%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
int n,ans,g[MAXN],pw[30],ipw[30];
int fac[MAXN],ifac[MAXN],q[MAXN<<2];
vector<int> ret;
namespace Poly
{
	int rev[MAXN<<2];
	vi res;
	int mem(int n)
	{
		int lg=1,lim=1;
		for(;lim<n;lim<<=1,lg++);
		rep(i,0,lim-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-2));
		return lim;
	}
	void ntt(int *a,int n,int f)
	{
	    rep(i,0,n-1) if(i<rev[i]) swap(a[i],a[rev[i]]);
	    for(int i=1,t=1;i<n;i<<=1,++t)
	    {
	        int wn= f>0?pw[t]:ipw[t];for(int j=0;j<n;j+=i<<1)
	        {
	            int w=1,x,y;for(int k=0;k<i;++k,w=mul(w,wn))
	                x=a[j+k],y=mul(a[j+k+i],w),a[j+k]=pls(x,y),a[j+k+i]=mns(x,y);
	        }
	    }
	    if(f>0) return ;int nv=Inv(n);rep(i,0,n-1) a[i]=mul(a[i],nv);
	}
	vi Mul(const vi &a,const vi &b)
	{
		static int A[MAXN<<2],B[MAXN<<2];
		int n=a.size(),m=b.size(),s=mem(n+m-1);
		if(min(n,m)<128)
		{
			res.assign(n+m-1,0);
			rep(i,0,n-1) rep(j,0,m-1) inc(res[i+j],mul(a[i],b[j]));
			return res;
		}
		rep(i,0,n-1) A[i]=a[i];rep(i,n,s-1) A[i]=0;
		rep(i,0,m-1) B[i]=b[i];rep(i,m,s-1) B[i]=0;
		ntt(A,s,1);ntt(B,s,1);
		rep(i,0,s-1) tms(A[i],B[i]);
		ntt(A,s,-1);
		res.resize(n+m);rep(i,0,n+m-1) res[i]=A[i];
        return res;
	}
	vi solve(int l,int r)
	{
		if(l==r) return {1,g[l]};
		int mid=l+r>>1;
		return Mul(solve(l,mid),solve(mid+1,r));
	}
}
void init(int n)
{
	fac[0]=ifac[0]=1;rep(i,1,n) fac[i]=mul(fac[i-1],i);
	ifac[n]=Inv(fac[n]);dwn(i,n-1,1) ifac[i]=mul(ifac[i+1],i+1);
	rep(i,1,25) pw[i]=qp(3,(MOD-1)/(1<<i)),ipw[i]=Inv(pw[i]);
}
int main()
{
	init(1e5);rep(T,1,read())
	{
		n=read(),ans=0;rep(i,1,n) g[i]=read();
		ret=Poly::solve(1,n);
		rep(i,1,n) inc(ans,mul(ret[i],mul(fac[i],fac[n-i])));
		printf("%d\n",mul(ans,ifac[n]));
	}
}

C. Club Assignment

将所有边升序排列,加入每条边,将该边两个点分在两个集合内。若有一边两点已经在同一集合内,则该边权值即为答案

实际上显然不可能将所有边都存下来

注意到上述过程上与生成树过程无异,只需要求出生成树之后再对两种颜色内部分别计算一下贡献

现在只需要求出一个异或最小生成树

实际上是利用\(boruvka\)这一算法,由高至低每一位对\(0,1\)两个子树间找一条尽可能小的边

可以访问某一子树中所有点在另一子树内查询,容易实现

总复杂度\(O(n\log^2w)\)

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 100100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
int n,tr[MAXN*15][2],tot,l[MAXN*15],r[MAXN*15],col[MAXN],ans;
vector<int> G[MAXN];
pii a[MAXN];
void add(int u,int v){G[u].pb(v);G[v].pb(u);}
void ins(int x,int id)
{
	int pos=0,t;dwn(i,29,0) 
	{
		t=(x>>i)&1;if(!tr[pos][t]) tr[pos][t]=++tot;
		pos=tr[pos][t];
		if(!l[pos]) l[pos]=id;r[pos]=id;
	}
}
int query(int x,int rt,int dep)
{
	if(dep<0||l[rt]==r[rt]) return l[rt];
	int t=(x>>dep)&1;
	return tr[rt][t]?query(x,tr[rt][t],dep-1):query(x,tr[rt][t^1],dep-1);
}
int queryw(int x,int res=0)
{
	if(!tr[0][0]&&!tr[0][1]) return inf;
	int pos=0,t;dwn(i,29,0) 
	{
		t=(x>>i)&1;
		if(tr[pos][t]) pos=tr[pos][t];
		else pos=tr[pos][t^1],res|=1<<i;
	}
	return res;
}
void solve(int rt,int dep)
{
	if(dep<0||l[rt]==r[rt]||l[rt]==r[rt]+1)
		{rep(i,l[rt],r[rt]-1) add(a[i].se,a[i+1].se);return ;}
	if(tr[rt][0]&&tr[rt][1])
	{
		int res=inf,tmp;pii g;
		rep(i,l[tr[rt][0]],r[tr[rt][0]])
		{
			tmp=query(a[i].fi,tr[rt][1],dep-1);
			if((a[i].fi^a[tmp].fi)<res) res=a[i].fi^a[tmp].fi,g={i,tmp};
		}
		add(a[g.fi].se,a[g.se].se);
		solve(tr[rt][0],dep-1);solve(tr[rt][1],dep-1);
	}
	else if(tr[rt][0]) solve(tr[rt][0],dep-1);
	else solve(tr[rt][1],dep-1);
}
void dfs(int x,int pa){col[x]=col[pa]^1;for(auto v:G[x]) if(v^pa) dfs(v,x);}
int main()
{
	rep(T,1,read())
	{
		n=read(),ans=inf;rep(i,1,n) a[i].fi=read(),a[i].se=i;
		sort(a+1,a+n+1);rep(i,1,n) ins(a[i].fi,i);
		l[0]=1,r[0]=n;solve(0,29);dfs(1,0);
		rep(i,1,n) G[i].clear();
		for(;~tot;tot--) l[tot]=tr[tot][0]=tr[tot][1]=0;tot=0;
		rep(i,1,n) if(!col[a[i].se]) {ans=min(ans,queryw(a[i].fi));ins(a[i].fi,0);}
		for(;~tot;tot--) tr[tot][0]=tr[tot][1]=0;tot=0;
		rep(i,1,n) if(col[a[i].se]) {ans=min(ans,queryw(a[i].fi));ins(a[i].fi,0);}
		for(;~tot;tot--) tr[tot][0]=tr[tot][1]=0;tot=0;
		printf("%d\n",ans);
		rep(i,1,n) putchar(col[i]?'2':'1');puts("");
	}
}

J. Jewel Grab

由于查询时的\(k\)很小,因为若能快速查找到区间内具有相同颜色的块,则可以暴力向后跳计算贡献

考虑对于每个元素\(a_i\)维护\(pre_i\),表示上一个与其颜色相同的元素的下标

每次查找即为在\([l,r]\)内查询第一个\(pre_i>x\)\(i\),用线段树维护\(pre\)然后线段树二分即可

对于修改,可以对每个颜色维护一个\(set\),通过\(upper\_bound\)函数快速找到后继和前驱进行修改

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 200100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
int n,m,col[MAXN],w[MAXN],pre[MAXN],pos[MAXN];
int mx[MAXN<<2],mxc[MAXN];
ll c[MAXN],ans;
inline void mdf(int x,int w){for(;x<=n;x+=x&-x) c[x]+=w;}
inline ll query(int x,ll res=0){for(;x;x-=x&-x) res+=c[x];return res;}
inline ll getsum(int l,int r){return query(r)-query(l-1);}
set<int> s[MAXN];
vector<int> del;
void build(int k,int l,int r)
{
	if(l==r) {mx[k]=pre[l];return ;}int mid=l+r>>1;
	build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	mx[k]=max(mx[k<<1],mx[k<<1|1]);
}
void upd(int k,int l,int r,int x)
{
	if(l==r) {mx[k]=pre[x];return ;}int mid=l+r>>1;
	x<=mid?upd(k<<1,l,mid,x):upd(k<<1|1,mid+1,r,x);
	mx[k]=max(mx[k<<1],mx[k<<1|1]);
}
int xres;
void find(int k,int l,int r,int a,int b,int w)
{
	if(a==n+1) return ;
	if(a<=l&&r<=b)
	{
		if(mx[k]<w) return ;
		if(l==r) {xres=min(l,xres);return ;}
		int mid=l+r>>1;
		if(mx[k<<1]>=w) find(k<<1,l,mid,a,b,w);
		else find(k<<1|1,mid+1,r,a,b,w);
		return ;
	}
	int mid=l+r>>1;
	if(a<=mid) find(k<<1,l,mid,a,b,w);
	if(b>mid) find(k<<1|1,mid+1,r,a,b,w);
}
int main()
{
	n=read(),m=read();int c,a,b,l,r;
	set<int>::iterator it,it2;
	rep(i,1,n) 
		col[i]=read(),w[i]=read(),mdf(i,w[i]),pre[i]=pos[col[i]],pos[col[i]]=i,s[col[i]].insert(i);
	build(1,1,n);
	rep(i,1,m)
	{
		c=read(),a=read(),b=read();
		if(c==2)
		{
			l=a;xres=n+1;find(1,1,n,a+1,n,l);r=xres;
			ans=getsum(l,r-1);
			while(r!=n+1&&b--)
			{
				xres=n+1;find(1,1,n,r+1,n,l);
				if(!mxc[col[r]]) mxc[col[r]]=w[pre[r]],del.pb(col[r]);
				ans-=mxc[col[r]];
				mxc[col[r]]=max(mxc[col[r]],w[r]);
				ans+=mxc[col[r]];
				ans+=getsum(r+1,xres-1);
				r=xres;
			}
			for(auto v:del) mxc[v]=0;del.clear();
			printf("%lld\n",ans);
			continue;
		}
		c=read();swap(b,c);
		it=s[col[a]].upper_bound(a);
		if(it!=s[col[a]].end())
		{
			it2=it,it2--;
			if(it2!=s[col[a]].begin()) it2--,pre[*it]=*it2;
			else pre[*it]=0;
			upd(1,1,n,*it);
		}
		s[col[a]].erase(a);col[a]=c;
		mdf(a,-w[a]);w[a]=b;mdf(a,w[a]);
		s[c].insert(a);
		it=s[c].upper_bound(a);
		if(it!=s[c].end()) {pre[*it]=a;upd(1,1,n,*it);}
		it--;
		if(it!=s[c].begin()) it--,pre[a]=*it;
		else pre[a]=0;
		upd(1,1,n,a);
	}
}

ccpc 2020 Changchun(dsu on tree、状压dp)

F. Strange Memory

\(dsu\ on \ tree\)板题,每次记录所有权值为\(x\)的点的编号的二进制信息

记录之后合并即可

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 100100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define vi vector<int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
int n,m,fst[MAXN],nxt[MAXN<<1],to[MAXN<<1],w[MAXN],cnt;
int sz[MAXN],hvs[MAXN],num[MAXN*10][18],sum,now,cur;
ll ans;
void add(int u,int v){nxt[++cnt]=fst[u],fst[u]=cnt,to[cnt]=v;}
void dfs(int x,int pa)
{
	sz[x]=1;
	ren if(to[i]^pa) 
	{
		dfs(to[i],x);sz[x]+=sz[to[i]];
		if(sz[to[i]]>sz[hvs[x]]) hvs[x]=to[i];
	}
}
vector<pii> tmp;
inline void mdf(int x,int id,int w)
{
	rep(i,0,16) if((id>>i)&1) num[x][i]+=w;
	num[x][17]+=w;
}
void Get(int x,int pa)
{
	tmp.pb({x,w[x]});ren if(to[i]^pa) Get(to[i],x);
}
void dsu(int x,int pa,int v)
{
	ren if(to[i]^pa&&to[i]^hvs[x]) dsu(to[i],x,0);
	if(hvs[x]) dsu(hvs[x],x,1);
	ren if(to[i]^pa&&to[i]^hvs[x])
	{
		tmp.clear();Get(to[i],x);
		for(auto t:tmp)
		{
			now=w[x]^t.se;if(now>1000000) continue;
			sum=num[now][17];
			rep(i,0,16)
				cur=(t.fi>>i)&1,
				ans+=(1LL<<i)*((ll)(cur?sum-num[now][i]:num[now][i]));
		}
		for(auto t:tmp) mdf(t.se,t.fi,1);
	}
	mdf(w[x],x,1);
	if(!v) 
	{
		tmp.clear();Get(x,pa);
		for(auto t:tmp) mdf(t.se,t.fi,-1);
	}
}
int main()
{
	n=read();rep(i,1,n) w[i]=read();int a,b;
	rep(i,2,n) a=read(),b=read(),add(a,b),add(b,a);
	dfs(1,0);dsu(1,0,1);
	printf("%lld\n",ans);
}

J. Abstract Painting

考虑顺序\(dp\),每次以\(i\)为右端点,有可能画出\(5\)种圆共\(32\)种方案

而是否能画出圆仅取决于该圆的左右端点是否已经被别的圆包含(非相切)

利用状态压缩记录状态,\(dp[i][State]\)表示以\(i\)为右端点,\(i-9,\dots ,i-1\)这9个点是否已经被包含的方案数

转移的时候先枚举\(i-1\)\(State\),再枚举画圆的\(32\)种方案(枚举画圆方案时该方案必须包含已给的圆

\(check\)出合法方案之后更新\(i\)\(State\)

最终$ ans=\sum\limits_{State}F[n][State]$

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 100100
#define MOD 1000000007
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define vi vector<int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
int n,m,ans;
int vis[1050][5],f[1010][550];
int main()
{
	n=read(),m=read();int x,r;
	rep(i,1,m) {x=read(),r=read();vis[x+r][r-1]=1;}
	f[0][0]=1;
	rep(i,1,n) rep(j,0,511) rep(k,0,31)
	{
		x=1;rep(b,0,4) if(!((k>>b)&1)&&vis[i][b]) {x=0;break;}
		if(!x) continue;
		rep(b,0,4) if(((k>>b)&1)&&((j>>(8-2*b))&1)) {x=0;break;}
		if(!x) continue;
		dwn(b,4,0) if(((k>>b)&1)&&i<b*2+2) {x=0;break;}
		if(!x) continue;
		r=j>>1;dwn(b,4,0) if((k>>b)&1)
			{rep(o,8-2*b,8) r|=(1<<o);break;}
		inc(f[i][r],f[i-1][j]);
	}
	rep(i,0,511) inc(ans,f[n][i]);
	printf("%d\n",ans);
}

ccpc 2021 Guilin(线段树二分、后缀树+线段树)

B. A Plus B Problem

用线段树维护被进位过但没有\(mod\ 10\)\(C\)

朴素的修改是单点修改,较为麻烦的情况是新增进位/不进位

进位相当于从位置\(x\)一直进到\(x\)前第一个不是\(9\)的位置为止,区间\(+1\)

由进位\(\rightarrow\)不进位同理,需要找到第一个不是\(10\)的位置,区间\(-1\)

维护区间最小值和最大值再用线段树上二分即可支持上述查询

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 1001001
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
int n,m,mn[MAXN<<2],mx[MAXN<<2],tag[MAXN<<2],g[MAXN];
char s[2][MAXN];
void build(int k,int l,int r)
{
	if(l==r) {mn[k]=mx[k]=g[l];return ;}
	int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	mn[k]=min(mn[k<<1],mn[k<<1|1]),mx[k]=max(mx[k<<1],mx[k<<1|1]);
}
inline void pshd(int k)
{
	mn[k<<1]+=tag[k],mn[k<<1|1]+=tag[k],
	mx[k<<1]+=tag[k],mx[k<<1|1]+=tag[k],
	tag[k<<1]+=tag[k],tag[k<<1|1]+=tag[k],tag[k]=0;
}
void mdf(int k,int l,int r,int a,int b,int w)
{
	if(a<=l&&r<=b) {mn[k]+=w,mx[k]+=w,tag[k]+=w;return ;}
	int mid=l+r>>1;if(tag[k]) pshd(k);
	if(a<=mid) mdf(k<<1,l,mid,a,b,w);
	if(b>mid) mdf(k<<1|1,mid+1,r,a,b,w);
	mn[k]=min(mn[k<<1],mn[k<<1|1]),mx[k]=max(mx[k<<1],mx[k<<1|1]);
}
void upd(int k,int l,int r,int x,int w)
{
	if(l==r){mn[k]=mx[k]=w,tag[k]=0;return ;}
	int mid=l+r>>1;if(tag[k]) pshd(k);
	x<=mid?upd(k<<1,l,mid,x,w):upd(k<<1|1,mid+1,r,x,w);
	mn[k]=min(mn[k<<1],mn[k<<1|1]),mx[k]=max(mx[k<<1],mx[k<<1|1]);
}
int getw(int k,int l,int r,int x)
{
	if(l==r) return mn[k];int mid=l+r>>1;if(tag[k]) pshd(k);
	return x<=mid?getw(k<<1,l,mid,x):getw(k<<1|1,mid+1,r,x);
}
int xres=0;
void query(int k,int l,int r,int a,int b,int w)
{
	if(a<=l&&r<=b)
	{
		if(l==r){if(mn[k]!=w) xres=max(xres,l);return ;}
		int mid=l+r>>1;pshd(k);
		if(mx[k<<1|1]!=w||mn[k<<1|1]!=w) query(k<<1|1,mid+1,r,a,b,w);
		else if(mx[k<<1]!=w||mn[k<<1]!=w) query(k<<1,l,mid,a,b,w);
		return ;
	}
	int mid=l+r>>1;if(tag[k]) pshd(k);
	if(b<=mid) query(k<<1,l,mid,a,b,w);
	else if(a>mid) query(k<<1|1,mid+1,r,a,b,w);
	else 
	{
		query(k<<1|1,mid+1,r,a,b,w);
		if(xres) return ;
		query(k<<1,l,mid,a,b,w);
	}
}
int main()
{
	n=read(),m=read();int t,a,b,las,res,nw;
	scanf("%s%s",s[0]+1,s[1]+1);
	rep(i,1,n) g[i]=s[0][i]-'0'+s[1][i]-'0';
	dwn(i,n-1,1) g[i]+=g[i+1]/10;
	build(1,1,n);
	while(m--)
	{
		t=read()-1,a=read(),b=read(),las=getw(1,1,n,a);
		if(b==s[t][a]-'0') {printf("%d %d\n",las%10,0);continue;}
		res=2,s[t][a]=b+'0',nw=b+s[t^1][a]-'0'+(a<n?getw(1,1,n,a+1)/10:0);
		upd(1,1,n,a,nw);
		if(nw>=10&&las<10&&a>1)
		{
			xres=0;query(1,1,n,1,a-1,9);xres=max(xres,1);
			res+=a-xres;mdf(1,1,n,xres,a-1,1);
		}
		if(nw<10&&las>=10&&a>1)
		{
			xres=0;query(1,1,n,1,a-1,10);xres=max(xres,1);
			res+=a-xres;mdf(1,1,n,xres,a-1,-1);
		}
		printf("%d %d\n",nw%10,res);
	}
}

J. Suffix Automaton

建反串后每个节点\(x\)\(fa_x\)连字符为\(ch[endpos_x-mxlen_{fa}]\)的边,然后按照字符从小到大\(dfs\)即得到字典序排序后的串

本题需要先按长度排序,若已知对该长度有贡献的节点集合,则只需找到按\(dfs\)序排序后的第\(k\)小,用线段树容易维护

而每个节点只会对连续的一段长度有贡献,分别在起始插入和终止删除即可

除此之外由于需要找到原串中的第一次出现位置,需要在反串中维护\(pos\)的最大值

#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 2001001
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
char s[MAXN];
vector<int> v[MAXN];
vector<pii> G[MAXN];
int m,n,sum[MAXN<<2],ans[MAXN],len[MAXN],dfn[MAXN],stp,hsh[MAXN];
pair<ll,int> q[MAXN];
int rt,las,tot,mxl[MAXN],fa[MAXN],pos[MAXN];
int tr[MAXN][26];
ll c[MAXN],num[MAXN];
void extend(int c,int id)
{
    int p=las,np=las=++tot;mxl[np]=mxl[p]+1;pos[np]=id;
    for(;p&&!tr[p][c];p=fa[p]) tr[p][c]=np;
    if(!p) {fa[np]=rt;return ;}int q=tr[p][c];
    if(mxl[q]==mxl[p]+1) {fa[np]=q;return ;}
    int nq=++tot;mxl[nq]=mxl[p]+1;pos[nq]=pos[q];
    memcpy(tr[nq],tr[q],sizeof(tr[nq]));
    fa[nq]=fa[q],fa[np]=fa[q]=nq;
    for(;p&&tr[p][c]==q;p=fa[p]) tr[p][c]=nq;
}
void dfs(int x)
{
	dfn[x]=++stp,hsh[stp]=x;sort(G[x].begin(),G[x].end());
	v[mxl[fa[x]]+1].pb(dfn[x]);v[mxl[x]+1].pb(-dfn[x]);
	for(auto e:G[x]) dfs(e.se),pos[x]=max(pos[x],pos[e.se]);
}
void mdf(int k,int l,int r,int x,int w)
{
	sum[k]+=w;if(l==r) return ;int mid=l+r>>1;
	x<=mid?mdf(k<<1,l,mid,x,w):mdf(k<<1|1,mid+1,r,x,w);
}
int query(int k,int l,int r,int w)
{
	if(l==r) return l;int mid=l+r>>1;
	if(sum[k<<1]>=w) return query(k<<1,l,mid,w);
	else return query(k<<1|1,mid+1,r,w-sum[k<<1]);
}
int main()
{
	rt=las=tot=1;scanf("%s",s+1);n=strlen(s+1);
	reverse(s+1,s+n+1);
	rep(i,1,n) extend(s[i]-'a',i);
	m=read();rep(i,1,m) q[i].fi=read(),q[i].se=i;
	sort(q+1,q+m+1);int j=1;
	rep(i,1,tot) c[mxl[fa[i]]+1]++,c[mxl[i]+1]--;
	rep(i,2,tot) G[fa[i]].pb({s[pos[i]-mxl[fa[i]]]-'a',i});
	dfs(1);
	rep(i,1,n)
	{
		for(auto t:v[i]) mdf(1,1,tot,t>0?t:-t,t>0?1:-1);
		c[i]+=c[i-1],num[i]=num[i-1]+c[i];
		for(;q[j].fi<=num[i]&&j<=m;++j)
		{
			ans[q[j].se]=pos[hsh[query(1,1,tot,q[j].fi-num[i-1])]];
			len[q[j].se]=i;
		}
	}
	rep(i,1,m) 
		if(!ans[i]) puts("-1 -1");
		else printf("%d %d\n",n+1-ans[i],n+1-(ans[i]-len[i]+1));
}

ccpc 2018 Guilin(贪心、容斥+ntt)

A. Array Merge

假设已经将\(A,B\)数列分为了若干块,对于新的两个块,容易得到实际上需要将平均数较大的块放在前面

考虑如何对\(A,B\)进行划分

在已经划分好了前\(k\)段时,对新加入的一个数\(x\),若\(x\)的平均值大于等于前一块则需要将两个块合并,继续此过程;否则作为一个新块

该过程事实上模拟了两个数列归并的过程,否则归并时还需要维护两个同数列的块合并的情况,较为繁琐

#include<bits/stdc++.h>
#define ll long long
#define db double
#define inf 2139062143
#define MAXN 100100
#define pii pair<int,int>
#define fi first
#define se second
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0',ch=getchar();}
	return x*f;
}
int n,m,la,lb,a[MAXN],b[MAXN],na,nb;ll ans;
pair<ll,int> g[MAXN],h[MAXN];
inline calc(int x,int y,int t)
{
	if(!t) rep(i,1,g[x].se) na++,ans+=1LL*(nb+na)*a[na];
	else rep(i,1,h[y].se) nb++,ans+=1LL*(nb+na)*b[nb];
}
int main()
{
	int x,p,q;rep(T,1,read())
	{
		n=read(),m=read(),la=lb=0;ans=0LL;
		rep(i,1,n)
		{
			a[i]=x=read();g[++la]={x,1};
			while(la>=2&&g[la].fi*g[la-1].se>=g[la-1].fi*g[la].se)
				g[la-1].fi+=g[la].fi,g[la-1].se+=g[la].se,la--;
		}
		rep(i,1,m)
		{
			b[i]=x=read();h[++lb]={x,1};
			while(lb>=2&&h[lb].fi*h[lb-1].se>=h[lb-1].fi*h[lb].se)
				h[lb-1].fi+=h[lb].fi,h[lb-1].se+=h[lb].se,lb--;
		}
		na=nb=0;p=q=1;
		while(p<=la||q<=lb)
		{
			if(q>lb) calc(p++,q,0);
			else if(p>la) calc(p,q++,1);
			else if(g[p].fi*h[q].se>h[q].fi*g[p].se) calc(p++,q,0);
			else calc(p,q++,1);
		}
		printf("Case %d: %lld\n",T,ans);
	}
}

B. Array Modify

过程相当于逆序后的\(A_i\)\(L\)\(1\)构成的多项式即\(1(L)\)\(m\)次卷积

考虑如何求\(1(L)\)\(m\)次幂,生成函数为:\((1+x+x^2+\cdots +x^{L-1})^m\)

对于\(x^n\)的系数,考虑容斥

当所有多项式均有无限项时,直接插板即可,系数为\(\binom{m+n-1}{n}\)

\(L\le n\)时,存在选择一个\(\ge L\)幂次的不合法方案

​ 需要减去的系数\(=\binom{m}{1}\sum\limits_{i=L}^n\binom{n-i+m-1-1}{n-i}=\binom{m}{1}\sum\limits_{i=0}^{n-L}\binom{i+m-2}{i}=\binom{m}{1}\binom{n-L+m-1}{n-L}\)

\(kL\le n\)时,本质为先选择\(k\)个块强制它们至少为\(L\),之后仍然插板

​ 系数为\(\binom{m}{k}\binom{N-kL+m-1}{m-1}\)

\(B_{kL}=(-1)^k\ \binom{m}{k}\)其余为\(0\)\(A_i=\binom{m+i-1}{i}\)\(A\)\(B\)卷积即可得到\(1(L)\)

#include<bits/stdc++.h>
#define ll long long
#define db double
#define inf 2139062143
#define MAXN 400100
#define MOD 998244353
#define pii pair<int,int>
#define fi first
#define se second
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0',ch=getchar();}
	return x*f;
}
namespace CALC
{
	inline int pls(int a,int b){return a+b>=MOD?a+b-MOD:(a+b<0?a+b+MOD:a+b);}
	inline int mns(int a,int b){return a-b<0?a-b+MOD:(a-b>=MOD?a-b-MOD:a-b);}
	inline int mul(int a,int b){return (1LL*a*b)%MOD;}
	inline void inc(int &a,int b){a=pls(a,b);}
	inline void dec(int &a,int b){a=mns(a,b);}
	inline void tms(int &a,int b){a=mul(a,b);}
	inline int qp(int x,int t,int res=1)
		{for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);return res;}
	inline int Inv(int x){return qp(x,MOD-2);}
}
using namespace CALC;
int n,m,L,g[MAXN],h[MAXN],a[MAXN],rev[MAXN],pw[30],ipw[30];
int mem(int n)
{
    int lg=1,lim=1;
    for(;lim<n;lim<<=1,lg++)
        pw[lg]=qp(3,(MOD-1)/(1<<lg)),ipw[lg]=Inv(pw[lg]);
    rep(i,0,lim-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-2));
    return lim;
}
void ntt(int *a,int n,int f)
{
    rep(i,0,n-1) if(i<rev[i]) swap(a[i],a[rev[i]]);
    for(int i=1,t=1;i<n;i<<=1,++t)
    {
        int wn= f>0?pw[t]:ipw[t];for(int j=0;j<n;j+=i<<1)
        {
            int w=1,x,y;for(int k=0;k<i;++k,w=mul(w,wn))
                x=a[j+k],y=mul(a[j+k+i],w),a[j+k]=pls(x,y),a[j+k+i]=mns(x,y);
        }
    }
    if(f>0) return ;int nv=Inv(n);rep(i,0,n-1) a[i]=mul(a[i],nv);
}
int main()
{
	int len,now;rep(T,1,read())
	{
		n=read(),L=read(),m=read();
		len=mem(n+1<<1);
		g[0]=1;rep(i,1,n-1) g[i]=mul(mul(g[i-1],m+i-1),Inv(i));
		h[0]=now=1;
		rep(i,1,n-1) 
			if(i%L==0&&i/L<=m) tms(now,mul(m+1-i/L,Inv(i/L))),h[i]=now=mns(0,now);
			else h[i]=0;
		rep(i,0,n-1) a[n-i-1]=read();
		rep(i,n,len-1) a[i]=g[i]=h[i]=0;
		ntt(g,len,1);ntt(h,len,1);rep(i,0,len-1) g[i]=mul(g[i],h[i]);ntt(g,len,-1);
		rep(i,n,len-1) g[i]=0;
		ntt(g,len,1);ntt(a,len,1);rep(i,0,len-1) a[i]=mul(a[i],g[i]);ntt(a,len,-1);
		printf("Case %d: ",T);
		dwn(i,n-1,0) printf("%d%c",a[i],i?' ':'\n');
	}
}
posted @ 2021-10-27 11:59  jack_yyc  阅读(74)  评论(0编辑  收藏  举报