A B-Suffix Array
题意:
给一个长度为N的字符串S(N
≤
\le
≤ 1e5),定义B(c1, c2, ……, ck) = b1b2……bk,其中bi是ci与ci前面第一个与之相同的字符的位置差。对S的每一个后缀,按照B(ci,……, cn)排序。S中只有a、b两种字符。
思路:
首先对S求B(S),对于S的任意一个后缀T,B(T)的后面一部分与B(S)是相同的,那么是从哪一位开始相同的呢?事实上B(T)从前面第一个a和第一个b出现的位置中最大那位以后的串就是和B(S)相同的了,也就是说如果前面既有了a又有了b,那么后面就和原串的值相同了。
这样我们就可以把B(T)分成两部分考虑,按照两部分的大小进行排序。后一部分是和B(S)相同的部分,求出B(S)的rank就可以了。前一部分是0、00、010、01110这样的形式,即有两个0,中间夹着一些1,那么直接按照长度排序就可以了(注意0111这种只有一个0的情况我们将它看做01110)。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 2e5 + 10;
struct NODE{
int x, y, id;
bool operator < (const NODE & other)
const { return x < other.x || (x == other.x && y <= other.y);}
}h[N];
int x[N], y[N], sa[N], c[N], ss[N];
char s[N];
void get_sa(int n,int m)
{
for(int i = 1; i <= n; i++) x[i] = ss[i];
for(int i = 0; i <= m; i++) c[i] = 0;
for(int i = 1; i <= n; i++) c[x[i]]++;
for(int i = 1; i <= m; i++) c[i] +=c[i - 1];
for(int i = n; i > 0; i--) sa[c[x[i]]--] = i;
for(int i = 1; i <= n; i++) y[i] = x[i];
m = 1;
for(int i = 1; i <= n; i++)
{
if(y[sa[i]] != y[sa[i - 1]]) m++;
x[sa[i]] = m;
}
for(int k = 1; k < n; k <<= 1)
{
int num = 0;
for(int i = n - k + 1; i <= n; i++) y[++num] = i;
for(int i = 1; i <= n; i++)
if(sa[i] > k)
y[++num] = sa[i] - k;
for(int i = 0; i <= m; i++) c[i] = 0;
for(int i = 1; i <= n; i++) c[x[y[i]]]++;
for(int i = 1; i <= m; i++) c[i] += c[i - 1];
for(int i = n; i > 0; i--) sa[c[x[y[i]]]--] = y[i];
for(int i = 1; i <= n; i++) y[i] = x[i];
m = 0;
for(int i = 1; i <= n; i++)
{
if(sa[i] + k > n || sa[i - 1] + k > n || y[sa[i]] != y[sa[i - 1]] || y[sa[i] + k] != y[sa[i - 1] + k])
m++;
x[sa[i]] = m;
}
}
}
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
scanf("%s", s + 1);
int prea = 0;
int preb = 0;
for(int i = 1; i <= n; i++)
{
if(s[i] == 'a')
{
if(prea) ss[i] = i - prea;
prea = i;
}
else
{
if(preb) ss[i] = i - preb;
preb = i;
}
}
get_sa(n,n);
prea = 0;
preb = 0;
x[n + 1] = 0; //注意这里
x[n + 2] = -1;
for(int i = n; i > 0; i--)
{
h[i].id = i;
if(s[i] == 'a')
{
if(preb)
{
h[i].x = preb - i + 1;
h[i].y = x[preb + 1];
}
else
{
h[i].x = n - i + 2;
h[i].y = x[n + 2];
}
prea = i;
}
else
{
if(prea)
{
h[i].x = prea - i + 1;
h[i].y = x[prea + 1];
}
else
{
h[i].x = n - i + 2;
h[i].y = x[n + 2];
}
preb = i;
}
}
sort(h + 1, h + n + 1);
for(int i = 1; i <= n; i++)
printf("%d ", h[i].id);
printf("\n");
}
return 0;
}
H
题意:
给一张带权有向图,询问:当每条边容量不多于ui/vi时,求从1到n流过单位流量花费的最小代价。
思路:费用流逐条添加增广路
求出有i条增广路所需花费的最小代价mn[i],则所需最小费用为mn[ui / vi] * ui / vi + mn[ui / vi+1] * (1 - ui / vi)
#define LL long long
using namespace std;
const int inf=1e9+10;
const int M=110;
const int N=60;
struct EDGD{
int u,v,re,c,to,nxt;
}edge[M*2];
int t[N],vist[N],dist[N],pre[N],pree[N],que[N*M*2];
LL mn[M];
int k,n,cost,cnt,m;
void memclear()
{
for(int i=1;i<=n;i++)
{ t[i]=0; }
k=0; cost=0; cnt=0;
}
void addedge(int x,int y,int c)
{
edge[++k].to=y; edge[k].nxt=t[x]; t[x]=k;
edge[k].v=1; edge[k].u=0; edge[k].c=c;
edge[k].re=k+1;
edge[++k].to=x; edge[k].nxt=t[y]; t[y]=k;
edge[k].v=0; edge[k].u=0; edge[k].c=-c;
edge[k].re=k-1;
}
LL gcd(LL x,LL y)
{
if(x<y) swap(x,y);
if(y==0)
{
if(x==0) x=1;
return x;
}
return gcd(y,x%y);
}
int spfa()
{
for(int i=2;i<=n;i++) dist[i]=inf;
dist[1]=0; que[1]=1; vist[1]=1;
int head=0,tail=1;
while(head<tail)
{
head++;
int x=que[head];
int p=t[x];
while(p)
{
int y=edge[p].to;
if(edge[p].v>edge[p].u&&dist[y]>dist[x]+edge[p].c)
{
dist[y]=dist[x]+edge[p].c;
pree[y]=p; pre[y]=x;
if(vist[y]==0)
{
vist[y]=1;
que[++tail]=y;
}
}
p=edge[p].nxt;
}
vist[x]=0;
}
if(dist[n]<inf) return 1;
return 0;
}
void update()
{
int maxa=inf, x=n;
while(x!=1)
{
int p=pree[x];
maxa=min(maxa,edge[p].v-edge[p].u);
x=pre[x];
}
x=n;
while(x!=1)
{
int p=pree[x];
cost+=maxa*edge[p].c;
edge[p].u+=maxa;
edge[edge[p].re].u-=maxa;
x=pre[x];
}
}
void solve_pre()
{
cnt=0;
while(spfa())
{
update();
mn[++cnt]=cost;
}
}
void solve(LL u,LL v)
{
if(u==0)
{
printf("NaN\n");
return;
}
LL bs=v/u;
if(v%u) bs++;
if(bs>cnt)
{
printf("NaN\n");
return;
}
LL ansu=u*mn[bs-1]+(v-u*(bs-1))*(mn[bs]-mn[bs-1]);
LL pp=gcd(ansu,v);
printf("%lld/%lld\n",ansu/pp,v/pp);
}
int main()
{
int x,y,q;
LL u,v;
while(scanf("%d%d",&n,&m)!=EOF)
{
memclear();
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&v);
addedge(x,y,v);
}
solve_pre();
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%lld%lld",&u,&v);
solve(u,v);
}
}
return 0;
}
I
题意:
给一张无向图,判断是否可以选择一些边,使得每个点的度为给定的值(1或2)。
思路:最大流判断是否满流。
建边:
<s, x, d[i]> <x+n, e , d[i]>
<x, y+n, 1> <y, x+n, 1> (如果x,y之间有边)
using namespace std;
const int N=120;
const int M=210;
const int inf=1e9+10;
struct EDGE{
int to, nxt, v, re;
EDGE(){}
EDGE(int a,int b,int c,int d){ to=a; nxt=b; v=c; re=d;}
}edge[M*4+N*4];
int t[N*2], vist[N*2], que[N*2], d[N*2];
int tot, s, e;
void memclear(int n,int m)
{
s=0; e=n*2+1; tot=0;
for(int i=s;i<=e;i++)
t[i]=0;
}
void addedge(int x,int y,int v)
{
tot++;
edge[tot]=EDGE(y, t[x], v, tot+1);
t[x]=tot;
tot++;
edge[tot]=EDGE(x, t[y], 0, tot-1);
t[y]=tot;
}
int bfs()
{
for(int i=s;i<=e;i++)
vist[i]=0;
que[1]=s;
vist[s]=1;
int head=0,tail=1;
while(head<tail)
{
head++;
int x=que[head];
for(int p=t[x];p>0;p=edge[p].nxt)
{
int y=edge[p].to;
if(edge[p].v>0&&!vist[y])
{
vist[y]=vist[x]+1;
que[++tail]=y;
}
}
}
return vist[e];
}
int dfs(int x,int f)
{
if(x==e||!f) return f;
int sumf=0;
for(int p=t[x];p>0;p=edge[p].nxt)
{
int y=edge[p].to;
if(vist[y]==vist[x]+1&&edge[p].v>0)
{
int flow=dfs(y,min(f,edge[p].v));
edge[p].v-=flow;
edge[edge[p].re].v+=flow;
f-=flow;
sumf+=flow;
if(!f) break;
}
}
return sumf;
}
int dinic()
{
int ans=0;
while(bfs()) { ans+=dfs(s,inf); }
return ans;
}
int main()
{
int n,m,x,y;
while(scanf("%d%d",&n,&m)!=EOF)
{
memclear(n,m);
int sum=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&d[i]);
addedge(s, i, d[i]);
addedge(i+n, e, d[i]);
sum+=d[i];
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
addedge(x, y+n, 1);
addedge(y, x+n, 1);
}
if(dinic()==sum) printf("Yes\n");
else printf("No\n");
}
return 0;
}
本文精选了三道算法竞赛题目,分别涉及后缀数组、费用流和最大流的应用。通过详细解析题意和提供源代码,帮助读者理解并掌握这些高级算法的实际应用,提升算法竞赛水平。

2039

被折叠的 条评论
为什么被折叠?



