图论专项测试2
T1 收藏家
转化模型,每个人手上有一个物品,并且第 \(i\) 个人一次至多持有 \(a_i\) 个物品,每次操作一个人可以把一个物品给另一个人,问最后第1个人至多有多少个物品。
存在时间问题,可以建分层图跑网络流。源点向每个人初始的点连 \(1\) 的边, \(1\) 最终的边向汇点连 \(a_1\) 的边,中间每一次操作就在两人对应时间的点上双向连 \(1\) 边。
这样很多点都是没用的,只保留有用的点即可。
\(code:\)
T1
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
int read(){
int x=0,f=0; char ch=getchar();
while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) putchar('-'), x=-x;
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
} using namespace IO;
const int NN=10010,MM=1000010;
int t,n,m,tot,a[NN],x[NN],y[NN],pre[NN];
namespace Network_Flows{
int S,T,ql,qr,flow,q[NN],idx=1;
int c[MM],to[MM],nex[MM],thd[NN],dis[NN],head[NN];
void add(int a,int b,int x){
to[++idx]=b; nex[idx]=head[a]; head[a]=idx; c[idx]=x;
to[++idx]=a; nex[idx]=head[b]; head[b]=idx; c[idx]=0;
}
bool bfs(){
memset(dis,0x3f,sizeof(dis));
memcpy(head,thd,sizeof(thd));
dis[S]=0; q[ql=qr=1]=S;
while(ql<=qr){
int u=q[ql++];
for(int v,i=head[u];i;i=nex[i]) if(c[i])
if(dis[v=to[i]]>dis[u]+1){
dis[v]=dis[u]+1;
q[++qr]=v;
}
if(u==T) return 1;
}
return 0;
}
int dfs(int u,int in){
if(u==T) return in;
int rest=in,go;
for(int v,i=head[u];i;head[u]=i=nex[i]) if(c[i]){
if(dis[v=to[i]]==dis[u]+1){
go=dfs(v,min(rest,c[i]));
if(go) c[i]-=go, c[i^1]+=go, rest-=go;
else dis[v]=0;
}
if(!rest) break;
}
return in-rest;
}
void dinic(){
memcpy(thd,head,sizeof(head));
while(bfs()) flow+=dfs(S,INT_MAX);
}
} using namespace Network_Flows;
signed main(){
t=read();
while(t--){
tot=flow=0; idx=1;
memset(pre,0,sizeof(pre));
memset(head,0,sizeof(head));
n=read(); m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=m;i++) x[i]=read(), y[i]=read();
T=++tot; S=++tot;
for(int i=1;i<=m;i++){
if(pre[x[i]]) add(pre[x[i]],tot+1,a[x[i]]);
else add(S,tot+1,1);
pre[x[i]]=++tot;
if(pre[y[i]]) add(pre[y[i]],tot+1,a[y[i]]);
else add(S,tot+1,1);
pre[y[i]]=++tot;
add(pre[x[i]],pre[y[i]],1);
add(pre[y[i]],pre[x[i]],1);
}
add(pre[1],T,a[1]);
dinic();
write(flow,'\n');
}
return 0;
}
T2 旅行
可以认为每个点的决策是独立的,只有当一个点的所有出点都采取最优决策时,这个点才能采取最优决策。于是考虑单独一点如何求出最优解。设 \(u\) 点最优期望为 \(f_u\),
显然的转移方程:
这里 \(v\) 可能因为重边被算多次。
删去一条边,会同时影响分子分母,不好直接算,可以分数规划,二分来算。
考虑到限制,不能直接贪心,可以网络流最大权闭合子图做。
\(code:\)
T2
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
typedef double DB;
#define x first
#define y second
int read(){
int x=0,f=0; char ch=getchar();
while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) putchar('-'), x=-x;
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
} using namespace IO;
const int NN=1010,NM=2010,MM=20010;
int n,m,k;
vector<pair<int,int>>lim[NN];
DB f[NN];
namespace Net{
int S,T,ql,qr,q[NM],idx=1;
int to[MM],nex[MM],dis[NM],thd[NM],head[NM];
DB c[MM];
void clear(){
idx=1; S=m+1; T=S+1;
memset(head,0,sizeof(head));
}
void add(int a,int b,DB d){
to[++idx]=b; nex[idx]=head[a]; head[a]=idx; c[idx]=d;
to[++idx]=a; nex[idx]=head[b]; head[b]=idx; c[idx]=0;
}
bool bfs(){
memset(dis,0x3f,sizeof(dis));
memcpy(head,thd,sizeof(thd));
dis[S]=0; q[ql=qr=1]=S;
while(ql<=qr){
int u=q[ql++];
for(int v,i=head[u];i;i=nex[i]) if(c[i])
if(dis[v=to[i]]>dis[u]+1){
dis[v]=dis[u]+1;
q[++qr]=v;
}
if(u==T) return 1;
}
return 0;
}
DB dfs(int u,DB in){
if(u==T) return in;
DB rest=in,go;
for(int v,i=head[u];i;head[u]=i=nex[i]) if(c[i]){
if(dis[v=to[i]]==dis[u]+1){
go=dfs(v,min(rest,c[i]));
if(go) c[i]-=go, c[i^1]+=go, rest-=go;
else dis[v]=0;
}
if(!rest) break;
}
return in-rest;
}
DB flow(DB res=0){
memcpy(thd,head,sizeof(head));
while(bfs()) res+=dfs(S,INT_MAX);
return res;
}
} using Net::S; using Net::T;
namespace Graph{
int idx,st[MM],to[MM],nex[MM],head[NN];
bool vis[NN];
void add(int a,int b,int i){
to[++idx]=b; nex[idx]=head[a];
head[a]=idx; st[i]=a;
}
bool check(int u,DB mid){
Net::clear();
DB sum=0;
for(int i=head[u];i;i=nex[i])
if(f[to[i]]-mid>1e-5) Net::add(S,i,f[to[i]]-mid) ,sum+=f[to[i]]-mid;
else if(f[to[i]]-mid<1e-5) Net::add(i,T,mid-f[to[i]]);
for(auto x:lim[u])
Net::add(x.first,x.second,INT_MAX);
return sum-Net::flow()>1e-5;
}
void calc(int u){
DB l=0,r=m;
while(r-l>1e-5){
DB mid=(r+l)/2.0;
if(check(u,mid)) f[u]=mid,l=mid;
else r=mid;
}
f[u]+=1;
}
void dfs(int u){
vis[u]=1;
for(int i=head[u];i;i=nex[i])
if(!vis[to[i]]) dfs(to[i]);
if(head[u]) calc(u);
}
} using Graph::st;
signed main(){
n=read(); m=read(); k=read();
for(int u,v,i=1;i<=m;i++)
u=read(),v=read(), Graph::add(u,v,i);
for(int u,v,i=1;i<=k;i++)
u=read(),v=read(),lim[st[u]].push_back({u,v});
Graph::dfs(1);
printf("%.5lf\n",f[1]);
return 0;
}
T3 字符串
\(SAM\) + \(LCT\) + 主席树。
一种暴力的做法是动态建 \(SAM\) ,维护每个节点 \(endpos\) 中最大的位置,记为 \(lst\) 。每次插入字符时用 \(min(len,pos-l+1)\) 更新链上代表的区间答案,之后暴跳 \(link\) 修改祖先链。
对于 \(SAM\) 中的 \(parent\;tree\) ,实际上有三种操作:
- 给一个点加一个叶子节点
- 在一对父子节点间插入一个点
- 修改一条直链
第三个操作很像 \(access\) 。实际上,这三种操作都能通过 \(LCT\) 实现。
对第一种操作,直接连虚边即可。第二种操作需分类讨论父亲与儿子是否在同一重链( \(splay\) ) 上。具体地,先 \(splay(fa)\) , \(splay(son)\) 判断所属关系,如果不在同一重链上,就让克隆节点与父亲连虚边,儿子节点成为克隆节点的右儿子。如果在同一重链上,就直接在中间加个节点。如果不清楚可以看土哥NB带图讲解。第三种操作可以在 \(access\) 后打标记修改。
对于查询答案,取 \(max\) 需分类讨论拆开 \(min\) ,需要支持常数取 \(max\) 和等差数列取 \(max\) ,时空都不太优(主要是不会),因此可以换一种思路,考虑二分答案。
在 \(access\) 时,直接在主席树上这条重链的 \(lst\) 处(一条重链 \(lst\) 一定相同)与链上最长 \(len\) 取 \(max\) ,查询时二分答案,若第 \(r\)棵主席树上 \([l=mid-1,r]\) 区间内最大值不小于 \(mid\) ,则说明最终答案大于等于 \(mid\) 。
\(code:\)
T3
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
typedef double DB;
int read(){
int x=0,f=0; char ch=getchar();
while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) putchar('-'), x=-x;
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=800010;
int n,m,ans;
char c,s[NN];
namespace Pers_SegmentTree{
const int TN=NN<<5;
int tot,ext,root[NN],lc[TN],rc[TN],mx[TN];
int clone(int v){
int u=++tot;
lc[u]=lc[v]; rc[u]=rc[v]; mx[u]=mx[v];
return u;
}
void update(int& rt,int pos,int val,int l=1,int r=ext){
rt=clone(rt); ckmax(mx[rt],val);
if(l==r) return;
int mid=l+r>>1;
if(pos<=mid) update(lc[rt],pos,val,l,mid);
else update(rc[rt],pos,val,mid+1,r);
}
int query(int rt,int opl,int opr,int l=1,int r=ext){
if(opl>opr) return 0;
if(l>=opl&&r<=opr) return mx[rt];
int mid=l+r>>1,res=0;
if(opl<=mid) ckmax(res,query(lc[rt],opl,opr,l,mid));
if(opr>mid) ckmax(res,query(rc[rt],opl,opr,mid+1,r));
return res;
}
} using namespace Pers_SegmentTree;
namespace LinkCut_Tree{
int fa[NN],val[NN],mxv[NN],lst[NN],tag[NN],son[NN][2];
bool get(int x){ return x==son[fa[x]][1]; }
bool isroot(int x){ return x!=son[fa[x]][0]&&x!=son[fa[x]][1]; }
void pushup(int x){ mxv[x]=max({mxv[son[x][0]],mxv[son[x][1]],val[x]}); }
void cov(int x,int v){ lst[x]=tag[x]=v; }
void pushdown(int x){ if(tag[x]) cov(son[x][0],tag[x]), cov(son[x][1],tag[x]); tag[x]=0; }
void pushall(int x){ if(!isroot(x)) pushall(fa[x]); pushdown(x); }
void rotate(int x){
int y=fa[x],z=fa[y],xpos=get(x),ypos=get(y);
son[y][xpos]=son[x][xpos^1];
if(son[x][xpos^1]) fa[son[x][xpos^1]]=y;
if(!isroot(y)) son[z][ypos]=x; fa[x]=z;
son[x][xpos^1]=y; fa[y]=x;
pushup(y); pushup(x);
}
void splay(int x){
pushall(x);
while(!isroot(x)){
int y=fa[x];
if(!isroot(y))
get(x)^get(y)?rotate(x):rotate(y);
rotate(x);
}
}
void insert(int f,int s,int id,int len){
splay(f); splay(s);
val[id]=len; lst[id]=lst[s];
if(!isroot(f)){
son[s][0]=id; son[id][0]=f;
fa[id]=s; fa[f]=id;
pushup(id); pushup(s);
} else{
fa[id]=f; son[id][1]=s;
fa[s]=id;
pushup(s); pushup(id);
}
}
void access(int x,int id){
root[id]=root[id-1];
for(int y=0;x;x=fa[y=x]){
splay(x);
son[x][1]=0; pushup(x);
update(root[id],lst[x],mxv[x]);
son[x][1]=y; pushup(x);
}
splay(1); cov(1,id);
}
} using namespace LinkCut_Tree;
namespace Suffix_Automaton{
int oto,pre,len[NN],link[NN],to[NN][26];
int clone(int v,int l){
int u=++oto;
memcpy(to[u],to[v],sizeof(to[v]));
link[u]=link[v]; val[u]=len[u]=l;
return u;
}
void extend(int c,int l){
int p=pre,now=++oto;
val[now]=len[now]=len[pre]+1;
while(p&&!to[p][c])
to[p][c]=now, p=link[p];
if(!p) link[now]=fa[now]=1;
else{
int q=to[p][c];
if(len[q]==len[p]+1) link[now]=fa[now]=q;
else{
int cln=clone(q,len[p]+1);
insert(link[q],q,cln,len[p]+1);
while(p&&to[p][c]==q)
to[p][c]=cln, p=link[p];
link[q]=link[now]=fa[now]=cln;
}
}
pre=now; access(now,l);
}
} using namespace Suffix_Automaton;
int getans(int lp,int rp){
int l=0,r=rp-lp+1,res;
while(l<=r){
int mid=l+r>>1;
if(query(root[rp],lp+mid-1,rp)>=mid) res=mid,l=mid+1;
else r=mid-1;
}
return res;
}
signed main(){
scanf("%s",s+1); oto=pre=1;
n=strlen(s+1); m=read(); ext=n+m;
for(int i=1;i<=n;i++) extend(s[i]-'a',i);
while(m--){
int op=read();
if(op==1){ cin>>c; extend((c-'a'+ans)%26,++n); }
else{
int l=(read()+ans-1)%n+1,r=(read()+ans-1)%n+1;
if(l>r) swap(l,r);
write(ans=getans(l,r),'\n');
}
}
return 0;
}