luogu 记录

luogu 记录

P8215 分组作业

题面:

老师布置了分组作业。在此之前,老师将班上 \(2n\) 个学生分成了 \(n\) 组,每组两个人。其中 \(1\) 号和 \(2\) 号为一组,\(3\) 号和 \(4\) 号为一组,……,\(2n-1\) 号和 \(2n\) 号为一组。

老师让每个队伍自行安排分工。这样是否合作就成了一个大问题。大家决定用表决的方式来确定。首先每个人决定是否愿意和队友合作。不同的人因为自己的原因和分配的队友的原因,对合作的意愿不一样,对于第 \(i\) 个学生,选择“愿意”会产生 \(c_i\) 的不满,选择“不愿意”会产生 \(d_i\) 的不满。

如果两名队友都选择“愿意”,那么根据实际情况他们可以合作或者不合作。但是如果有一名队友选择“不愿意”,那么他们只能不合作。

学生中还有 \(m\) 个单向的喜欢关系,一个关系形如“\(A\) 喜欢 \(B\)”。在这样一个关系中,如果 \(A\) 没有和队友合作,且 \(B\) 选择了“愿意”,\(A\) 会有略微沮丧,产生 \(a_i\) 的不满;如果 \(A\) 表决了“不愿意”,但 \(B\) 成功与队友合作,那么 \(A\) 会羡慕嫉妒恨并产生 \(b_i\) 的不满。(由于当 \(A\)\(B\) 在同一组时这种设定会变得很奇怪,所以题目保证不会有这种情况)其中 \(i\) 表示第 \(i\) 个关系。

如果一个学生 \(i\) 选择了“愿意”但是他的队友选择了“不愿意”,那么他会因为队友产生 \(e_i\) 的不满。

问所有情况下最小的不满之和是多少。

\(1\le n \le 5000\)\(0\le m \le 10000\)\(1\le a_i,b_i,c_i,d_i,e_i\le 10^9\)

题解:

智慧的网络流建图题。

套路的,每个学生都是一个点 \(i\),他有两种选择,都需要付出相应的代价,转化为最小割问题,割到 \(S\) 点集或是 \(T\) 点集分别对应两种选择。即建立边 \((S,i,d_i)\)\((i,T,c_i)\) 我们分别称之为 \(d\) 类边和 \(c\) 类边。

之后再建立对每一组同学 \((i,i+1)\) 建边 \((i,i+1,e_i)\)\((i+1,i,e_{i+1})\)(称之为 \(e\) 类边),表示如果两个人选择状态不一样就会有经过对应 \(e\) 边的 \(S\) 可达 \(T\) 的路径,那就还需要割掉某个 \(e\) 边。

最高妙的地方就是如何处理“喜欢”的限制。

对每组建立点 \(x_i\) 对应组 \((2i-1,2i)\)。若割断后 \(S\) 能到达 \(x_i\) 表示 \(i\) 组选择合作,否则表示不合作。然后建边 \((x_i,2i-1,inf),(x_i,2i,inf)\) (称之为 \(i\) 类边)。

对于每个关系 \(A\) “喜欢” \(B\),建边 \((B,x_{\lceil A/2\rceil},a_i),(x_{\lceil B/2\rceil},A,b_i)\) 称之为 \(a\) 类边和 \(b\) 类边。

正确性可以分类讨论证明。

首先对于 \(a\)\((i,x_j,a_k)\),如果 \(x_j\) 里的组员 \(y\) 选择了不愿意,而 \(i\) 选择了愿意,那么就会有路径 \(S\to i\to x_j\to y\to T\),所以 \(a_k\) 是一定会被割掉的。(不可能不割掉 \(a_k\),如果 \(a_k\) 不割那么由于 \(i\) 边的存在 \(c_{2j-1}\)\(c_{2j}\) 都会割掉,那么 \(y\) 选择不愿意时的 \(d_y\) 就不用割了。)

若一个组 \(x_i\) 只有 \(b\) 边,那么不管组员怎么选都肯定不合作,答案不会变劣,对应到图上即 \(x_i\) 没有入边,所以 \(S\) 也必然不能到达 \(x_i\)

若一个组 \(x_i\) 只有 \(a\) 边,上述论证了如果有人不愿意那 \(a_k\) 肯定会割掉,如果都愿意那选择合作答案肯定不会变劣,这对应了如果两条 \(c\) 边全部割掉那一定没有 \(S\)\(x_i\) 可达 \(T\) 的路径,所以 \(a\) 边也不用割了。

若一个组 \(x_i\) 同时有 \(a,b\) 两种边,那么他割掉的边中肯定不会同时有 \(a,b\) 两种边,这肯定不如把所有 \(a\) 边全割掉或把所有 \(b\) 边全割掉更优。此时若他割掉所有 \(a\) 边表示 \(x_i\) 组不合作,反之表示合作。这两种情况正好分别对应了题目中“喜欢”的两种惩罚状态。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=5005,M=10005,inf=1e9+7;
int n,m,S,T,pos[N*2],dep[N*3],E[N*3],head[N*3],cnt=1;
struct edge{
	int v,nxt,w;
}e[N*8+M*2<<1];

void add(int u,int v,int w){
	e[++cnt]={v,head[u],w};
	head[u]=cnt;
	e[++cnt]={u,head[v],0};
	head[v]=cnt;
}

bool bfs(){
	for(int i=S;i<=T;i++) dep[i]=0,E[i]=head[i];
	queue<int>q; q.push(S); dep[S]=1;
	while(!q.empty()){
		int x=q.front(); q.pop();
		for(int i=head[x],v;i;i=e[i].nxt){
			v=e[i].v;
			if(e[i].w&&!dep[v]){
				dep[v]=dep[x]+1;
				q.push(v);
			}
		}
	}
	return dep[T]!=0;
}

int dfs(int x,int W){
	if(x==T) return W;
	int now=0;
	for(int i=E[x],v;i&&now<W;i=e[i].nxt){
		E[x]=i; v=e[i].v;
		if(e[i].w&&dep[v]==dep[x]+1){
			int tmp=dfs(v,min(e[i].w,W-now));
			if(!tmp) dep[v]=-1;
			else{
				now+=tmp;
				e[i].w-=tmp;
				e[i^1].w+=tmp;
				if(now==W) return W;
			}
		}
	}
	return now;
}

ll dinic(){
	ll ans=0;
	while(bfs()) ans+=dfs(S,inf);
	return ans;
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();m=read();
	S=0;T=n*3+1;
	for(int i=2;i<=n<<1;i+=2) pos[i-1]=pos[i]=n*2+i/2;
	for(int i=1;i<=n<<1;i++){
		add(pos[i],i,inf);
		int	A=read(),B=read(),C=read();
		add(S,i,B); add(i,T,A);
		add(i,(i-1^1)+1,C);
	}
	for(int i=1;i<=m;i++){
		int u=read(),v=read(),A=read(),B=read();
		int x=pos[u],y=pos[v];
		add(y,u,B); add(v,x,A);
	}
	printf("%lld",dinic());
	return 0;
}

P4548 歌唱王国

题面:

在歌唱王国,所有人的名字都是一个非空的仅包含整数 \(1\sim n\) 的字符串。

王国里生活着一大群咕噜兵,他们靠不停地歌唱首领——牛人酋长们的名字来获取力量。咕噜兵每一次歌唱过程是这样的:首先,他从整数生成器那儿获得一个数字,然后花一个时间单位将此数字唱出来,如果他发现某个牛人酋长的名字已经被歌唱出来(即此名字是歌唱序列的一个连续子串),那么这次歌唱过程就立即结束。

相关名词定义:

  • 歌唱序列:如果某人歌唱了 \(x\) 个数字,第 \(i\) 次歌唱的数字为 \(a_i\),那么歌唱序列 \(=(a_1,a_2,\cdots,a_x)\)
  • 整数生成器:歌唱王国的神物,它有一个按钮,如果你按一下按钮,将从 \(1\sim n\) 数字中等概率的随机返回一个整数。
  • 歌唱时间:在一次歌唱过程中花费的时间。

歌唱时间是随机的,无法预料;不过歌唱时间的期望值是固定的,此期望值即平均来说歌唱时间有多长,亦可称作平均歌唱时间。

王国里的人非常喜欢歌唱,他们希望歌唱的时间越长越好,所以他们决定罢免一些牛人酋长,使得平均歌唱时间变长。但是他们不能罢免掉所有的牛人酋长,否则他们每次歌唱都无法停止,无法获取力量;于是他们决定只保留一个牛人酋长而罢免其余的牛人酋长。

你的任务是:对于给定的 \(n\)、牛人酋长的个数 \(t\) 以及每一个牛人酋长的名字,告诉王国里的人们,对于 \(1\leq i\leq t\),如果保留第 \(i\) 个牛人酋长,罢免掉其余的,那么平均歌唱时间将是多少。

提示:此数为一个非负整数!

输出要求:由于这个数字太大,所以你只需输出这个数的末 \(4\) 位数字。如果不足 \(4\) 位,则前面补 \(0\)\(1\leq n\leq 10^5\)\(t\leq 50\)\(1\leq m_i\leq 10^5\)

题解:

题目即要求对于每个模式串求在文本串中第一次出现的期望长度。

考虑 \(kmp\) 的匹配过程,设当前模式串 \(s\) 长度为 \(k\)\(f(i)\) 表示文本串当前匹配到了模式串的第 \(i\) 个字符之后直到结束还需要的期望步数。初始 \(f(k)=0\)

有转移方程 \(f(i)=1+\frac 1 n\sum_{c=1}^nf(tr(i,c))\)\(tr(i,c)\) 表示前 \(i\) 个字符已经匹配上之后在加入一个字符 \(c\) 会匹配到模式串的哪个位置。即若 \(c=s_{i+1}\),那么 \(tr(i,c)=i+1\),否则 \(tr(i,c)=tr(nxt_i,c)\)

注意到 \(tr(i,c)\)\(tr(nxt_i,c)\) 的结果只有当 \(c=s_{i+1}\) 时不同,所以设 \(j=nxt_i\)\(f(j)-\frac 1 nf(tr(j,s_{i+1}))=f(i)-\frac 1 nf(tr(i,s_{i+1}))\)

因为 \(tr(i,s_{i+1})=i+1,tr(nxt_i,s_{i+1})=nxt_{i+1}\) 化简得 \(f(nxt_i)-f(i)=\frac 1 n(f(nxt_{i+1})-f(i+1))\)

\(g(i)=f(nxt_i)-f(i)\),有转移 \(g(i)=\frac{g(i+1)} n\),边界是 \(g(1)=f(0)-f(1)\)。根据 \(f\) 的转移方程有 \(f(0)=1+\frac{n-1} n f(0)+\frac 1 n f(1)\),即 \(f(0)-f(1)=n\),所以 \(g(1)=n\)\(g(i)=n^i\)

因为 \(f(k)=0\),所以 \(g(k)=f(nxt_k)=n^k\),同理 \(f(nxt_{nxt_k})=n^k+n^{nxt_k}\)。所以答案是从 \(k\) 开始跳 \(nxt\)\(n\) 的沿途位置次幂加和。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=1e5+5,mod=10000;
int a[N],nxt[N];
ll fac[N];

void Add(ll &x,ll y){
	x+=y;
	if(x>=mod) x-=mod;
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	fac[0]=1; fac[1]=read();
	for(int i=2;i<N;i++) fac[i]=fac[i-1]*fac[1]%mod;
	int T=read();
	while(T--){
		int n=read();
		for(int i=1;i<=n;i++) a[i]=read();
		nxt[1]=0;
		for(int i=2,j=0;i<=n;i++){
			while(j&&a[i]!=a[j+1]) j=nxt[j];
			if(a[i]==a[j+1]) j++;
			nxt[i]=j;
		}
		ll ans=0;
		for(int i=n;i>=1;i=nxt[i]) Add(ans,fac[i]);
		printf("%04lld\n",ans);
	}
	return 0;
}

P4218 [CTSC2010] 珠宝商

题面:

给定一个长为 \(m\) 的串 \(T\) 和一个 \(n\) 个点的树,每个点有一个小写字母。记 \(str_{u,v}\) 表示 \(u\)\(v\) 的路径上的点连接成的字符串。

\(P(S)\) 表示字符串 \(S\)\(T\) 中的出现次数。求 \(\sum_{u,v}P(str_{u,v})\)\(n,m\leq 50000\)

题解:

处理树上所有路径问题考虑点分治。设当前分治重心为 \(x\),每个点在当前连通块中以 \(x\) 为根时子树大小是 \(siz_x\)

要求出 \(\sum_{x\in {\rm path}(u,v)}P(str_{u,v})\)

\(lpos(S),rpos(S)\) 表示字符串 \(S\)\(T\) 中所有出现位置的左/右端点集合。

式子可变型为 \(\sum_p\sum_u\sum_v[p\in rpos(str_{u,x})][p\in lpos(str_{x,v})]\) 容斥掉 \(u,v\) 属于同一棵分治子树的。

\(x\) 开始遍历每个点,维护 \(str_{u,x}\) 在 SAM 上的点,将其标记一次。

\(lst_{p}\) 表示 \(T[1:p]\) 在 SAM 上的点,\(num_p\) 表示 SAM 上的点 \(p\) 被标记的次数,则 \(\sum_u[p\in rpos(str_{u,x})]\) 即为 \(lst_p\) 沿 parent 树到根路径上 \(num\) 之和。对于 \([p\in lpos]\) 在反串 SAM 上标记即可。

怎么维护 SAM 在字符串前添加一个字符?

\(R[x]\) 表示 SAM 上 \(x\) 节点的 \(endpos\) 集合中的任意一个值,\(son[x][c]\) 表示 \(x\) 节点对应长为 \(len[x]\) 的字符串前添加字符 \(c\) 所对应的节点,有 \(son[fa[x]\ ]\ [\ T[\ R[x]-len[fa[x]]\ ]\ ]=x\)

对于在 \(S\) 前添加字符 \(c\):设 \(S\) 当前对应节点 \(v\)

  • \(|S|<len[v]\),如果 \(c=T[R[v]-|S|]\) 则对应 \(v\),否则为不存在。

  • \(|S|=len[v]\),如果 \(son[v][c]\) 存在则对应 \(son[v][c]\),否则为不存在。

单次复杂度 \(m+siz_x\)。总复杂度 \(O(n\log n+nm)\) 显然不对。

显然对于任意连通块有 \(O(siz^2)\) 的暴力找出所有 \(str_{u,v}\) 询问 \(endpos\) 大小,没有容斥且做完之后不需要继续向下点分治。所以考虑根号分治对小的跑暴力,大的运行上述算法。

注意如果 \(siz_x\) 大但其要容斥的子树 \(siz_v\) 小则在容斥时要跑暴力算法,否则容易用菊花图卡掉。

设两算法的阈值为 \(B\),算法一复杂度是 \(B^2\times \frac n B=nB\),算法二是 \(m\times \frac n B=\frac{nm} B\),平衡为 \(O(n\sqrt n)\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=5e4+5,M=N<<1;
int n,m,siz[N],tot,mx,rt,Bc;
vector<int>e[N];
bool vis[N];
char s[N];
ll ans;

struct SAM{
	int len[M],fat[M],siz[M],R[M],num[M],tot,lst[N],tr[M][26],ids[M],c[N],n,son[M][26];
	char s[N];
	
	int ADD(int c,int cur){
		int x=cur;cur=++tot;
		len[cur]=len[x]+1;
		siz[cur]=1; R[cur]=len[cur];
		while(x&&!tr[x][c]){
			tr[x][c]=cur;
			x=fat[x];
		}
		if(!x){
			fat[cur]=1;
			return cur;
		}
		int y=tr[x][c];
		if(len[y]==len[x]+1){
			fat[cur]=y;
			return cur;
		}
		int dn=y,up=++tot;
		len[up]=len[x]+1;
		fat[up]=fat[y];fat[dn]=fat[cur]=up;
		for(int i=0;i<26;i++) tr[up][i]=tr[dn][i];
		while(x&&tr[x][c]==dn){
			tr[x][c]=up;
			x=fat[x];
		}
		return cur;
	}
	
	void build(){
		tot=lst[0]=1;
		for(int i=1;i<=m;i++)
			lst[i]=ADD(s[i]-'a',lst[i-1]);
		for(int i=1;i<=tot;i++) c[len[i]]++;
		for(int i=1;i<=m;i++) c[i]+=c[i-1];
		for(int i=1;i<=tot;i++) ids[c[len[i]]--]=i;
		for(int i=tot;i>=2;i--){
			int x=ids[i];
			siz[fat[x]]+=siz[x];
			R[fat[x]]=R[x];
			son[fat[x]][s[R[x]-len[fat[x]]]-'a']=x;
		}
	}
	
	int nxt(int x,char c,int str){
		if(!x) return 0;
		if(str<len[x]){
			if(c==s[R[x]-str]) return x;
			else return 0;
		}
		else{
			if(son[x][c-'a']) return son[x][c-'a'];
			else return 0;
		}
	}
	
	void clear(){
		for(int i=1;i<=tot;i++) num[i]=0;
	}
	
	void pushdown(){
		for(int i=2;i<=tot;i++){
			int x=ids[i];
			num[x]+=num[fat[x]];
		}
	}
}A,B;

void dfz(int x,int fa){
	int num=0;siz[x]=1;
	for(int v:e[x])
		if(v!=fa&&!vis[v]){
			dfz(v,x);
			siz[x]+=siz[v];
			num=max(num,siz[v]);
		}
	num=max(num,tot-siz[x]);
	if(num<mx) mx=num,rt=x;
}

void init(){
	scanf("%s",A.s+1);
	for(int i=1;i<=m;i++) B.s[i]=A.s[m-i+1];
	A.build();B.build();
}

namespace subtask1{
	ll dfz(int x,int fa,int now){
		int c=s[x]-'a';
		if(!A.tr[now][c]) return 0;
		now=A.tr[now][c];
		ll ans=A.siz[now];
		for(int v:e[x]) 
			if(!vis[v]&&v!=fa) ans+=dfz(v,x,now);
		return ans;
	}
	
	ll dfs(int x,int fa){
		siz[x]=1;
		ll ans=0;
		ans+=dfz(x,0,1);
		for(int v:e[x])
			if(!vis[v]&&v!=fa){
				ans+=dfs(v,x);
				siz[x]+=siz[v];
			}
		return ans;
	}
}

namespace subtask2{
	void dfs(int x,int fa,int rtx,int rty,int len){
		rtx=A.nxt(rtx,s[x],len);
		rty=B.nxt(rty,s[x],len);
		len++;
		if(!rtx&&!rty) return ;
		if(rtx) A.num[rtx]++;
		if(rty) B.num[rty]++;
		for(int v:e[x])
			if(v!=fa&&!vis[v]) dfs(v,x,rtx,rty,len);
	}
	
	void dfz(int x,int fa){
		siz[x]=1;
		for(int v:e[x])
			if(v!=fa&&!vis[v]){
				dfz(v,x);
				siz[x]+=siz[v];
			}
	}
	
	ll solve(int x,int rtx,int rty,int len){
		if(!rtx&&!rty) return 0;
		A.clear();B.clear();
		dfs(x,0,rtx,rty,len);
		A.pushdown();B.pushdown();
		ll ans=0;
		for(int i=1;i<=m;i++) ans+=1ll*A.num[A.lst[i]]*B.num[B.lst[m-i+1]];
		return ans;
	}
}

namespace subtask3{
	vector<int>vec;
	
	void dfs(int x,int fa,int now,int len){
		now=A.nxt(now,s[x],len);
		len++;
		if(!now) return ;
		vec.push_back(now);
		for(int v:e[x])
			if(v!=fa&&!vis[v]) dfs(v,x,now,len);
	}
	
	ll dfz(int x,int fa,int now){
		now=A.tr[now][s[x]-'a'];
		if(!now) return 0;
		ll ans=A.siz[now];
		for(int v:e[x])
			if(v!=fa&&!vis[v]) ans+=dfz(v,x,now);
		return ans;
	}
	
	ll solve(int x,int fa,int now,int len){
		vec.clear();
		dfs(x,fa,now,len);
		ll ans=0;
		for(int v:vec) ans+=dfz(x,fa,v);
		return ans;
	}
}

void calc(int x){
	subtask2::dfz(x,0);
	int rtx=A.nxt(1,s[x],0),rty=B.nxt(1,s[x],0);
	if(!rtx&&!rty){
		vis[x]=1;
		return ;
	}
	ans+=subtask2::solve(x,1,1,0);
	vis[x]=1;
	for(int v:e[x])
		if(!vis[v]){
			if(siz[v]<=Bc) ans-=subtask3::solve(v,x,rtx,1);
			else ans-=subtask2::solve(v,rtx,rty,1);
		}
}

void sol(int x){
	ll pre=ans;
	if(tot<=Bc){
		ans+=subtask1::dfs(x,0);
		vis[x]=1;
		return ;
	}
	else calc(x);
	for(int v:e[x])
		if(!vis[v]){
			tot=siz[v];rt=0;mx=n;
			dfz(v,x); sol(rt);
		}
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();m=read();
	Bc=sqrt(n);
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		e[u].push_back(v);
		e[v].push_back(u);
	}
	scanf("%s",s+1);
	init();
	tot=n;rt=0;mx=n;
	dfz(1,0);sol(rt);
	printf("%lld",ans);
	return 0;
}
posted @ 2025-07-04 15:33  programmingysx  阅读(37)  评论(0)    收藏  举报
Title