Codeforces Round #797 (Div. 3)F G

F

Polycarp 找到了字符串 s 和排列 p。结果证明它们的长度相同并且等于 n。

n 个元素的排列 — 是一个长度为 n 的数组,其中从 1 到 n 的每个整数恰好出现一次。例如,[1,2,3] 和 [4,3,5,1,2] 是排列,但是 [1,2,4], [4,3,2,1,2] 和 [0,1 ,2] 不是。

在一次操作中,他可以将 s 与 p 相乘,因此他将 s 替换为字符串 new,其中对于从 1 到 n 的任何 i,newi=spi 是真的。例如,当 s=wmbe 和 p=[3,1,4,2] 时,操作后字符串会变成 s=s3s1s4s2=bwem。

Polycarp 想知道在经过多少次操作后,该字符串将首次等于其初始值。由于可能需要很长时间,他在这件事上请求您的帮助。

可以证明所需的操作次数总是存在的。它可能非常大,因此请使用 64 位整数类型。

输入
输入的第一行包含一个整数 t (1≤t≤5000) — 输入中的测试用例数。

每个案例的第一行包含单个整数 n (1≤n≤200) — 字符串的长度和排列。

每个 case 的第二行包含一个长度为 n 的字符串 s,其中包含小写拉丁字母。

每个case的第三行包含n个整数——排列p(1≤pi≤n),所有的pi都是不同的。

输出
输出 t 行,每行包含输入对应测试用例的答案。作为答案输出单个整数 - 最小操作次数,之后字符串 s 将变得与操作之前相同。

思路:

这个问题其实可以转换成存在n个循环节,有一个最少步数可以使得所有的循环节回归原位。那么,我们的方法当然是先把所有的循环节处理出来,然后我们会发现符合经过多少次会重回原位的次数和kmp匹配时候的次数是有一定关系的,有多少个字符匹配上了,假如最后一位被k个字符匹配上了,那么我们会发现n-k如果可以被n整除,那么也就是说明这个是可以在n-k次之后就复归原位的。为什么呢,有如下kmp性质

假设 s 长度为 n,如果存在循环节,则其长度为 L=n−ne[n],另外有如下两条性质:

  • 如果 n 可以被 n−ne[n] 整除,则 s 存在循环节,周期 T=n/L
  • 否则,还需添加字母补全,需要补充的字母个数为 L−n%L=L−ne[n]%L,其中 L=n−ne[n](性质归纳转载至acwing_zyy

不过目前还不知道怎么证明。代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[205];
char s1[205];
int b[205];
vector<int> v[205];
// C++ Version
vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++) {
    int j = pi[i - 1];
    while (j > 0 && s[i] != s[j]) j = pi[j - 1];
    if (s[i] == s[j]) j++;
    pi[i] = j;
  }
  return pi;
}
int c[205];
signed main(){
	int t;
	cin>>t;
	while(t--){
		int n;
		scanf("%lld",&n);
		memset(a,0,sizeof a);
		scanf("%s",s1+1);
		for(int i=1;i<=n;i++){
			scanf("%lld",&b[i]);
			v[i].clear();
		}
		for(int i=1;i<=n;i++){
			if(a[i]!=1){
				int p=b[i];
				v[i].push_back(i);
				a[i]=1;
				while(p!=i){
					a[p]=1;
					v[i].push_back(p);
					p=b[p];
				}
			}
		}
		for(int i=1;i<=n;i++){
			string s="";
			if(v[i].size()==0)
				continue;
			for(int j=0;j<v[i].size();j++)
				s+=s1[v[i][j]];
			vector<int> u=prefix_function(s);
		//	cout<<s<<" ";
			c[i]=s.size()-u[s.size()-1];
		//	cout<<c[i]<<endl;
			if(s.size()%c[i]!=0)
				c[i]=s.size();
		}
		int res=1;
		for(int i=1;i<=n;i++)
			if(v[i].size())
				res=res/__gcd(c[i],res)*c[i];
		cout<<res<<"\n";	
	}
}

G

轨道上有n个独立的车厢。车厢从左到右从 1 到 n 编号。车厢不相互连接。车厢向左移动,因此编号为 1 的车厢在所有车厢前面移动。

第 i 个车厢有自己的引擎,可以将车厢加速到 ai km/h,但车厢的速度不能超过前面的车厢。请参阅示例进行解释。

所有的车厢同时开始向左移动,它们自然形成了火车。我们称火车——具有相同速度的连续移动的车厢。

例如,我们有 n=5 个车厢,数组 a=[10,13,5,2,6]。那么车厢的最终速度将为 [10,10,5,2,2]。将分别形成3列火车。

还有消息说某些引擎已损坏:

消息“k d”表示第k个车厢的速度降低了d(即,车厢的最大速度发生了变化ak=ak-d)。
消息按顺序到达,下一条消息的处理会考虑所有先前消息的变化。

在每条消息之后确定形成的火车的数量。

输入
输入数据的第一行包含一个整数 t (1≤t≤104)——输入测试用例的数量。

接下来是测试用例的描述。

每个测试用例的第一行是空的。

测试用例的第二行包含两个整数 n 和 m (1≤n,m≤105)——分别是车厢的数量和减慢车厢速度的消息数量。

第三行包含 n 个整数:a1,a2,…,an (0≤ai≤109) — 数字 ai 表示编号为 i 的马车可以达到 ai km/h 的速度。

接下来的 m 行包含两个整数 kj 和 dj (1≤kj≤n, 0≤dj≤akj)——这是数字 kj 的车厢速度降低了 dj 的消息。换句话说,它的最大速度 akj=akj-dj 发生了变化。请注意,任何时候每个托架的速度都是非负的。换句话说,ai≥si,其中 si — 是这样的 dj 的总和,即 kj=i。

保证所有测试用例的n之和不超过105。同样,保证所有测试用例的m之和不超过105。

输出
打印 t 行。在每一行打印相应测试用例的答案。

对于每个测试用例,打印 m 个数字:每条消息后形成的火车数量。

思路:

对于这题,我们的思考是,如果插入一个数,该数字只会受到前面的影响或者不受前面的影响却可以影响后面这两种结果。那么我们的思考是,我们每次如果要修改一次值的时候,我们只需要考虑两种,第一种是我修改完的值有没有比我目前在的这一段的值小,有的话这个就要裂开,然后一段段的去后面进行合并区间。没有的话我们就不需要裂开,继续保持原样。那么合并区间又要怎么合并呢,我们根本不需要去管r,我们只需要管两个属性l,也就是这一段的起始坐标,val,也就是这一段的值。我们可以每次修改一个数字,就只需要将这个数字的起始坐标独立出来,然后按照val的值的大小选择不独立,被前面区间继续合并掉,也就是直接删除这个当前坐标点。还是向后合并,删除后面值大于这个值的坐标点。

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
struct node{
	int l;
	int val;
	bool operator<(const node &s1) const{
		if(s1.l==l)
			return val<s1.val;//这样就不用发愁安插下去的值会跑到前面来了 
		return l<s1.l;	
	}
}a[100005];
int n,m;
set<node> s;
void add(int l){
//	cout<<l<<" "<<a[l].val<<endl;
	s.insert({l,a[l].val});//相当于把这个数字独立出来了,已经拆分开了。 
	auto it=s.find({l,a[l].val});
	if(it!=s.begin()&&a[l].val>=(prev(it)->val)){
		s.erase(it);
		return;
	}
	while(next(it)!=s.end()&&a[l].val<=(next(it)->val))
		s.erase(next(it));//如果说下面的安插了还没更小的话,删除下面那个数字 
	return;
}
signed main(){
	ios::sync_with_stdio(false);
	int t;
	cin>>t;
	while(t--){
		cin>>n>>m;
		for(int i=1;i<=n;i++){
			a[i].l=i;//表示当前这个区间的开头在哪 
			cin>>a[i].val;
		}	
		s.clear();
		for(int i=1;i<=n;i++)
			add(i);
		while(m--){
			int x,y;
			cin>>x>>y;
			a[x].val-=y;
			add(x);
			printf("%d ",s.size());
		}
	    printf("\n");
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值