总结
还算正常发挥吧。T1高精度没有管它,T2打的 O ( 26 n + n l o g n ) O(26n+nlogn) O(26n+nlogn),离正解 O ( n ? ) O(n?) O(n?)很近了,只是不知道怎么去把 C C C为奇的个数和 A B AB AB拆分的方案数算出来,就只能用前缀和预处理了。T4很吃亏,本来线段树+暴力有40pts,结果没加取模,估计只有30pts。T3完全不可做,n=2时10pts没打对,又白丢了10pts。
T3,T4总共白丢了20pts,实在不应该。最后大概有210左右吧。对于T3这样的构造题实在无从下手,应该多肝CF。
简要题解
本人已经被NOIP搞崩溃了。但本着心平气和的原则,还是把它写完。
T1
略。
T2
扩展 KMP 什么的我都不会,还有什么分析性质分别计数的都看不懂,我就说最简单的做法吧:
先枚举(AB)。然后向后扩展,看有几个AB。答案就是 k ∗ f ( i , p r e [ k ∗ i + 1 ] ) k*f(i,pre[k*i+1]) k∗f(i,pre[k∗i+1])。
正解
继续分析发现,对于固定的
(
A
B
)
(AB)
(AB),根据重复次数的奇偶性,其对应的
C
C
C 中出现奇数次的字符个数只有两种可能。
直接统计每种可能对答案的贡献即可。复杂度 O ( n log 26 ) O(n \log 26) O(nlog26),期望得分 100 p t s 100pts 100pts。
具体做法:
首先我们暴力枚举
X
X
X 的长度
X
=
A
+
B
X=A+B
X=A+B
然后对于每个长度,我们二分它
i
i
i 能取到的最大值。
因为长度越长这个字符串越不可能循环,所以具有单调性。
上面的东西是
∑
i
=
1
N
log
(
N
i
)
\sum_{i=1}^{N}\log\left(\frac{N}{i}\right)
∑i=1Nlog(iN)的
据神仙
h
ehezhou
h
e
h
e
z
h
o
u
\color{black}\text{h}\color{red}\text{ehezhou}hehezhou
hehezhouhehezhou 说是
O
(
N
)
O(N)
O(N) 的。
我们获得了每个
X
X
X 所能取到的最大
i
i
i 的长度。
同时,当
X
X
X 固定时,当
i
∈
{
1
,
3
,
5...
}
i\in\{1,3,5...\}
i∈{1,3,5...} 或
i
∈
{
2
,
4
,
6...
}
i\in\{2,4,6...\}
i∈{2,4,6...} 时,把
X
X
X 拆成
A
A
A 和
B
B
B 的方案数是一样的,所以我们可以
O
(
1
)
O(1)
O(1) 统计贡献。
然后枚举
X
X
X,在
X
X
X 改变时只需要计算增加的那个
X
X
X 的贡献就好了。
复杂度均摊
O
(
N
l
o
g
26
)
O(Nlog26)
O(Nlog26)。
考场做法
考场上开了一个 s u m [ 2 e 6 ] [ 30 ] sum[2e6][30] sum[2e6][30]的数组,差点以为暴空间了。
然而:2000005 × 30 × 4 ÷ 1024 ÷ 1024 =228.8824081420898 MiB
于是乎苟了84pts。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=2e6+5;
inline int read() {
int x=0,f=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
char s[N];
int nxt[N],pre[N],pre2[N],bit[27],sum[27],n;
bool tag[27];
void Kmp() {
nxt[1]=0;
for(int i=2,j=0;i<=n;i++) {
while(j!=0&&s[i]!=s[j+1]) j=nxt[j];
if(s[i]==s[j+1]) j++;
nxt[i]=j;
}
}
int main() {
// freopen("string.in","r",stdin);
// freopen("string.out","w",stdout);
int T=read();
while(T--) {
long long ans=0;
memset(bit,0,sizeof(bit));
memset(tag,0,sizeof(tag));
scanf("%s",s+1);
n=strlen(s+1);
Kmp();
pre[n+1]=pre[0]=0;
for(int i=n;i>=1;i--) {
tag[s[i]-'a']^=1;
if(tag[s[i]-'a']) pre[i]=pre[i+1]+1;
else pre[i]=pre[i+1]-1;
}
memset(tag, 0, sizeof(tag));
int cnt=0;
for(int i=1;i<n;i++) {
int L=0,R=n/(i*2),res=0;
while(L<=R) {
int mid=(L+R)>>1,len=i*(mid*2+1);
if(len<n&&i%(len-nxt[len])==0) L=mid+1,res=mid;
else R=mid-1;
}
ans+=bit[pre[i+1]]*(res+1);
L=1,R=n/(i*2),res=0;
while(L<=R) {
int mid=(L+R)>>1,len=i*(mid*2);
if(len<n&&i%(len-nxt[len])==0) L=mid+1,res=mid;
else R=mid-1;
}
if(res>0) ans+=bit[pre[i*2+1]]*res;
tag[s[i]-'a']^=1;
if(tag[s[i]-'a']) cnt++;
else cnt--;
for(int j=cnt;j<=26;j++) bit[j]++;
}
printf("%lld\n",ans);
}
return 0;
}
T3
我们先构造一个全为0的柱子,再把每个柱子的1提上来,把这些1全部放到一个柱子上,这样柱子数就从n变成了n-1。直到只
剩两个颜色,此时上述方法找不到一个没有动过的柱子2。换种方法即可。
操作次数分析
最外层枚举颜色一个
n
n
n ,每次构造全
1
1
1 列时需要
n
m
+
m
nm+m
nm+m(因为
1
1
1 的总个数为
m
m
m,所以分解时的第一步均摊的总次数为
m
m
m)
因为列数随着颜色一个一个处理完会减小,所以 n m + m nm+m nm+m 中的 n n n 其实是个等差数列,也就是 ∑ i = 1 n i m + m \sum\limits_{i=1}^n im+m i=1∑nim+m。所以有个 1 / 2 1/2 1/2 的常数
构造全 0 0 0 列时上限需要 4 m 4m 4m 次,所以操作次数上限为 ∑ i = 1 n i m + 5 m \sum\limits_{i=1}^n im+5m i=1∑nim+5m ,极限数据满打满算要操作 600,000 次,可以轻松通过本题
复杂度 == 操作次数,所以不需要管它
另一种思路:分治?
先考虑一种类似于2(3)根柱子的情况:
- 有两根每根 m m m个珠子的柱子和一根空柱子。 \ \ 废话
- 有两种颜色col1,col2。(假设col1的个数 ≥ \ge ≥col2的个数)
- 要求将①柱子装满col1的珠子,③柱仍为空。
怎么最快分离颜色呢?
…
好了我们可以在两种颜色的情况下快速memset一个柱子。
那怎么变成两种颜色呢???
考虑分治:
若当前颜色集合为 S S S
那么把它分成两份,一份是当 c o l 1 col1 col1,一份当 c o l 2 col2 col2。
可是有很多个柱子,那可以两两来做每次可以memset一个柱子。
因为 m ∣ c o u n t o f ( c o l 1 ) m|countof(col1) m∣countof(col1) 且 m ∣ c o u n t o f ( c o l 2 ) m|countof(col2) m∣countof(col2) 所以最后肯定能使若干个柱子全为 c o l 1 col1 col1,若干个柱子全为 c o l 2 col2 col2。
每次大约走 5 n m 5nm 5nm步。
在递归求解被划为 c l o 1 clo1 clo1的color和被划为 c l o 2 clo2 clo2的color。
最后次数 ≤ 5 n m l o g 2 n \le 5nmlog_2n ≤5nmlog2n
//666
#include<bits/stdc++.h>
using namespace std;
const int N=55;
const int M=405;
const int K=820005;
struct Node{
int tot,pos,a[M];
int cnt[N];
void Push(int x) {
cnt[x]++;
a[++tot]=x;
}
int Top() {
return a[tot];
}
void Pop() {
cnt[a[tot]]--;
tot--;
}
}s[N];
struct Data {
int s,t;
}P[K];
int n,m,k;
bool sol[N];
void Move(int x,int y) {
P[++k]=(Data){s[x].pos,s[y].pos};
s[y].Push(s[x].Top());
s[x].Pop();
}
void Work(int x,int y,int z,int col) {
int t=s[x].cnt[col];
for(int i=1;i<=t;i++) {
Move(y,z);
}
for(int i=1;i<=m;i++) {
if(s[x].Top()==col) Move(x,y);
else Move(x,z);
// if(s[x].cnt[col]==0) break;
}
for(int i=1;i<=m-t;i++) Move(z,x);
for(int i=1;i<=m;i++) {
if(s[2].Top()==col||s[1].tot==m) Move(2,z);
else Move(2,1);
}
swap(s[2],s[z]);
swap(s[1],s[z-1]);
// for(int i=1;i<=t;i++) Move(y,x);
// for(int i=1;i<=t;i++) Move(z,y);
}
void Work2(int x,int y,int z,int col) {
int t=s[x].cnt[col];
for(int i=1;i<=t;i++) {
Move(y,z);
}
for(int i=1;i<=m;i++) {
if(s[x].Top()==col) Move(x,y);
else Move(x,z);
}
swap(s[x],s[y]);
swap(s[y],s[z]);
// for(int i=1;i<=t;i++) Move(y,x);
// for(int i=1;i<=t;i++) Move(z,y);
}
void Work3(int x,int y,int z,int col) {
int t=s[x].cnt[col];
for(int i=1;i<=t;i++) {
Move(y,z);
}
for(int i=1;i<=m;i++) {
if(s[x].Top()==col) Move(x,y);
else Move(x,z);
}
for(int i=1;i<=m-t;i++) Move(z,x);
for(int i=1;i<=t;i++) Move(y,x);
for(int i=1;i<=t;i++) Move(z,y);
}
int main() {
// freopen("ball3.in","r",stdin);
// freopen("ans.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
int x;
scanf("%d",&x);
s[i].Push(x);
}
for(int i=1;i<=n+1;i++) s[i].pos=i;
for(int i=n;i>2;i--) {
Work(1,i,i+1,i);
for(int j=1;j<i;j++) {
Work2(j,i,i+1,i);
}
for(int j=1;j<i;j++) {
while(s[j].Top()==i) {
Move(j,i+1);
}
}
for(int j=1;j<i;j++) {
while(s[j].tot!=m) {
Move(i,j);
}
}
}
Work3(1,2,3,2);
Work3(2,1,3,2);
while(s[1].Top()==2) Move(1,3);
while(s[2].Top()==2) Move(2,3);
while(s[2].tot>0) Move(2,1);
printf("%d\n",k);
for(int i=1;i<=k;i++) printf("%d %d\n",P[i].s,P[i].t);
}