题解 P4685 【[IOI2008] Linear Garden 直线型花园】
这题正解当然是数位DP
但是当初不会,看到这题直接傻眼,则如之何……
被我用找规律硬找出来了,但是过程十分繁琐调了我五小时,眼睛都要看瞎了
这里简单说一下找出来的结果
首先是长度为
然后令 这不废话吗
并且如果把合法花园
然后只用考虑
最后一个毁天灭地惨无人道(还不是因为你太菜)的规律,如何计算
发现它满足如下程序所得
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 就不用动了
至于选哪两个奇数……不是每次奇数把自己分成两半都会有一半是奇数,另一半是偶数嘛,那么下一层所选的奇数就是它们分出来的两奇数
然后还有每一层第一个花园的第
可能有些细节没有提到(因为过于复杂且已经过去好久了),还需大家自己看程序琢磨一下
最后把 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
不建议大家用找规律这种不是办法的办法做题,一是经常找不准,二是要找就要花很多的时间,考场上可是不能这么干的,到时候万一只会找规律不就药丸