【比赛】NOIP2020

总结

还算正常发挥吧。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]) kf(i,pre[ki+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=1nim+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=1nim+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) mcountof(col1) m ∣ c o u n t o f ( c o l 2 ) m|countof(col2) mcountof(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);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值