杂题

1

CF446D
题意:给定 500 个点的图,上面有不多于 100 个标记点,其中 n 一定是标记点。求从 1 号点出发,随机游走到的第 K 个点恰好是 n 的概率。随机游走定义为每次在一个点等概率选择一条边走出去。 K ≤ 1 0 9 K\leq 10^9 K109

如果我们求出 p i , j p_{i,j} pi,j 表示从 i 出发第一个到达的关键点是 j 的概率,其中 i,j 都是关键点。那我们就可以矩乘了,转移矩阵就是这个 p。为了求 p,我们枚举一个 j,可以高斯消元出所有 p i , j p_{i,j} pi,j,复杂度 n4。如果我们钦定不等于 j 的关键点 k 的 p 等于 0,发现高消的矩阵每次都不会变,只有最右一列常数项有变化。这样我们求出系数矩阵的逆矩阵,每次用 n2 的时间乘上一个向量即可。时间复杂度 O ( n 3 + n 3 log ⁡ n ) O(n^3+n^3\log n) O(n3+n3logn)。注意求逆矩阵的时候行变换要做完整。

#include
#define ll long long
#define fir first
#define sec second
using namespace std;
const int N=510;
struct edge {
	int to,next;
}ed[200010];
struct Matrix {
	double a[510][510]; int n,m;
	Matrix (int _n=0,int _m=0) {n=_n,m=_m;}
	void clr() {memset(a,0,sizeof(a));}
};
int sz,head[N],tag[N],ind[N],n,m,K,cnt,du[N];
Matrix operator * (Matrix a,Matrix b)
{
	Matrix ans(a.n,b.m);
	for(int i=1;i<=a.n;i++)
	{
		for(int j=1;j<=b.m;j++)
		{
			double tmp=0;
			for(int k=1;k<=a.m;k++) tmp+=a.a[i][k]*b.a[k][j];
			ans.a[i][j]=tmp;
		}
	}
	return ans;
}
int read()
{
	int x=0,flag=1;char c=getchar();
	while(!isdigit(c)) {if(c=='-') flag=-1;c=getchar();}
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x*flag;
}
void add_edge(int from,int to)
{
	ed[++sz].to=to,du[to]++;
	ed[sz].next=head[from];
	head[from]=sz;
}
Matrix inv(Matrix a)
{
	Matrix b(a.n,a.m); b.clr();
	Matrix z=a;
	for(int i=1;i<=n;i++) b.a[i][i]=1;
	for(int i=1;i<=n;i++)
	{
		int ind=i; for(int j=i+1;j<=n;j++) if(fabs(a.a[j][i])>fabs(a.a[ind][i])) ind=j;
		if(ind^i) for(int j=1;j<=n;j++) swap(a.a[i][j],a.a[ind][j]),swap(b.a[i][j],b.a[ind][j]);
		double t=a.a[i][i]; for(int j=1;j<=n;j++) a.a[i][j]/=t,b.a[i][j]/=t;
		for(int j=1;j<=n;j++) if(i^j)
		{
			double t=a.a[j][i];
			for(int k=1;k<=n;k++) a.a[j][k]-=t*a.a[i][k],b.a[j][k]-=t*b.a[i][k];
		}
	}
	return b;
}
Matrix qpow(Matrix ans,int b)
{
	Matrix base=ans; b--;
	while(b)
	{
		if(b&1) ans=ans*base;
		base=base*base;
		b>>=1;
	}
	return ans;
}
int main()
{
	n=read(),m=read(),K=read()-1;
	for(int i=1;i<=n;i++)
	{
		tag[i]=read();
		if(tag[i]) ind[i]=++cnt;
	}
	for(int i=1;i<=m;i++) 
	{
		int u=read(),v=read();
		add_edge(u,v),add_edge(v,u);
	}
	Matrix a(n,n);
	for(int u=1;u<=n;u++)
	{
		if(tag[u]) a.a[u][u]=1;
		else 
		{
			for(int i=head[u];i;i=ed[i].next)
			{
				int v=ed[i].to;
				a.a[u][v]+=1.0/du[u];
			}
			a.a[u][u]=-1;
		}
	}
	Matrix trans(n,n),tmp(n,1),z(n,1),p(cnt,cnt),ans(cnt,1);
	trans=inv(a);
	for(int i=1;i<=n;i++) if(tag[i])
	{
		for(int j=1;j<=n;j++) tmp.a[j][1]=0;
		tmp.a[i][1]=1;
		z=trans*tmp;
		for(int j=1;j<=n;j++) if(tag[j])
		{
			for(int k=head[j];k;k=ed[k].next)
			{
				int v=ed[k].to;
				p.a[ind[i]][ind[j]]+=z.a[v][1];
			}
			p.a[ind[i]][ind[j]]/=du[j];
		}
		ans.a[ind[i]][1]=z.a[1][1];
	}
	if(K>1) ans=qpow(p,K-1)*ans;
	printf("%.10lf",ans.a[ind[n]][1]);
	return 0;
}

2

BZOJ4664 Count
题意:给出 n 个数,把他们任意排列,定义一个序列的混乱程度为相邻两项差的绝对值。求混乱程度为 L 的方案数。 n ≤ 100 , a i , L ≤ 1000 n\leq 100,a_i,L\leq 1000 n100,ai,L1000

一个显然的想法是类似 BZOJ4998,发现对于相邻两项一定是大数的贡献为正,小数的贡献为负。可以从小到大把数插进去,维护已经插入的数是连续的几段。每次插入一个数讨论是新建一段还是合并两段还是放在某段的一端,就可以计算这个数的贡献。但是这样复杂度不能接受,因为当前很负的贡献最后可能合法。

观察到贡献是可以拆开计算的。 h r − h l = ∑ l < i ≤ r h i − h i − 1 h_r-h_l=\sum_{l<i\leq r} h_i-h_{i-1} hrhl=l<irhihi1,按照这个贡献的计算方式发现贡献只增不减,就可以只存到 L 了。

#include
#define ll long long
#define fir first
#define sec second
using namespace std;
const int mod=1e9+7;
int f[2][110][1010][4],h[110];
int read()
{
	int x=0,flag=1;char c=getchar();
	while(!isdigit(c)) {if(c=='-') flag=-1;c=getchar();}
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x*flag;
}
inline void add(int &a,int b) {a+b>=mod?a+=b-mod:a+=b;}
int main()
{
	int n=read(),L=read(),now=0;
	for(int i=1;i<=n;i++) h[i]=read();
	sort(h+1,h+n+1);
	f[now][1][0][0]=1;
	if(n>1) f[now][1][0][1]=2;	//f[i][j][k][0/1/2] 放了前i个数,形成连续j段,混乱度为k,两端放了l个的方案数 
	else f[now][1][0][2]=1;
	for(int i=1;i0) add(f[now^1][j+1][nxt][l],t*(j+1-l)%mod);
					add(f[now^1][j+1][nxt][l+1],t*(2-l)%mod);
					add(f[now^1][j][nxt][l],t*(j*2-l)%mod);
					add(f[now^1][j][nxt][l+1],t*(2-l)%mod);
					if(j>1) add(f[now^1][j-1][nxt][l],t*(j-1)%mod);
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<=L;i++) ans=(ans+f[now][1][i][2])%mod;
	cout<

3

定义字符串 s 的子串是 s 的重复子串当且仅当这个子串在 s 里出现了两次(不要求不相交)。给定 s,每次询问给出 l,r,求 s[l…r] 的最长重复子串的长度。

首先有一个 LCT 的做法。从左到右扫描线,在后缀树上每个点维护上一次出现的位置,并且用线段树维护每个左端点的答案。右端点移动一个位置,access 的时候更新答案即可。

说一下后缀树上 right 集合并的做法。如果二分了一个答案,问题就变成了在一个区间内能不能找到两个点在后缀树上的 lca 深度 >=mid,显然我们只需要关注 height>=mid 的那些连续段里在原串位置相邻的点对即可。而 height 相当于后缀树上 lca,因此我们可以在后缀树上启发式合并 right,这样会产生 nlogn 个点对,剩下的用主席树维护即可。

#include
#define ll long long
#define fir first
#define sec second
#define pb push_back
using namespace std;
const int N=200010;
typedef pair P;
int rt[N],l[N],fa[N],ch[N][26],lst=1,cnt=1,c[N];
char s[N];
vector 

d[N]; set ss[N]; int read() { int x=0,flag=1;char c=getchar(); while(!isdigit(c)) {if(c=='-') flag=-1;c=getchar();} while(isdigit(c)) x=x*10+c-'0',c=getchar(); return x*flag; } void extend(int c,int x) { int np=++cnt,p=lst; l[np]=l[p]+1,lst=np; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if(!p) fa[np]=1; else { int q=ch[p][c]; if(l[q]==l[p]+1) fa[np]=q; else { int nq=++cnt; l[nq]=l[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q],fa[np]=fa[q]=nq; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } ss[lst].insert(x); } namespace Segment { int tot,ls[N*400],rs[N*400],Min[N*400]; void modify(int pre,int &root,int l,int r,int x,int y) { root=++tot,Min[root]=1e9; if(l==r) {Min[root]=min(Min[root],y); return;} int mid=l+r>>1; if(x<=mid) modify(ls[pre],ls[root],l,mid,x,y),rs[root]=rs[pre]; else modify(rs[pre],rs[root],mid+1,r,x,y),ls[root]=ls[pre]; Min[root]=min(Min[ls[root]],Min[rs[root]]); } int query(int root,int l,int r,int x,int y) { if(x<=l&&y>=r) return Min[root]; int mid=l+r>>1,ans=1e9; if(x<=mid) ans=query(ls[root],l,mid,x,y); if(y>mid) ans=min(ans,query(rs[root],mid+1,r,x,y)); return ans; } } using namespace Segment; bool cmp(int a,int b) {return l[a]>l[b];} int main() { int n=read(),m=read(); scanf("%s",s+1); Min[0]=1e9; for(int i=1;i<=n;i++) extend(s[i]-'a',i); for(int i=1;i<=cnt;i++) ss[i].insert(-1e9),ss[i].insert(1e9); for(int i=1;i<=cnt;i++) c[i]=i; sort(c+1,c+cnt+1,cmp); for(int i=1;i<=cnt;i++) { int u=c[i],a=fa[u],b=u; if(ss[a].size()n) continue; int k=*--ss[a].lower_bound(x),z=*ss[a].lower_bound(x); if(k>=1&&k<=n) d[l[fa[u]]].pb(P(k,x)); if(z>=1&&z<=n) d[l[fa[u]]].pb(P(x,z)); ss[a].insert(x); } } for(int i=n;i>=1;i--) { rt[i]=rt[i+1]; for(int j=0;j>1; if(query(rt[mid],1,n,x+mid-1,y)<=y) l=mid+1,ans=mid; else r=mid-1; } cout<

4

BZOJ4762 最小集合
题意:定义一个非空集合是合法的,当且仅当它满足以下两个条件。
1.集合内所有元素and和为0
2.它的非空子集中仅有它本身满足1
给出一个集合S,求它的合法非空子集数。 n ≤ 1000 , a i ≤ 1024 n\leq 1000,a_i\leq 1024 n1000,ai1024

第二个条件等价于删去集合里的每个元素的 and 都不为 0,相当于有 |S| 个限制,那么我们就可以容斥。枚举一个集合不满足条件,答案等于 ∑ S [ f ( S ) = 0 ] ∑ T ⊆ S [ OR x ∈ T f ( S ⊕ x ) = 0 ] ( − 1 ) ∣ T ∣ \sum_{S}[f(S)=0]\sum_{T\subseteq S}[\text{OR}_{x\in T}f(S \oplus x)=0](-1)^{|T|} S[f(S)=0]TS[ORxTf(Sx)=0](1)T。然后就可以 DP 了,设 f[i][S][T] 表示考虑了前 i 个数,选出一些数 and 为 S,去掉每个数的 f 的或为 T 的方案数(带上-1)。答案是 f[n][0][0]。

#include
#define ll long long
#define fir first
#define sec second
using namespace std;
const int mod=1e9+7;
int f[2][1030][1030],a[1010];
int read()
{
	int x=0,flag=1;char c=getchar();
	while(!isdigit(c)) {if(c=='-') flag=-1;c=getchar();}
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x*flag;
}
inline void add(int &a,int b) {a+b>=mod?a+=b-mod:a+=b;}
int main()
{
	int n=read(),now=0,U=1023;
	for(int i=1;i<=n;i++) a[i]=read();
	f[now][1023][1023]=1;
	for(int i=0;i

5

给定一棵树,求 ∑ i = 1 n ∑ j = 1 n d i s ( i , j ) ⋅ φ ( i j ) \sum_{i=1}^n\sum_{j=1}^n dis(i,j)·\varphi(ij) i=1nj=1ndis(i,j)φ(ij) n ≤ 1 0 5 n\leq 10^5 n105

众所周知, φ ( i j ) = φ ( i ) ⋅ φ ( j ) φ ( gcd ⁡ ( i , j ) ) ⋅ gcd ⁡ ( i , j ) \varphi(ij)=\frac{\varphi(i)\cdot\varphi(j)}{\varphi(\gcd(i,j))}\cdot \gcd(i,j) φ(ij)=φ(gcd(i,j))φ(i)φ(j)gcd(i,j)。证明的话按照容斥的式子展开即可。最后化简出来:
a n s = ∑ d = 1 n d φ ( d ) ∑ e = 1 n / d μ ( e ) g ( d e ) ans=\sum_{d=1}^n\frac{d}{\varphi(d)}\sum_{e=1}^{n/d}\mu(e)g(de) ans=d=1nφ(d)de=1n/dμ(e)g(de)

其中 g ( n ) = ∑ n ∣ i ∑ n ∣ j d i s ( i , j ) ⋅ φ ( i ) ⋅ φ ( j ) g(n)=\sum_{n|i}\sum_{n|j} dis(i,j)\cdot \varphi(i)\cdot \varphi(j) g(n)=ninjdis(i,j)φ(i)φ(j)。g 显然可以通过虚树上乱搞得到。

(代码稍微压了压)

#include
#define ll long long
#define fir first
#define sec second
using namespace std;
const int N=200010,mod=998244353;
struct edge {int to,next;} ed[N<<1];
int sz,head[N],deep[N],seq[N],f[N][20],g[N],phi[N],st[N],top,sigma,cnt,val[N],mu[N],np[N],tim,A[N],B[N],vis[N],prime[N],gg[N],a[N],inv[N],n,size[N];
int read()
{
	int x=0,flag=1;char c=getchar();
	while(!isdigit(c)) {if(c=='-') flag=-1;c=getchar();}
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x*flag;
}
void add1(int from,int to)
{
	ed[++sz]=(edge){to,head[from]};
	head[from]=sz;
}
void dfs0(int u,int ff)
{
	deep[u]=deep[ff]+1; seq[A[u]=++tim]=u;
	for(int i=head[u];i;i=ed[i].next)
	{
		int v=ed[i].to; if(v==ff) continue;
		dfs0(v,u),seq[++tim]=u;
	}	B[u]=tim;
}
void pre(int n)
{
	phi[1]=1,mu[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!np[i]) prime[++cnt]=i,phi[i]=i-1,mu[i]=-1;
		for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
		{
			np[i*prime[j]]=1;
			if(i%prime[j]==0) {phi[i*prime[j]]=phi[i]*prime[j];break;}
			phi[i*prime[j]]=phi[i]*(prime[j]-1),mu[i*prime[j]]=-mu[i];
		}
	}
}
int Lca(int x,int y)
{
	x=A[x],y=A[y]; if(x>y) swap(x,y); int k=gg[y-x+1];
	return deep[f[x][k]]<=deep[f[y-(1<1;i--) if(!vis[t=Lca(a[i],a[i-1])]) vis[t]=2,a[++cnt]=t;
	sort(a+1,a+cnt+1,cmp); st[top=1]=a[1];
	for(int i=2;i<=cnt;i++)
	{
		while(top&&(A[a[i]]B[st[top]])) (size[st[top-1]]+=size[st[top]])%=mod,ans=(ans+1ll*val[top]*size[st[top]]%mod*(sigma-size[st[top]]))%mod,top--;
		st[++top]=a[i],val[top]=deep[a[i]]-deep[st[top-1]];
	}
	while(top) (size[st[top-1]]+=size[st[top]])%=mod,ans=(ans+1ll*val[top]*size[st[top]]%mod*(sigma-size[st[top]]))%mod,top--;
	for(int i=1;i<=cnt;i++) vis[a[i]]=size[a[i]]=val[i]=0;
	return ans;
}
int main()
{
	n=read();int ans=0; for(int i=1,u,v;i>1]+1;
	inv[1]=1; for(int i=2;i<=n;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<=tim;i++) f[i][0]=seq[i]; 
	for(int j=1;j<=18;j++) for(int i=1;i+(1<

6

给定一个长度为 n 的字符串 s,有 m 个串 Ai(一开始为空),和一个集合 S(一开始为空),维护三种操作:1.向 S 里添加一个编号 i(i<=m)2.把下标在 S 里的串 Ai 后面同时添加一个读入的字符 c。3.查询 Ai 在 S 里出现了多少次。 n , q ≤ 1 0 5 n,q\leq 10^5 n,q105

假设我们得到了添加字符 c 组成的字符串 T,观察到每个串进出集合一次会在后面添加 T 的一个子串。也就是说如果给出 S+T 的两个子串 A,B,我们能定位到 A+B 的信息,这个题目就做完了。考场上一直在想 SAM 的做法,发现这个问题“匹配”的性质不强。发现匹配 A+B 的后缀一定是字典序一段连续区间,因此我们可以在 sa 上二分。

启示:1.一个串匹配的后缀在 sa 上是一个区间,查询某个串可以在 sa 上二分。

#include
#define ll long long
#define fir first
#define sec second
using namespace std;
const int N=1000010;
int sum[N],gg[N],wb[N],pos[N],sa[N],x[N],y[N],xx[N],cnt,f[N][21],rk[N],height[N],lst[N],opt[N],n,m,q,vis[N];
char s[N];
struct node {
	int x,y,len;
	node (int _x=0,int _y=0,int _len=0) {x=_x,y=_y,len=_len;}
	void print()
	{
		if(len==0) puts("0");
		else if(len==-1) puts("0");
		else cout<j) y[++p]=sa[i]-j;
		for(int i=1;i<=m;i++) wb[i]=0;
		for(int i=1;i<=n;i++) wb[x[y[i]]]++;
		for(int i=1;i<=m;i++) wb[i]+=wb[i-1];
		for(int i=n;i>=1;i--) sa[wb[x[y[i]]]--]=y[i];
		swap(x,y),x[sa[1]]=1;p=1;
		for(int i=2;i<=n;i++) 
		{
			if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j]) x[sa[i]]=p;
			else x[sa[i]]=++p;
		}
	}
	for(int i=1;i<=n;i++) rk[sa[i]]=i;
	for(int i=1;i<=n;i++)
	{
		if(k) k--;
		if(rk[i]==1) continue;
		int j=sa[rk[i]-1];
		while(s[i+k]==s[j+k]) k++;
		height[rk[i]]=k;
	}
	for(int i=1;i<=n;i++) f[i][0]=height[i];
	for(int j=1;j<=20;j++) for(int i=1;i+(1<r) swap(l,r); l++; int k=gg[r-l+1];
	return min(f[l][k],f[r-(1<=len) return 1;
	return s[x+t]<=s[y+t];
}
inline bool dydy(int x,int y,int len)
{
	int t=lcp(x,y);
	if(t>=len) return 1;
	return s[x+t]>=s[y+t];
}
node merge(node a,node b)
{
	if(a.len==-1) return a;
	if(b.len==0) return a;
	int l=a.x,r=a.y,ansl=a.y+1,ansr=a.x-1;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(dydy(sa[mid]+a.len,b.x,b.len)) ansl=mid,r=mid-1;
		else l=mid+1;
	}
	l=a.x,r=a.y;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(xydy(sa[mid]+a.len,b.x,b.len)) ansr=mid,l=mid+1;
		else r=mid-1;
	}
	if(ansl>ansr) return node(0,0,-1);
	return node(ansl,ansr,a.len+b.len);
}
int main()
{
	for(int i=2;i<=1000000;i++) gg[i]=gg[i>>1]+1;
	int id=read(); n=read(),m=read(),q=read(); scanf("%s",s+1); s[n+1]='a'-2; cnt=n+1;
	for(int i=1;i<=q;i++) 
	{
		opt[i]=read(); 
		if(opt[i]!=2) xx[i]=read();
		else 
		{
			char c=getchar(); while(c<'a'||c>'z') c=getchar();
			s[++cnt]=c;
		}
		pos[i]=cnt;
	}
	build(cnt,n);
	s[cnt+1]='a'-1;
	for(int i=1;i<=m;i++) ksj[i]=node(1,cnt,0);
	for(int i=1;i<=q;i++)
	{
		int x=xx[i];
		if(opt[i]==1)
		{
			if(vis[x]) ksj[x]=merge(ksj[x],node(lst[x]+1,0,pos[i]-lst[x]));
			else lst[x]=pos[i];
			vis[x]^=1;
		}
		else if(opt[i]==3)
		{
			if(!vis[x]) ksj[x].print();
			else merge(ksj[x],node(lst[x]+1,0,pos[i]-lst[x])).print();
		}
	}
	return 0;
}

你可能感兴趣的:(杂题)