1 条题解
-
0
自动搬运
来自洛谷,原作者为

Seauy
I remember your song, sung by centuries of wind.搬运于
2025-08-24 22:02:37,当前版本为作者最后更新于2020-05-01 16:24:12,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
这题正解当然是数位DP
但是当初不会,看到这题直接傻眼,则如之何……
被我用找规律硬找出来了,但是过程十分繁琐
调了我五小时,眼睛都要看瞎了这里简单说一下找出来的结果
首先是长度为 的公园的个数递推式:
然后令 (同时保证了字典序),发现如果第一位为 0,那么花园的排名 ,否则
这不废话吗并且如果把合法花园 按位取反一下的话,便是
然后只用考虑 的情况就行了
最后一个毁天灭地惨无人道(还不是因为你太菜)的规律,如何计算
发现它满足如下程序所得 的结果
void DFS(int ans,int depth,int thrw,bool com) { if(depth>=n) {cnt=ans;return;} if(com) { if(Garden[depth+1]=='P') DFS((ans+POW2(thrw))%m,depth+2,thrw-1,1); else DFS(ans,depth+2,thrw-1,1); return; } if(depth&1)//向右边保留,左边去除 { if(Garden[depth+1]=='P') { //printf("+POW %d\n",POW2(thrw)); DFS((ans+POW2(thrw))%m,depth+1,thrw-(!(n&1)),0);//走右边 } else DFS(ans,depth+2,thrw-1,1); } else//向左边保留,右边去除 { if(Garden[depth+1]=='P') { //printf("+%d\n",f[n-depth-1]+1-POW2(thrw)); DFS(ans+(f[n-depth-1]+1-POW2(thrw)+m)%m,depth+2,thrw-1,1); } else DFS(ans,depth+1,thrw-(n&1),0); } }至于为什么,我先奉上我找规律用的程序
/* 01 序列 称其为平衡序列时,任何一段的 0 与 1 个数之差小于等于 2 将长度为 n 的所有平衡 01 序列 按字典序排序,问某个序列在第几个 %m L=0 P=1 已知 Last 是平衡序列,长 a n=1 0 | 1 n=2 00 01 | 10 11 n=3 001 010 011 | 100 101 110 n=4 0010 */ #include<bits/stdc++.h> #define mid ((L+R)>>1) using namespace std; const int MAXN=1e6; int n; int A[50][MAXN+5],num[50]; int f[50]; void PrintBinary(int ob,int len) {for(int i=len;i;i--) cout<<((ob&(1<<(i-1)))>0);cout<<' ';} int sum[3]; bool Check(int a,int len,bool b) { memset(sum,0,sizeof sum); sum[b]=1; for(int i=1;i<=len;i++,a>>=1) { sum[a&1]++; if(abs(sum[0]-sum[1])>2) return 0; } return 1; } bool Begin(int depth) { if(depth==1) return 0; return depth&1; } int Translate(string ob,int len) { int cnt=0; for(int i=0;i<len;i++) cnt=(cnt<<1)+(ob[i]=='P'); return cnt; } int main() { int show;scanf("%d",&show); f[1]=2; A[1][1]=0,A[1][2]=1,num[1]=2; scanf("%d",&n); for(int i=2;i<=n;i++) for(int j=1;j<=num[i-1];j++) { if(Check(A[i-1][j],i-1,0)) A[i][++num[i]]=(A[i-1][j]<<1); if(Check(A[i-1][j],i-1,1)) A[i][++num[i]]=(A[i-1][j]<<1|1); } for(int i=1,choice,temp,val,times;i<=n;i++) { if(i>1) { if(i&1) f[i]=pow(2,(i-3)/2+1)+f[i-1]; else f[i]=pow(2,(i-2)/2+1)+f[i-1]; } printf("n=%d have %d ---------------- formula result: %d\n",i,num[i],f[i]); for(int j=1;j<=num[i];j++) PrintBinary(A[i][j],i); printf("\n"); if(show==i) { while(1) { scanf("%d",&choice); if(choice==-1) break; temp=0; times=1; val=Begin(choice); for(int j=1,lst=-1;j<=num[i];j++) { if(!temp) { temp=1; lst=((A[i][j]&(1<<(i-choice)))>0); continue; } if(((A[i][j]&(1<<(i-choice)))>0)!=lst) { lst=((A[i][j]&(1<<(i-choice)))>0); printf("%d ",temp); temp=1; times++; } else temp++; } printf("%d\n",temp); for(int j=1;j<=times;j++) printf("%d ",val),val=1-val; printf("\n"); } for(string KEY;1;) { cin>>KEY; if(KEY=="-1") break; int Code=Translate(KEY,i); for(int L=1,R=num[i];L<=R;) { if(A[i][mid]==Code) {printf("%d\n",mid);break;} if(A[i][mid]<Code) L=mid+1; else R=mid-1; } } } } return 0; }先输入 ,表示程序会展示 中所有花园的状态,并且展示到 的时候会暂停
对于暂停的那个 ,接着输入若干 ,表示在长度为 的所有花园中,依次输出第 位连续为 的段的长度
最后输入 -1 结束这一层的研究
比如当 时,所有段转成 表示法长这样:
00101 00110 01001 01010 01011 01100 01101 10010 10011 10100 10101 10110 11001 11010
那么你再输入 ,那么它会输出
2 3 2 2 3 2
1 0 1 0 1 0
表示前 2 个公园的第 3 位为 1,接下来 3 个公园的第 3 位为 0 ,再接下来 2 个公园的第 3 位为 1……
如果你展示出所有 ,发现去掉 那行,会形成一个数字金字塔,顶端只有两个 ,而最底层全为 1
从上一层变到下一层的规律大概是:有两个奇数,会选择两个相反的方向,一个把自己的一半向下取整放在左边,另一半放在右边,自己消失(两半的和当然要跟原来一样);另一个则是向右
其它所有偶数全把自己分成相等的两半,1 就不用动了
至于选哪两个奇数……不是每次奇数把自己分成两半都会有一半是奇数,另一半是偶数嘛,那么下一层所选的奇数就是它们分出来的两奇数
然后还有每一层第一个花园的第 位是 0 还是 1 的问题。规律是前两层第一个花园的第 位是 0,之后一层便是 1,然后 交替
可能有些细节没有提到(因为过于复杂且已经过去好久了),还需大家自己看程序琢磨一下
最后把 AC 代码奉上:
/* 当 n 为 1 时, 奇数编号序列末尾为 0,偶数为 1 当 n 为奇数时,奇数编号序列末尾为 1,偶数为 0 当 n 为偶数时,奇数编号序列末尾为 0,偶数为 1 序列个数为 当 n 为奇数时 f[n]=(2^((n-3)/2)+f[n-1]/2)*2 当 n 为偶数时 f[n]=(2^((n-2)/2)+f[n-1]/2)*2 f[1]=2 当序号 <= f[n]/2 时,开头为 0 当序号 > f[n]/2 时,开头为 1 对于奇数来说 当序号 <= 2^((n-3)/2) 时,第二个为 0 接下来 f[n-1]/2 个 ,第二个为 1 接下来 f[n-1]/2 个 ,第二个为 0 最后 2^((n-3)/2) 个,第二个为 1 9 31 31 8 23 23 8 8 15 8 8 15 8 4 4 4 11 8 8 11 4 4 4 4 4 4 4 7 4 4 4 4 4 4 7 4 4 4 对于每个 n,第一个序列总会是 0010101010101... 序列个数的一半为 当 n 为奇数时 f[n]/2=2^((n-3)/2)+f[n-1]/2 当 n 为偶数时 f[n]/2=2^((n-2)/2)+f[n-1]/2 f[1]/2=1 */ #include<bits/stdc++.h> using namespace std; const int MAXN=1e6; int n,m,cnt; int f[MAXN+5]; string Garden; int mem[MAXN+5]={1}; int POW2(int index) { if(mem[index]) return mem[index]; if(index<0) return 1; return mem[index]=(POW2(index-1)<<1)%m; } void DFS(int ans,int depth,int thrw,bool com) { if(depth>=n) {cnt=ans;return;} if(com) { if(Garden[depth+1]=='P') DFS((ans+POW2(thrw))%m,depth+2,thrw-1,1); else DFS(ans,depth+2,thrw-1,1); return; } if(depth&1)//向右边保留,左边去除 { if(Garden[depth+1]=='P') { //printf("+POW %d\n",POW2(thrw)); DFS((ans+POW2(thrw))%m,depth+1,thrw-(!(n&1)),0);//走右边 } else DFS(ans,depth+2,thrw-1,1); } else//向左边保留,右边去除 { if(Garden[depth+1]=='P') { //printf("+%d\n",f[n-depth-1]+1-POW2(thrw)); DFS(ans+(f[n-depth-1]+1-POW2(thrw)+m)%m,depth+2,thrw-1,1); } else DFS(ans,depth+1,thrw-(n&1),0); } } int main() { f[1]=2,f[0]=1; scanf("%d %d",&n,&m); cin>>Garden,Garden=" "+Garden; //cout<<Garden<<endl; //printf("%d\n",Garden.length()); //printf("Begin prework.\n"); for(int i=2;i<=n;i++) { if(i&1) f[i]=(POW2((i-3)/2+1)+f[i-1])%m; else f[i]=(POW2((i-2)/2+1)+f[i-1])%m; } //printf("Prework Complete.\n"); if(Garden[1]=='P') { for(int i=1;i<=n;i++) if(Garden[i]=='P') Garden[i]='L'; else Garden[i]='P'; //printf("Start DFS\n"); DFS(1,1,n/2-1,0); printf("%d\n",(f[n]-cnt+1+m)%m); } else DFS(1,1,n/2-1,0),printf("%d\n",cnt); return 0; }时空复杂度
最后 BB 两句这题是窝省集训队入门测题目,当时信心满满地交了上去,第二天去上课路上发现只有 60,T 到飞起
然后让教练帮忙看看哪错了,却没发现
稍加检查,发现是写递归 pow
忘记记忆化了smg由于常数比直接数位DP小太多了,所以一时间拿了窝省的效率 rk1
不过随后便被窝省神仙甩掉了,最后一次看到是 rk3
不建议大家用找规律这种不是办法的办法做题,一是经常找不准,二是要找就要花很多的时间,考场上可是不能这么干的,
到时候万一只会找规律不就药丸
- 1
信息
- ID
- 3670
- 时间
- 1500ms
- 内存
- 63MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者