Educational Codeforces Round 40 (Rated for Div. 2) Solution

Problem A Diagonal Walking

题目大意

  给定一个只包含'U'和'R'的字符串,你可以将"RU"或者'UR"替换为"D"。问能使串长能变成的最小值。

  直接把 RU 或者 UR 替换为 D 即可。

Code

/**
 * Codeforces
 * Problem#954A
 * Accepted
 * Time: 31ms
 * Memory: 0k
 */
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

int n;
char s[233];

int main() {
	scanf("%d", &n);
	scanf("%s", s + 1);
	int ans = n;
	for (int i = 1; i < n; i++) {
		if (s[i] ^ s[i + 1]) {
			i++, ans--;
			continue;
		}
	}
	printf("%d
", ans);
	return 0;
}

Problem B String Typing

题目大意

  有一台打字机,要输入1个字符串,有两种操作:

  1. 向末尾输入一个字符
  2. 将当前输入的字符串复制一遍粘在后面

  操作2只能使用1次。问最少的操作次数。

  没看到只能用1次,傻傻地写了个dp。并成功获得了:

  然后急急忙忙去加个状态。

  其实只能用1次直接贪心就好了。当减少的操作次数最多的时候用操作2.

Code

 1 /**
 2  * Codeforces
 3  * Problem#954B
 4  * Accepted
 5  * Time: 31ms
 6  * Memory: 0k
 7  */
 8 #include <bits/stdc++.h>
 9 using namespace std;
10 typedef bool boolean;
11 
12 int n;
13 char str[105];
14 int f[105][2];
15 
16 inline void init() {
17     scanf("%d", &n);
18     scanf("%s", str + 1);
19 }
20 
21 boolean equal(int l1, int l2, int len) {
22     for (int i = 0; i < len; i++)
23         if (str[l1 + i] != str[l2 + i])
24             return false;
25     return true;
26 }
27 
28 inline void solve() {
29     f[0][1] = 2000;
30     for (int i = 1; i <= n; i++) {
31         f[i][0] = f[i - 1][0] + 1;
32         f[i][1] = f[i - 1][1] + 1;
33         if (!(i & 1) && (equal(1, (i >> 1) + 1, i >> 1)))
34             f[i][1] = min(f[i][1], f[i >> 1][0] + 1);
35     }
36     printf("%d
", min(f[n][0], f[n][1]));
37 }
38 
39 int main() {
40     init();
41     solve();
42     return 0;
43 }
Problem B

Problem C Matrix Walk

题目大意

  有一个$x imes y$的网格图,但是不知道$x, y$的值。规定$i$行$j$列的格子的标号为$(i - 1)y + j$。

  给定一个长度为$n$的经过的格子序列,规定只能走相邻的格子,且不能走出边界,也不能停留在同一个格子。问是否可能存在一组$x, y$使得路径合法,如果存在,输出这一组。

  找最大的一组相邻的差作为$y$。

  $x$足够大就好了(为什么?因为没影响)。

  然后代进去检验是否合法。

Code

 1 /**
 2  * Codeforces
 3  * Problem#954C
 4  * Accepted
 5  * Time: 61ms
 6  * Memory: 800k
 7  */
 8 #include <bits/stdc++.h>
 9 using namespace std;
10 typedef bool boolean;
11 
12 int n;
13 int *ar;
14 
15 inline void init() {
16     scanf("%d", &n);
17     ar = new int[(n + 1)];
18     for (int i = 1; i <= n; i++)
19         scanf("%d", ar + i);
20 }
21 
22 int mxd = 1;
23 set<int> s;
24 void termin() {
25     puts("NO");
26     exit(0);
27 }
28 
29 inline void solve() {
30     for (int i = 1; i < n; i++) {
31         int dif = abs(ar[i + 1] - ar[i]);
32         if (!dif)
33             termin();
34         if (mxd > 1 && dif > 1 && dif != mxd)
35             termin();
36         if (dif > 1)
37             mxd = dif;
38     }
39     
40     int ly = ar[1] % mxd;
41     if (!ly) ly = mxd;
42     int lx = (ar[1] - ly) / mxd + 1;
43     for (int i = 2, cx, cy; i <= n; i++) {
44         cy = ar[i] % mxd;
45         if (!cy) cy = mxd;
46         cx = (ar[i] - cy) / mxd + 1;
47         if (abs(cx - lx) + abs(cy - ly) != 1)
48             termin();
49         lx = cx, ly = cy;
50     }
51     puts("YES");
52     printf("%d %d", 1000000000, mxd);
53 }
54 
55 int main() {
56     init();
57     solve();
58     return 0;
59 }
Problem C

Problem D Fight Against Traffic

题目大意

  给定$n$个点$m$条边的无向连通简单图。每条边边权均为1。

  要求加一条原本不存在的边,使得新图没有自环,$s, t$之间的距离不改变。

  问方案数。

  看这数据范围挺小的。

  分别跑出$s, t$的最短路径生成树。

  然后枚举边的两个端点,判断加入后新产生的路径长度是否合法。

Code

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 typedef bool boolean;
 4 
 5 const int N = 1e3 + 5;
 6 
 7 int n, m, s, t;
 8 boolean g[N][N];
 9 int f1[N], f2[N];
10 
11 inline void init() {
12     scanf("%d%d%d%d", &n, &m, &s, &t);
13     for (int i = 1, u, v; i <= m; i++) {
14         scanf("%d%d", &u, &v);
15         g[u][v] = true, g[v][u] = true;
16     }
17 }
18 
19 typedef class Node {
20     public:
21         int p, dis;
22 
23         Node(int p = 0, int dis = 0):p(p), dis(dis) {    }
24 
25         boolean operator < (Node b) const {
26             return dis > b.dis;
27         }
28 }Node;
29 
30 priority_queue<Node> que;
31 void dijstra(int s, int* f) {
32     memset(f, 0x7f, sizeof(int) * (n + 1));
33     que.push(Node(s, f[s] = 0));
34     while (!que.empty()) {
35         Node e = que.top();
36         que.pop();
37         if (e.dis != f[e.p])
38             continue;
39         for (int i = 1; i <= n; i++)
40             if (g[e.p][i] && e.dis + 1 < f[i])
41                 que.push(Node(i, f[i] = e.dis + 1));
42     }
43 }
44 
45 int ans = 0;
46 inline void solve() {
47     dijstra(s, f1);
48     dijstra(t, f2);
49     for (int i = 1; i <= n; i++)
50         for (int j = i + 1; j <= n; j++)
51             if (!g[i][j] && f1[i] + f2[j] + 1 >= f1[t] && f1[j] + f2[i] + 1 >= f1[t])
52                 ans++;
53     printf("%d", ans);
54 }
55 
56 int main() {
57     init();
58     solve();
59     return 0;
60 }
Problem D

Problem E Water Taps

题目大意

  给定$n, T, a_{i}, t_{i}$,找出一组$x_{i}$满足$0leqslant x_{i} leqslant a_{i}$,且$frac{sum_{i = 1}^{n}x_{i}t_{i}}{sum_{i = 1}^{n}x_{i}} = T$。问$sum_{i = 1}^{n}x_{i}$的最大值。无解输出0。

  移项,排序后贪心即可。

Code

/**
 * Codeforces
 * Problem#954E
 * Accepted
 * Time: 93ms
 * Memory: 4400k
 */
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 2e5 + 5;

#define ll long long
#define pii pair<int, int>

int n, T;
ll sum = 0;
int a[N];
vector<pii> vL, vR;

int main() {
	scanf("%d%d", &n, &T);
	double ans = 0;
	for (int i = 1; i <= n; i++) {
		scanf("%d", a + i);
		ans += a[i];
	}
	for (int i = 1, t; i <= n; i++) {
		scanf("%d", &t);
		sum += 1ll * a[i] * (t - T);
		if (t <= T) {
			vL.emplace_back(T - t, a[i]);
		} else {
			vR.emplace_back(t - T, a[i]);
		}
	}
	if (sum < 0) {
		sum = -sum;
	} else {
		vL = vR;
	}
	sort(vL.begin(), vL.end(), greater<pii>());
	for (auto e : vL) {
		if (!sum)
			break;
		ll tmp = 1ll * e.first * e.second;
		if (tmp > sum) {
			ans -= 1.0 * sum / e.first;
			break;
		} else {
			sum -= tmp;
			ans -= e.second;
		}
	}
	printf("%.9lf
", ans);
	return 0;
}

Problem F Runner's Problem

题目大意

  给定一个$3 imes m$的棋盘,在$(2, 1)$的地方有一个棋子,棋子每次只能移动到下一列的相邻的行或者当前行。

  棋盘上有$n$段障碍,第$i$段障碍是在第$a_{i}$行的$l_{i}$列到第$r_{i}$列。棋子不能到障碍格上。

  问棋子从$(2, 1)$到$(2, m)$的方案数。(答案模$10^{9} + 7$)

  随便写写dp方程,把转移表变成矩阵。跑快速幂。

Code

  1 /**
  2  * Codeforces
  3  * Problem#954F
  4  * Accepted
  5  * Time: 390ms
  6  * Memory: 1700k
  7  */
  8 #include <bits/stdc++.h>
  9 #ifndef WIN32
 10 #define Auto "%lld"
 11 #else
 12 #define Auto "%I64d"
 13 #endif
 14 using namespace std;
 15 typedef bool boolean;
 16 
 17 #define ll long long
 18 #define pli pair<ll, int>
 19 
 20 typedef class Segment {
 21     public:
 22         ll l, r;
 23         int sta;
 24 
 25         Segment() {        }
 26         Segment(ll l, ll r, int sta):l(l), r(r), sta(sta) {    }
 27 }Segment;
 28 
 29 const int M = 1e9 + 7;
 30 
 31 typedef class Matrix {
 32     public:
 33         int r, c;
 34         int a[3][3];
 35 
 36         Matrix(int r = 0, int c = 0):r(r), c(c) {}
 37 
 38         Matrix operator * (Matrix b) {
 39             Matrix rt(r, b.c);
 40             assert(c == b.r);
 41             for (int i = 0; i < r; i++)
 42                 for (int j = 0; j < b.c; j++) {
 43                     rt[i][j] = 0;
 44                     for (int k = 0; k < c; k++)
 45                         rt[i][j] = (rt[i][j] + a[i][k] * 1ll * b[k][j]) % M;
 46                 }
 47             return rt;
 48         }
 49 
 50         int* operator [] (int p) {
 51             return a[p];
 52         }
 53 }Matrix;
 54 
 55 const int N = 3e4 + 6;
 56 
 57 Matrix qpow(Matrix a, ll p) {
 58     assert(p >= 0);
 59     Matrix pa = a, rt(3, 3);
 60     for (int i = 0; i < 3; i++)
 61         for (int j = 0; j < 3; j++)
 62             rt[i][j] = (i == j);
 63     for ( ; p; p >>= 1, pa = pa * pa)
 64         if (p & 1)
 65             rt = rt * pa;
 66     return rt;
 67 }
 68 
 69 int n;
 70 ll m;
 71 pli ad[N], rm[N];
 72 Segment ss[N];
 73 
 74 inline void init() {
 75     scanf("%d"Auto, &n, &m);
 76     int a;
 77     ll l, r;
 78     for (int i = 1; i <= n; i++) {
 79         scanf("%d"Auto""Auto, &a, &l, &r);
 80         ad[i].first = l, ad[i].second = a - 1;
 81         rm[i].first = r + 1, rm[i].second = a - 1;
 82     }
 83 }
 84 
 85 Matrix mktrans(int os, int ns) {
 86     Matrix rt(3, 3);
 87     for (int i = 0; i < 3; i++)
 88         for (int j = 0; j < 3; j++)
 89             rt[i][j] = (abs(i - j) <= 1 && !(os & (1 << i)) && !(ns & (1 << j)));
 90     return rt;
 91 }
 92 
 93 int ts[4], tp = 0;
 94 inline void solve() {
 95     sort(ad + 1, ad + n + 1);
 96     sort(rm + 1, rm + n + 1);
 97     int ca = 1, cb = 1;
 98     ll st = 1, ed;
 99     while (ca <= n || cb <= n) {
100         while (ca <= n && ad[ca].first == st)
101             ts[ad[ca].second]++, ca++;
102         while (cb <= n && rm[cb].first == st)
103             ts[rm[cb].second]--, cb++;
104         
105         int sta = 0;
106         for (int i = 0; i < 3; i++)
107             if (ts[i])
108                 sta |= (1 << i);
109 
110         ed = m + 1;
111         if (ca <= n)
112             ed = ad[ca].first;
113         if (cb <= n)
114             ed = min(ed, rm[cb].first);
115         ss[++tp] = Segment(st, ed - 1, sta);
116         st = ed; 
117     }
118 
119     Matrix f(1, 3);
120     f[0][0] = f[0][2] = 0, f[0][1] = 1;
121     Matrix T = mktrans(ss[1].sta, ss[1].sta);
122     f = f * qpow(T, ss[1].r - ss[1].l);
123 //        cerr << f[0][0] << " " << f[0][1] << " " << f[0][2] << endl;
124     for (int i = 2; i <= tp; i++) {
125         T = mktrans(ss[i - 1].sta, ss[i].sta);
126         f = f * T;
127         T = mktrans(ss[i].sta, ss[i].sta);
128         f = f * qpow(T, ss[i].r - ss[i].l);
129 //        cerr << ss[i].l << " " << ss[i].r << endl;
130 //        cerr << f[0][0] << " " << f[0][1] << " " << f[0][2] << endl;
131     }
132     printf("%d
", f[0][1]);
133 }
134 
135 int main() {
136     init();
137     solve();
138     return 0;
139 }
Problem F

Problem G Castle Defense

题目大意

  有片城墙被分为$n$个防守段。第$i$段有$a_{i}$个弓箭手。定义一段防守段的防御力是距离它的距离不超过$r$的所有防守段的弓箭手数量之和。定义这片城墙的稳定程度是每个防守段的防御力的最小值。

  现在有$k$个后备弓箭手,你可将他们分配任何防守段,但不能调动原有的弓箭手。问调动后最大的稳定程度。

  显然二分答案。

  考虑怎么check。对于一个防御值小于$mid$的防守段$i$,能对它产生贡献的一些防守段中分配的后备弓箭手总数不得小于$mid - def[i[$。

  然后设在第$i$个防守段设置的弓箭手数量为$a_{i}$,对它求一个前缀和$s$。

  显然使用差分约束,然后连边就很显然了:

  • $s_{r} - s_{l - 1} leqslant mid - def[i], (def[i] < mid)$
  • $s[i]leqslant s[i + 1]$

  由于图比较特殊,不必用最短路算法。直接一个for,完事。

  (感觉把防守段替换为烽火台食用更佳,总之原题说的是section,实在不知道怎么翻译比较好)。

Code

 1 /**
 2  * Codeforces
 3  * Problem#954G
 4  * Accepted
 5  * Time: 358ms
 6  * Memory: 13700k
 7  */
 8 #include <bits/stdc++.h>
 9 #ifndef WIN32
10 #define Auto "%lld"
11 #else
12 #define Auto "%I64d"
13 #endif
14 using namespace std;
15 typedef bool boolean;
16 
17 #define ll long long
18 
19 const int N = 5e5 + 5;
20 
21 int n, r;
22 ll k;
23 int ar[N];
24 ll ps[N], def[N];
25 ll f[N];
26 
27 inline void init() {
28     scanf("%d%d"Auto, &n, &r, &k);
29     for (int i = 1; i <= n; i++)
30         scanf("%d", ar + i);
31     for (int i = 1; i <= n; i++)
32         ps[i] = ps[i - 1] + ar[i];
33 }
34 
35 boolean check(ll mid) {
36     memset(f, 0, sizeof(f));
37     for (int i = 1; i <= n; i++) {
38         f[i] = max(f[i - 1], f[i]);
39         if (def[i] >= mid)
40             continue;
41         int l = max(i - ::r, 1), r = min(i + ::r, n);
42         f[r] = max(f[l - 1] + mid - def[i], f[r]);
43     }
44     return f[n] <= k;
45 }
46 
47 inline void solve() {
48     for (int i = 1; i <= n; i++) {
49         int l = max(i - ::r, 1), r = min(i + ::r, n);
50         def[i] = ps[r] - ps[l - 1];
51     }
52     
53     ll l = 0, r = 2e18;
54     while (l <= r) {
55         ll mid = (l + r) >> 1;
56         if (check(mid))
57             l = mid + 1;
58         else
59             r = mid - 1;
60     }
61     printf(Auto, l - 1);
62 }
63 
64 int main() {
65     init();
66     solve();
67     return 0;
68 }
Problem G

Problem H Path Counting

题目大意

  给定一棵深度为$n$的树,根节点的深度为1,深度为$i$的点的度数为$a_{i}$,保证$a_{n} = 0$。要求对于每个$1leqslant d leqslant 2n - 2$,输出长度为$d$的简单无向路径数量对模$10^9 + 7$的剩余。

  现在说一下一个非常常见的计数对象:取路径的最高点。

  发现每个深度的每个子树的情况完全相同,因此只需要对每个深度$i$计算答案,然后乘上一个常数$c_{i}$。

  设$g_{i,d}$表示在深度为$i$的一个子树中,距离根节点$d$条边的点的数量。

  那么有:

$ans_{d} = sum_{i = 1}^{n}left[g_{i, d} + left(sum_{j = 1}^{left lfloor d/2 ight floor}g_{i + 1, j - 1}cdot g_{i +1, d - j - 1} ight )a_{i}(a_{i} - 1) - [2mid d]g_{i + 1,d/2 - 1}^2 ight ]c_{i}$

  然后来慢慢解释这个式子。

  $g_{i, d}$是从当前点出发的路径数量,$中间求和是拼接两条路径,为了防止算重,选择两个不同的子树,并要求第一个子树选择的路径长度小于等于第二个子树中选择,当选择路径长度不同时,正反都可以,所以答案乘2,再减去选择路径长度相同的一部分的答案就行了。

  然后开心地发现这个式子是三方的。

  接着发现$g, c$都是可以直接算的:

$g_{i, d}=prod_{j = 0}^{d - 1}a_{i + j}$

$c_{i} = prod_{j = 1}^{i - 1}a_{j}$

  中间的一坨求和式子可以看成,枚举任意$i$,然后在$i + 1$后面选择两段连续的$a$,两段都要包含$a_{i + 1}$,长度和为$d - 2$,再把这两段的积乘起来。

  然后很容易发现,设$f_{i, d} = sum_{j = 0}^{left lfloor d/2 ight floor}g_{i, j}cdot g_{i, d - j}$,那么算$f_{i + 1, d - 2}$的时候可以把$j = 0$的一部分先刨开,然后除以$a_{i}^{2}$就行了。

$f_{i + 1, d - 2} = (f_{i, d} - g_{i, d})a_{i}^{-2}$

  于是这个zz做法可以实现$O(n^{2})$。常数贼大,跑得贼慢,sad。。。

  然后看了看标算。标算取路径两端点作为计数点,这样每条路径会被计算2次,答案除以2就行了。对于以某个点为端点的路径可以分成两种,第一种是向上走(子树外),第二种是向下走(子树内)。第二部分显然,第一部分转移考虑它走到父节点后的方向,时间复杂度一样,但常数小很多。

  1 /**
  2  * Codeforces
  3  * Problem#954H
  4  * Accepted
  5  * Time: 1544ms
  6  * Memory: 200k
  7  */
  8 #include <bits/stdc++.h>
  9 using namespace std;
 10 typedef bool boolean;
 11 
 12 const int N = 5005, M = 1e9 + 7;
 13 
 14 void exgcd(int a, int b, int& x, int& y) {
 15     if (!b)
 16         x = 1, y = 0;
 17     else {
 18         exgcd(b, a % b, y, x);
 19         y -= (a / b) * x;
 20     }
 21 }
 22 
 23 int inv(int a, int n) {
 24     int x, y;
 25     exgcd(a, n, x, y);
 26     return (x < 0) ? (x + n) : (x);
 27 }
 28 
 29 int n;
 30 int inv2 = (M + 1) >> 1;
 31 int ar[N], br[N];
 32 int pro[N];
 33 int ans[N << 1];
 34 int f[2][N << 1];
 35 
 36 int add(int a, int b) {
 37     a += b;
 38     if (a >= M)
 39         a -= M;
 40     return a;
 41 }
 42 
 43 int sub(int a, int b) {
 44     a -= b;
 45     if (a < 0)
 46         a += M;
 47     return a;
 48 }
 49 
 50 inline void init() {
 51     scanf("%d", &n);
 52     for (int i = 1; i < n; i++)
 53         scanf("%d", ar + i);
 54     ar[n] = 0;
 55     for (int i = 1; i <= n; i++)
 56         br[i] = inv(ar[i], M);
 57 }
 58 
 59 int pow2(int x) {
 60     return x * 1ll * x % M;
 61 }
 62 
 63 inline void solve() {
 64     int cur = 0, nxt = 1, P = 1;
 65     pro[0] = 1;
 66     for (int i = 1; i <= n; i++)
 67         pro[i] = pro[i - 1] * 1ll * ar[i] % M;
 68     f[cur][0] = 1;
 69     for (int d = 1; d <= ((n - 1) << 1); d++)
 70         for (int i = max(0, d - n); i <= n && i <= d && i <= d - i; i++)
 71             f[cur][d] = add(f[cur][d], pro[i] * 1ll * pro[d - i] % M);
 72     for (int i = 1; i < n; i++) {
 73         if (i > 1) {
 74             pro[0] = 1;
 75             for (int j = 0; i + j <= n; j++)
 76                 pro[j + 1] = pro[j] * 1ll * ar[i + j] % M;
 77         }
 78         for (int d = 2, x, C; d <= ((n - i) << 1); d++) {
 79             x = ((d <= n - i) ? (pro[d]) : (0));
 80             C = ar[i] * 1ll * (ar[i] - 1) % M * P % M;
 81             f[nxt][d - 2] = sub(f[cur][d], x) * 1ll * br[i] % M * 1ll * br[i] % M;
 82             ans[d] = add(ans[d], f[nxt][d - 2] * 1ll * C % M);
 83             if (!(d & 1))
 84                 ans[d] = sub(ans[d], pow2(pro[d >> 1] * 1ll * br[i] % M) * 1ll * C % M * inv2 % M);
 85         }
 86         for (int d = 1; d <= n - i; d++)
 87             ans[d] = add(ans[d], pro[d] * 1ll * P % M);
 88         P = P * 1ll * ar[i] % M;
 89         swap(cur, nxt);
 90     }
 91 
 92     for (int i = 1; i <= (n - 1) << 1; i++)
 93         printf("%d ", ans[i]);
 94 }
 95 
 96 int main() {
 97     init();
 98     solve();
 99     return 0;
100 }
Problem H

Problem I Yet Another String Matching Problem

题目大意

  定义一个操作是将串内的一种字符改成另一种字符。定义两个串的编辑距是使得这两个串相等的最少操作数。

  给定两个串$S, T left(|S|leqslant |T| ight)$,询问$S$的每个串长等于$|T|$的子串和$|T|$的编辑距。字符集 'a' ~ 'f' 

  进行一种操作相等于让两种字符等价。这样我们可以将1次操作看作在无向图中将两个字符连接起来。

  考虑对于$S_{i} eq T_{j}$,那么$S_{i}$需要和$T_{j}$等价,那么在无向图中连一条边。答案是字符集大小减去连通块个数。

  然后讲标算。标算比较套路,注意到边的数量不超过30条,因此想办法判断每条边在每一次询问中是否存在。

  考虑枚举这条边$(a, b)$,将$S$出现$a$的地方标为1,将$T$出现$b$的地方也标为1。如果$S_{i} = a wedge T_{j} = b$,那么以$i - j$作为起点的地方就会存在这样一条边。

  然后就是套路了,翻转串$T$,原来的$j$变为了$|T| - j$,贡献的位置就是$|T| + i - j$,然后跑FFT算答案。

  剩下的就是暴力了。可以dfs,也可以dsu算连通块的个数。

  由于我很懒,不想写FFT,就成了可恶的嘴巴AC选手。

  然后来膜一下Execution Time榜rk 1同学的Hash做法。

  考虑按顺序枚举$S$的每个子串$S'$,注意到我们只关心连通块的个数。

  不幸的是我们暂时不能区分多个连通块,但是我们有办法当前的连通子图不是极大的情况。

  由于字符集很小,我们可以尝试暴力枚举一个连通块由哪些字符组成。考虑选择了一个非极大连通子图,那么存在一个点有一条边连向另一个字符。

  因此这个选定的字符集在$S'$中出现的所有位置和$T$中出现的所有位置不相同。因此通过判断出现位置的集合是否相同可以判断非极大连通子图。

  如何判断选定的字符集是否连成一个连通块?我们按照字符集包含的字符个数的顺序来枚举,然后对于找的一个极大连通子图,我们把些字符ban掉。这样就能保证了。

  判断出现位置集合是否相同可以用Hash,每做完一个询问更新一下位置集合的Hash值。

  好像还有一个神奇的dfs做法?没看懂。qwq

  口胡另一个做法,考虑最多有 $|Sigma|$ 条边,可以用 Hash 表示出每一种字符出现的所有位置,即 $h_c(s) = sum_{i = 0}^{|s| - 1} x^i [s_i = c]$。用并查集维护已经有的边形成的连通块。考虑用二分求出下一对不同的字符,每次求每个字符的 Hash 值乘上它的所在连通块的根的标号。 可以做到 $O(n|Sigma|^2 log n)$ 或者 $O(nB_{|Sigma|}  + n|Sigma|log n)$

Code

 1 /**
 2  * Codeforces
 3  * Problem#954I
 4  * Accepted
 5  * Time: 62ms
 6  * Memory: 1200k
 7  */
 8 #include <algorithm>
 9 #include <iostream>
10 #include <cstdlib>
11 #include <cstring>
12 #include <cstdio>
13 using namespace std;
14 typedef bool boolean;
15 
16 #define ull unsigned long long
17 
18 const int N = 125005, S = 1 << 6;
19 const ull base = 5;
20 
21 int n, m;
22 char sa[N], sb[N];
23 ull pow7[N];
24 ull ps[S], pt[S];
25 int bit[S], sta[S];
26 
27 inline void init() {
28     scanf("%s%s", sa, sb);
29     n = strlen(sa), m = strlen(sb);
30     pow7[0] = 1;
31     for (int i = 1; i < n; i++)
32         pow7[i] = pow7[i - 1] * base;
33 }
34 
35 void update(ull *ar, int c, ull val, int sgn) {
36     int temp = (1 << c);
37     for (int s = temp; s < S; s = (s + 1) | temp)
38         ar[s] += val * sgn;
39 }
40 
41 boolean cmp(const int& a, const int& b) {
42     return bit[a] < bit[b];
43 }
44 
45 inline void prepare() {
46     bit[0] = 0;
47     for (int i = 1; i < S; i++)
48         bit[i] = bit[i - (i & (-i))] + 1;
49     for (int i = 0; i < S; i++)
50         sta[i] = i;
51     sort(sta, sta + S, cmp);
52 }
53 
54 inline void solve() {
55     for (int i = 0; i < m; i++)
56         update(ps, sa[i] - 'a', pow7[i], 1);
57     for (int i = 0; i < m; i++)
58         update(pt, sb[i] - 'a', pow7[i], 1);
59 
60     for (int i = 0; i <= n - m; i++) {
61         int ban = 0, res = 0;
62         for (int j = 0; j < S; j++) {
63             int s = sta[j];
64             if (!(s & ban)) {
65                 if (!ps[s] && !pt[s])
66                     ban |= s;
67                 else if (pt[s] * pow7[i] == ps[s]) {
68                     res += bit[s] - 1;
69                     ban |= s;
70                 }
71             }
72         }
73         printf("%d ", res);
74         if (i + m < n) {
75             update(ps, sa[i] - 'a', pow7[i], -1);
76             update(ps, sa[i + m] - 'a', pow7[i + m], 1);
77         }
78     }
79 }
80 
81 int main() {
82     init();
83     prepare();
84     solve();
85     return 0;
86 }
Problem I
原文地址:https://www.cnblogs.com/yyf0309/p/9310653.html