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;
}