Codeforces 827E Rusty String

一万年没有写过题解了...这题idea是不错

题意

给你一个只由V/K/?组成的字符串,问这个字符串的循环长度可以是多少,其中表示既可以是V也可以是K。将所有可能的循环长度都输出。

想法

如果没有?,那就是next数组判断。而之所以可以用next数组判断,是因为假设循环长度为\(d\),那么将字符串向右滑动\(d\)位之后重合的部分一定完全相同。基于这个理论,这道题也可以这么想。
假设循环长度为\(d\),将字符串向右滑动\(d\)位,重合的部分中,如果\(a_{d+i} == b_{i}(0 \leq i \leq n-1)\),则\(d\)可以被记录到答案中。
如果把b数组翻转一下是不是就很像卷积了?但是怎么做乘法呢?
我一开始想如果相等就为1,即两个1相乘,最后加起来如果是\(n-d\)就说明有效。但是这样VK就要分开看啊,没有办法进行一个统一的判断。
然后idea就来了...

for (int i = 0; i < n; i++) {
    if (s[i] == 'V') a[i] = 1;
    else if (s[i] == 'K') b[n - i] = 1;
}

这样的话...如果匹配就是0,如果加起来就是0那么可行...妙啊!
然后跑ntt/fft就好了。数据里面好像卡了一发长度为1的ntt?特判一下就好了。

Code
#include <bits/stdc++.h>
#define N (1 << 20)
#define ll long long
#define pb push_back
using namespace std;
const ll M = 998244353;
const ll g = 3; 
ll Pow(ll a, ll n) {
   ll ret = 1;
   while (n) {
      if (n & 1) ret = ret * a % M;
      n >>= 1;
      a = a * a % M;
   }
   return ret;
}
void rader(ll y[], int len) {
   for (int i = 1, j = len / 2; i < len - 1; i++) {
      if (i < j) swap(y[i], y[j]);
      int k = len / 2;
      while (j >= k) {
         j -= k;
         k /= 2;
      }
      if (j < k) j += k;
   }
}
void ntt(ll y[], int len, int on) {
   rader(y, len);
   for (int h = 2; h <= len; h <<= 1) {
      ll wn = Pow(g, (M - 1) / h);
      if (on == -1) wn = Pow(wn, M - 2);
      for (int j = 0; j < len; j += h) {
         ll w = 1;
         for (int k = j; k < j + h / 2; k++) {
            ll u = y[k];
            ll t = w * y[k + h / 2] % M;
            y[k] = (u + t) % M;
            y[k + h / 2] = (u - t + M) % M;
            w = w * wn % M;
         }
      }
   }
   if (on == -1) {
      ll t = Pow(len, M - 2);
      for (int i = 0; i < len; i++)
     y[i] = y[i] * t % M;
   }
}
ll a[N], b[N];
int n, len;
char s[N];
int T;
bool ans[N]; 
vector <int> w; 
void init()
{
   w.clear();
   for (int i = 0; i <= len; i++) {
      a[i] = b[i] = ans[i] = 0;
   }
}
int main()
{
   cin >> T;
   while (T--) {
      scanf("%d%s", &n, s);
      if (n == 1) printf("1\n1");
      else {
         len = 1; 
         while (len <= n * 2) len <<= 1;
         init(); 
         for (int i = 0; i < n; i++) {
            if (s[i] == 'V') a[i] = 1;
            else if (s[i] == 'K') b[n - i] = 1;
         } 
         ntt(a, len, 1);
         ntt(b, len, 1);
         for (int i = 0; i <= len; i++)
            a[i] = (a[i] * b[i]) % M;
         ntt(a, len, -1);
         for (int i = 1; i <= n; i++)
            if (a[n - i] == 0 && a[n + i] == 0) ans[i] = true;
         for (int i = 1; i <= n; i++) {
            for (int j = i << 1; j <= n; j += i)
               ans[i] &= ans[j];
            if (ans[i]) w.pb(i);
         }
         cout << w.size() << endl;
         for (int i = 0; i < (int)w.size(); i++)
            printf("%d ", w[i]);
      }
      putchar('\n');
   }
   return 0; 
}

转载于:https://www.cnblogs.com/zkGaia/p/7160247.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值