【做题】atc_cf17-final_E - Combination Lock——巧妙转化及图论

题意:给出一个由26个小写字母组成的字符串,可以任意地进行若干个操作,每次操作是让指定区间内的字母变为下一个字母(z变成a)。问是否存在方案使得这个字符串变为回文串。

一开始的想法是构造len/2条模26意义下方程,但由于26不是质数,判断有没有解并不容易。(下文自动省略模26)

我们发现,一次操作对于方程组的影响大概是这样的:

如上图所示,一次对区间[l,r]进行的操作,其中从一端到其关于中点的对称点的区间实质是没有贡献的。(上图中为区间[l,l'])同样地,对于完全位于中点右边的区间,可以将其翻转到中点左边。

因此,所有操作的影响都被移动到了中点左边,则每一条方程都变成了如下形式:k1*x1+k2*x2+...+kn*xn=p。其中,p为常数,且ki为0或1。

而且,每一次操作的影响都是一个区间,所以这就成了一个通过区间操作是一个序列中元素全部变为0的问题。

上述问题简单地说就是,回文串就是左右两边的元素差值为0。所以最终目的就是使这些差值全部变为0。

所以很容易想到对于那个需要变成0的数列差分得到序列a,每一次操作区间[l,r]就是a[l]+1,a[r+1]-1。这等价于令a[l]+a[r+1]一定,然后任意确定a[l]和a[r+1]的值。

同样地,两个操作[l1,r-1]和[l2,r-1]也就是a[l1]+a[l2]+a[r]一定,然后任意确定这三个数的值。

因此,我们可以建一张有len/2+1个点的图,点i的权值为a[i],每一次操作[l,r]就是给点l和点r+1连一条边。则存在方案 <=> 每个连通分量内点权和为0。

上述过程可以用并查集实现。

时间复杂度O(n*α(len))。(下面代码没有按秩合并,时间复杂度为O(n*logn);有代码更优美的实现方式,即计算时保留S的右半部分)

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 100010;
 4 int n,m,s[N],sum[N],dif[N];
 5 int flag[N];
 6 int get_flag(int pos) {
 7     return flag[pos] == pos ? pos : flag[pos] = get_flag(flag[pos]);
 8 }
 9 void fit(int& x) {
10     if (x > n/2) x = n-x+1;
11 }
12 int fix(int x) {
13     if (x<0) x = 26-(-x)%26;
14     x %= 26;
15     return x;
16 }
17 int main() {
18     int l,r;
19     for (char tmp=getchar();tmp>='a'&&tmp<='z';tmp=getchar())
20         s[++n] = tmp-'a';
21     for (int i=1;i<=n/2;++i)
22         s[i] -= s[n-i+1],dif[i]=s[i]-s[i-1],flag[i]=i;
23     dif[n/2+1] = -s[n/2];
24     scanf("%d",&m);
25     for (int i=1;i<=m;++i) {
26         scanf("%d%d",&l,&r);
27         if (n&1) {
28             if (l == n/2+1&&r == n/2+1) continue;
29             if (l == n/2+1) l++;
30             if (r == n/2+1) r--;
31         }
32         if (l<=n/2&&r>n/2) {
33             fit(r);
34             if (l>r) swap(l,r);
35             r--;
36         } else {
37             fit(l);fit(r);
38             if (l>r) swap(l,r);
39         }
40         flag[get_flag(l)]=flag[get_flag(r+1)];
41     }
42     for (int i=1;i<=n/2+1;++i) sum[get_flag(i)] += dif[i];
43     for (int i=1;i<=n/2+1;++i) if (fix(sum[i])!=0) {
44         return 0*puts("NO");
45     }
46     puts("YES");
47     return 0;
48 }

 

小结:一种重要解题方法就是对题目进行转化。

posted @ 2017-12-06 18:59  莫名其妙的aaa  阅读(311)  评论(0编辑  收藏  举报