Codeforces Round #797 (Div. 3) F--G

F. Shifting String

题意:

给定一个长度为n的字符串及一个等长度的排列组合p[],在进行一次操纵中位置i上字符用str[p[i]]替换。求最少经历多少次才能重新得到原串。

思路:

可知会得到若干置换环。那么必存在周期,且形成的若干环相互独立,那么ans = 整体环的最小周期lcm。
题目范围可以暴力求环的周期。
但是本题可同通过kmp,O(n)获得环的最小周期。
temp = len - ne[len]:最小循环长度
KMP其中一个推论。
if(len % temp == 0 && ne[len]) 说明原串为周期串,可完全由若干个temp组成。

那么对于本题,若本身为周期串,那么ans = temp,否则,ans = len。

code

char str[maxn];
int p[maxn], ne[maxn];
bool book[maxn];
ll gcd(ll a, ll b)
{
	return b ? gcd(b, a % b) : a;
}
ll lcm(ll a, ll b)
{
	return a / gcd(a, b) * b;
}
int cal(string temp)
{
	int len = temp.size();
	for(int i = 2, j = 0 ; i <= len ; i ++)
	{
		while(j && temp[i] != temp[j + 1]) j = ne[j];
		if(temp[i] == temp[j + 1]) j ++;
		ne[i] = j;
	}
	int ans = 0;
	if((len - 1) % (len - 1 - ne[len - 1]) == 0 && ne[len - 1])
	ans = len - 1 - ne[len - 1];
	else ans = len - 1;
	for(int i = 1 ; i <= len ; i ++)
	ne[i] = 0;
	return ans;
}
int main()
{
	int T;
	scanf("%d", &T);
	while(T --)
	{
		int n;
		scanf("%d", &n);
		scanf("%s", str + 1);
		for(int i = 1 ; i <= n ; i ++)
		{
			scanf("%d", &p[i]);
			book[i] = 0;
		}	
		ll res = 1;
		for(int i = 1 ; i <= n ; i ++)
		{
			if(!book[i])
			{
				string temp; // a b
				temp = "*";
				int cur = p[i];//
				while(!book[cur])
				{
					temp += str[cur];
					book[cur] = 1; 
  					cur = p[cur]; 
				}
				int len = temp.size();
				res = lcm(res, cal(temp) * 1ll);
			}
		}
		printf("%lld\n",res);
	}
	return 0;
}

G. Count the Trains

题意:

给定n个物品,向左运动,给定权值a[i]。如果a[i + 1] >= a[i],那么i和i+1可看成一段。给定m个操纵,
对下标为k的物品权值-=d。对于每个操纵输出此时的物品形成的段数。

思路:

类似区间合并。但是可以发现,两段合并,有可能右边整体合并到左边,有可能右边中部分合并到左边,有可能右边和左边不合并。
可以发现,正常的线段树无法维护这一过程。可以采取线段树上二分。
tre[],属性:维护一个整体最小值,及当前节点对应区间形成的段数。
在两个节点合并时。

void pushup(int rt)
{
	tre[rt].minn = min(tre[rt * 2].minn, tre[rt * 2 + 1].minn);
	tre[rt].cnt = tre[rt * 2].cnt + query(rt * 2 + 1, tre[rt * 2].minn);
}

左子树中形成的段数 + 右子树合并到当前左端点的贡献。需要另外调用query。(实际上就是线段树上二分)

int query(int rt, int w)
{
	if(tre[rt].minn >= w) return 0;
	if(tre[rt].l == tre[rt].r) return 1;
	if(tre[rt * 2].minn >= w) return query(rt * 2 + 1, w); //内部也能合并 
	return query(rt * 2, w) + tre[rt].cnt - tre[rt * 2].cnt;
}

可知执行最后一个return,左子树中可形成新的段,就要单独去找左子树形成的新段,左子树中部分可形成新的段,那么此时右子树对当前节点的左端点的贡献为tre[rt].cnt - tre[rt * 2].cnt(当前右子树对整体的贡献)

code

struct note{
	int l;
	int r;
	int minn;
	int cnt;
	int mid()
	{
		return (l + r) >> 1;
	}
}tre[maxn << 2];
int a[maxn];
int query(int rt, int w)
{
	if(tre[rt].minn >= w) return 0;
	if(tre[rt].l == tre[rt].r) return 1;
	if(tre[rt * 2].minn >= w) return query(rt * 2 + 1, w); //内部也能合并 
	return query(rt * 2, w) + tre[rt].cnt - tre[rt * 2].cnt;
}
void pushup(int rt)
{
	tre[rt].minn = min(tre[rt * 2].minn, tre[rt * 2 + 1].minn);
	tre[rt].cnt = tre[rt * 2].cnt + query(rt * 2 + 1, tre[rt * 2].minn);
}
void build(int rt, int l, int r)
{
	tre[rt] = {l, r};
	if(tre[rt].l == tre[rt].r)
	{
		tre[rt].minn = a[tre[rt].l];
		tre[rt].cnt = 1;
		return ;
	}
	int mid = tre[rt].mid();
	build(rt * 2, l, mid), build(rt * 2 + 1, mid + 1, r);
	pushup(rt);
}
void modify(int rt, int l, int w)
{
	if(tre[rt].l == tre[rt].r && tre[rt].l == l)
	{
		tre[rt].minn = tre[rt].minn - w;
		return ;
	}
	int mid = tre[rt].mid();
	if(l > mid) modify(rt * 2 + 1, l, w);
	else modify(rt * 2, l, w);
	pushup(rt);
}
int main()
{
	int T;
	scanf("%d", &T);
	while(T --)
	{
		int n, m;
		scanf("%d %d", &n, &m);
		for(int i = 1 ; i <= n ; i ++)
		scanf("%d", &a[i]);
		build(1, 1, n);
		vector <int> alls;
		for(int i = 1 ; i <= m ; i ++)
		{
			int k, d;
			scanf("%d %d", &k, &d);
			modify(1, k, d);
			alls.push_back(tre[1].cnt);
		} 
		for(auto t : alls)
		printf("%d ", t);
		printf("\n");
	}
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值