牛客多校训练营2020第8场

F

题意:一些原料生产一些产品,给出反应方程、初始原料的量,求反应的瓶颈。
思路:看出是一个DAG图,对于每个反应,反应物向生成物连边,反应物的系数为边权,生成五系数为点权。f[i]表示从原料到i点的(边权/点权)的乘积。f[i] / i的点权/ i的初始量的最大值即为答案。

from fractions import Fraction as F

if __name__ == '__main__':
    n,m = [int(x.strip()) for x in input().split()]
    node_id = {}
    node = []
    node_cnt = []
    edge_v = [ [] for i in range(n) ]
    edge = [ [] for i in range(n) ]
    node_v = [F(1) for i in range(n)]
    vist = [0 for i in range(n)]
    
    for i in range(0, n):
        x,v = input().strip().split()
        node_id[x] = i
        node.append(x)
        node_cnt.append(int(v))
        
    for i in range(0, m):
        s = [x.strip() for x in input().split()]
        s = [x for x in s if x !='+' and x != '=']
        x = s[-1]
        x_cnt = F(s[-2])
        node_v[node_id[x]] = x_cnt
        for j in range(0, len(s) - 2, 2):
            y_cnt = F(s[j])
            y = s[j + 1]
            edge[node_id[x]].append(node_id[y])
            edge_v[node_id[x]].append(y_cnt)
            vist[node_id[y]] = vist[node_id[y]] + 1
            
    que =[]
    f = [F(0)]*n
    
    for i in range(n):
        f.append(0)
        
    for i in range(n):
        x = node[i]
        if vist[i] == 0:
            que.append(i)
            f[i] = F(1)
            
    while not que == []:
        x_id = que[-1]
        que.pop()
        for i in range(0, len(edge[x_id])):
            y_id = edge[x_id][i]
            f[y_id] = f[y_id] + f[x_id] * edge_v[x_id][i] / node_v[x_id]
            vist[y_id] = vist[y_id] - 1
            if vist[y_id] == 0:
                que.append(y_id)
                
    maxa = f[0] / node_cnt[0] /node_v[0]
    cnt = 0
    ans = []
    
    for i in range(n):
        if f[i] / node_cnt[i] / node_v[i] > maxa:
            maxa = f[i] / node_cnt[i] / node_v[i]
            
    for i in range(n):
        if f[i] / node_cnt[i] / node_v[i] == maxa:
            ans.append(node[i])
            cnt = cnt + 1

    ans = sorted([ans[x] for x in range(cnt)])
    print(cnt)
    print(*ans)

G

题意:每张牌有四个属性,每个属性有3个状态。如果三张牌满足以下条件,则可以构成一个合法集合:对于任意一种属性,要么三张牌的状态互不相同,要么完全相同。给出N( N ≤ 256 N \leq 256 N256)张牌,某些牌某写属性可能为”?“(万能状态,可以当成任意一种状态),求是否存在合法的集合。
思路
考场AC:
记录f [0 / 1 / 2 / 3 ] [0 / 1 / 2 / 3] [0 / 1 / 2 / 3] [0 / 1 / 2 / 3] 记录每种牌有多少,其中f[0][x][x][x]表示第一种属性任意的牌,即f[0][x][x][x] = f[1][x][x][x]+f[2][x][x][x]+f[3][x][x]。
枚举两张牌,选第三张牌。对于每一种属性,如果两张牌有至少一个万能状态,那么第三张牌任意;否则,如果一样,第三张牌为相同状态,如果不一样,第三张牌为第三种状态。这样就唯一确定了第三张牌的所有状态。(注意,包括”任意“状态)
处理第三张牌的集合:
记录f [0 / 1 / 2 / 3 ] [0 / 1 / 2 / 3] [0 / 1 / 2 / 3] [0 / 1 / 2 / 3] 记录每种牌有多少,其中f[0][x][x][x]表示第一种属性任意的牌,即f[0][x][x][x] = f[1][x][x][x]+f[2][x][x][x]+f[3][x][x]。
每张牌最多能满足256种状态,所以暴力更新f就可以。
时间复杂度 O( T N 2 TN^{2} TN2

出题人正解:
21张牌种必有一个合法集合……所以,O( 2 1 3 21^{3} 213)暴力枚举做就好了……

H

题意:给一些字符串,每个字符串无限循环,求所有无限循环字符串的公共子串的大小。如果公共子串无限循环,输出-1。
思路
对于每个给出的串,先求其本质串(用kmp去循环节),用最小表示法表示。
如果所有串变为一个,那么输出-1。
否则,将所有串倍长4倍,在sam上跑就好。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N = 3e6 + 10;
string str[N];
char s[N];
int ch[N][26],fa[N],len[N*8];
int last=1,tot=1;
void insert(int x)
{
	int p=last;
	int np=last=++tot;
	len[np]=len[p]+1;
	while(p&&!ch[p][x]) { ch[p][x]=np; p=fa[p]; }
	if(!p) fa[np]=1;
	else
	{
		int q=ch[p][x];
		if(len[q]==len[p]+1) fa[np]=q;
		else
		{
			int nq=++tot;
			for(int i=0;i<26;i++) ch[nq][i]=ch[q][i];
			fa[nq]=fa[q]; fa[np]=fa[q]=nq;
			len[nq]=len[p]+1;
			while(p&&ch[p][x]==q)
			{ ch[p][x]=nq; p=fa[p]; }
		}
	}
}

int fail[N];
int get_fail()
{
	int l = strlen(s + 1);
	fail[1] = 0;
	int j = 0;
	for(int i = 2; i <= l; i++)
	{
		fail[i] = 0;
		while(j && s[j + 1] != s[i])
			j = fail[j];
		if(s[j + 1] == s[i])
			fail[i] = ++j;
	}
	if(l % (l - fail[l]))	return l;
	return l - fail[l];
 } 
int get_min(int l)
{
	int  i = 0, j = 1, k = 0;
	while(i < l && j < l && k < l)
	{
		if(s[(i + k) % l + 1] == s[(j + k) % l + 1])
			k++;
		else
		{
			if(s[(i + k) % l + 1] > s[(j + k) % l + 1])
				i = i + k + 1;
			else
				j = j + k + 1;
			if(i == j)
				i++;
			k = 0;
		 } 
	}
	return min(i + 1, j + 1);
 } 
 
int mx[N], mi[N], r[N];
void solve(int id)
{
	for(int i = 1; i <=tot; i++)
		mx[i] = 0;
	int l = str[id].size();
	int p=1,now=0;
	for(int i = 0; i < l; i++)
	{
		int x = str[id][i] - 'a';
		if(ch[p][x])
		{ now++; p = ch[p][x];}
		else
		{
			while(p&&!ch[p][x])	p = fa[p];
			if(!p){p = 1;now = 0;}
			else
			{ now = len[p] + 1; p = ch[p][x];}
		}
		mx[p] = max(mx[p], now);
	}
	for(int i = 1; i <= tot; i++)
	{
		int x = r[i];
		mx[fa[x]] = max(mx[fa[x]], mx[x]);
		mi[x] = min(mi[x], mx[x]);
	}
}
int cmp (int x, int y)
{
	return len[x] > len[y];
}
int main()
{
	int n, l, p;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
	{
		scanf("%s", s + 1);
		l = get_fail();
		p = get_min(l);
		for(int j = p; j <= l; j++)
			str[i] += s[j];
		for(int j = 1; j < p; j++)
			str[i]+= s[j];  
	}
	sort(str + 1, str + n + 1);
	int nn = unique(str + 1, str + n +1) - str - 1;
	if(nn == 1)
	{
		printf("-1\n");
		return 0;
	 } 
	n = nn;
	int minlen = min(str[1].size(), str[2].size());
	int maxlen = max(str[1].size(), str[2].size());
	if(str[1].size() == minlen) p = 1;
	else p = 2; 
	for(int i = 3; i <= n; i++)
	{
		l = str[i].size();
		maxlen = min(maxlen, l);
		if(maxlen < minlen)	swap(maxlen, minlen);
		if(l == minlen)	p = i;
	}
	string sstr = str[p];
	while(str[p].size() < 4 * maxlen)
		str[p] += sstr;
	
	minlen = str[p].size();
	for(int i = 1; i <= n; i++)
		if(i != p)
		{
			str[i]+=str[i];
			str[i]+=str[i];
			if(str[i].size() < minlen)
			{
				minlen = str[i].size();
				p = i;
			}
		}
		
	l = str[p].size();
	last = 1;
	for(int i = 0; i < l; i++)
		insert(str[p][i] - 'a');
	for(int i = 1; i <= tot; i++)
		r[i] = i, mi[i] = len[i];
	sort(r + 1, r + tot + 1, cmp);
	
	for(int i = 1; i <= n; i++)
		if(i != p)
			solve(i);
	
	LL ans = 0;
	for(int i = 2; i <= tot; i++)
		ans += max(0, mi[i] - len[fa[i]]);
	cout<<ans<<endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值