AtCoder Beginner Contest 324 E + F

E - Joint Two Strings

E

题意

N N N 个只包含小写字母的字符串 S 1 . . . S N S_1 ... S_N S1...SN,可以形成 N 2 N^2 N2 个有序对 ( i , j ) (i,j) (i,j),问有多少个字符串 S = S i + S j S = S_i + S_j S=Si+Sj 满足:

  • 字符串 T T T S S S子序列(非连续的字母)
思路

我们可以维护两个数组 A A A B B B ,其中:

  • A i A_i Ai 表示 S i S_i Si 包含的最长 T T T前缀字母个数
  • B i B_i Bi 表示 S i S_i Si 包含的最长 T T T后缀字母个数

那么对于 S = S i + S j S = S_i + S_j S=Si+Sj,只要 A i + B j ≥ ∣ T ∣ ( T 的长度 ) A_i + B_j \geq |T| \quad (T的长度) Ai+BjT(T的长度) 即可,这样子 T T T 就是 S S S 的子序列了。

所以我们可以枚举第 i i i 个字符串为前缀,那么后缀的 B j B_j Bj 只需要满足 B j ≥ ∣ T ∣ − A i B_j \geq |T| - A_i BjTAi 即可,符合条件的 j j j 的个数就是以 i i i 为前缀的答案。可以直接二层循环来寻找答案,这样子的时间复杂度是 O ( N ) O(N) O(N) 的,因为已经确定了前缀的长度,那么后缀的长度其实已经确定了一个区间范围,最后可以证明遍历的长度不会超过字符串总长度 ∑ i = 1 n ∣ S i ∣ ≤ 5 × 1 0 5 \sum_{i=1}^n |S_i| \leq 5\times10^5 i=1nSi5×105

时间复杂度为: ∑ i = 1 n ∑ j = ∣ T ∣ − A i ∣ T ∣ c n t [ j ] \sum_{i=1}^n \sum_{j=|T| -A_i}^{|T|} cnt[j] i=1nj=TAiTcnt[j] c n t [ j ] cnt[j] cnt[j] 是后缀长度为 j j j 的字符数量
= ∑ i = 1 n ( ∣ T ∣ − ( ∣ T ∣ − A i ) + 1 ) \hspace{50pt} = \sum_{i=1}^{n} (|T| - (|T| - A_i) + 1) =i=1n(T(TAi)+1)
= ∑ i = 1 n ( A i + 1 ) \hspace{50pt} = \sum_{i=1}^{n} (A_i+ 1) =i=1n(Ai+1)
= ∑ i = 1 n A i \hspace{50pt} = \sum_{i=1}^{n} A_i =i=1nAi
≤ 5 × 1 0 5 \hspace{50pt} \leq 5\times 10^5 5×105

AtCoder 的原证明

// Problem: E - Joint Two Strings
// Contest: AtCoder - Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324)
// URL: https://atcoder.jp/contests/abc324/tasks/abc324_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

const int N=500005;

std::string s[N];
int a[N];
int b[N];
ll cnt[N];

int cal(const std::string& s,const std::string& T){
	int idx = 0;
	for(auto c : s){
		if(c == T[idx])	++idx;
		if(idx == (int)T.size())	break;
	}
	return idx;
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int n;
    std::string T;
    std::cin>>n>>T;
    fore(i,1,n+1){
    	std::cin>>s[i];
    	a[i] = cal(s[i],T);
    }
    reverse(T.begin(),T.end());
    fore(i,1,n+1){
    	reverse(s[i].begin(),s[i].end());
    	b[i] = cal(s[i],T);
    }
    fore(i,1,n+1)	++cnt[b[i]];
    
	ll ans = 0;
    fore(i,1,n+1){ //枚举每一个s[i]来做前缀
    	int len = (int)T.size() - a[i]; //后缀最起码的长度
    	fore(j,len,T.size()+1)	ans += cnt[j];
    }
    std::cout<<ans;
	return 0; 
}

F - Beautiful Path

F

题意

有一张有向图有 N N N 个节点和 M M M 条边,每条边有 漂亮值 b i b_i bi花费 c i c_i ci
对于每一条边,都有 u i < v i u_i < v_i ui<vi,即:只有从小节点到大节点的边
问从 1 → N 1 \rightarrow N 1N 的路径中 ∑ b i ∑ c i \quad \dfrac {\sum b_i}{\sum c_i} cibi 最大 的值是多少?

思路

非常典型的 01 01 01 分数规划,可以设 ∑ b i ∑ c i ≥ x \quad \dfrac {\sum b_i}{\sum c_i} \geq x cibix,只要找到最大的 x x x 就是答案。移项得: ∑ b i − x ⋅ ∑ c i ≥ 0 \sum b_i - x \cdot \sum c_i \geq 0 bixci0
故我们可以把边权改为 b i − x ⋅ c i b_i - x \cdot c_i bixci,只需要跑一条最大路径即可。

由于答案的上界 2 × 1 0 9 2 \times 10^9 2×109 和下界精度 1 0 − 9 10^{-9} 109 之间差了大概 2 60 2^{60} 260 倍,因此二分次数设置成 70 70 70 次就差不多了。

如果用 D i j k s t r a Dijkstra Dijkstra 来跑最大路,时间复杂度是 70 ⋅ M ⋅ l o g N ≈ 2 × 1 0 8 70 \cdot M \cdot log \hspace{2pt}N \approx 2 \times 10^8 70MlogN2×108 T I L E TILE TILE

这里有个小 T i p s Tips Tips:

由于这张图的特殊性,只有从小点到大点的边,所以这是一个 D A G DAG DAG,那么跑最大路时,可以依次遍历所有的节点,并更新这个节点对后面的贡献。有点像拓扑排序,当前扫到的节点 i i i 前面的所有点到 i i i 的路径一定都被查看过了。时间复杂度就是: O ( N + M ) O(N+M) O(N+M)

正确的时间复杂度: O ( 70 × ( M + N ) ) O(70 \times (M + N)) O(70×(M+N))

// Problem: F - Beautiful Path
// Contest: AtCoder - Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324)
// URL: https://atcoder.jp/contests/abc324/tasks/abc324_f
// Memory Limit: 1024 MB
// Time Limit: 5000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

const int N=200005;
int n,m;
const double eps = 1e-15;

struct node{
	int to;
	double b;
	double c;
};
std::vector<node> g[N];

bool check(double x){
	std::vector<double> d(n+1,-1e20);
	d[1] = 0;
	fore(u,1,n)
		for(auto [v,b,c] : g[u])
			d[v] = std::max(d[v],d[u] + b - x*c);
	return d[n] >= 0;
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    std::cin>>n>>m;
    double l = 0 , r = 2e9;
    fore(i,0,m){
    	int u,v;
    	double b,c;
    	std::cin>>u>>v>>b>>c;
    	g[u].push_back({v,b,c});
    }
    double ans = 0;
    fore(i,0,70){
    	double mid = (l+r)/2.0;
    	if(check(mid)){
    		ans = mid;
    		l = mid;
    	}
    	else r = mid;
    }
    std::cout<<std::fixed<<std::setprecision(15)<<ans;
	return 0; 
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值