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;
}

浙公网安备 33010602011771号