牛客暑期多校训练营2020第1场

本文精选了三道算法竞赛题目,分别涉及后缀数组、费用流和最大流的应用。通过详细解析题意和提供源代码,帮助读者理解并掌握这些高级算法的实际应用,提升算法竞赛水平。

传送门

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值