bzoj2214

24 篇文章 0 订阅

poi的好(毒)题。。
题意:
有一个1..n的排列,有两种操作:
(a) 将最后一个数移到最前面
(b) 把第三个数移到最前面
我们将连续进行k次同一个操作称为“一块操作”,表示为ka或kb。
找到一个操作序列使得进行这些操作后,排列变为1,2,3,…,n。
n<=2000

#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#define N 2100
using namespace std;
struct node{int o,d;}ans[N*N];
int n,a[N],cnt,las,b[N];
bool ok()
{
    for(int i=1;i<=n;i++) if(a[i]!=i) return 0;
    return 1;
}
void print()
{
    printf("%d\n",cnt);
    for(int i=1;i<=cnt;i++) 
    {
        printf("%d",ans[i].d);
        if(ans[i].o==1) printf("a ");
        else printf("b ");
    }
}
void ins(int o,int d)
{
    if(o!=ans[cnt].o) {ans[++cnt].o=o;ans[cnt].d=d;}
    else
    {
        ans[cnt].d+=d;ans[cnt].d%=n;
        if(ans[cnt].d==0) cnt--;
    }
}
bool solve()
{
    if(n%2) return 0;
    ins(1,n-2);ins(2,2);
    for(int i=n;i>4;i-=2) ins(1,n-2),ins(2,2);
    if(n-4) ins(1,n-4);
    return 1;
}
void make()
{
    while(a[1]!=1)
    {
        ins(1,1);
        int t=a[1];a[1]=a[3];a[3]=a[2];a[2]=t;
    }
    if(a[2]==2) print();
    else printf("NIE DA SIE\n");
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    if(ok()) {printf("0\n");return 0;}
    if(n==2) {printf("1\n1a\n");return 0;}
    if(n==3) {make();return 0;}
    for(int k=2;k<=n-2;k++)
    {
        for(int i=2;i<=n;i++)
            if(a[i]==k)
            {
                ins(1,n-i+1);
                int bl=0;
                for(int j=i;j<=n;j++) b[++bl]=a[j];
                for(int j=1;j<i;j++) b[++bl]=a[j];
                for(int j=1;j<=n;j++) a[j]=b[j];
                break;
            }
        int las=0,pre;
        for(int i=1;i<=n;i++) if(a[i]==k-1) {las=i;break;}
        pre=las;
        bool bo=0;
        while(las<n)
        {
            if(las+2<=n) {ins(1,2);ins(2,1);las+=2;}
            else {ins(1,1);ins(2,2);las++;bo=1;}
        }
        int bl=1;
        b[1]=k;
        for(int i=pre+1;i<=n;i++) b[++bl]=a[i];
        for(int i=2;i<=pre;i++) b[++bl]=a[i];
        for(int i=1;i<=n;i++) a[i]=b[i];
        if(bo) swap(a[2],a[3]);
        int oo=1;
    }
    if(a[2]!=n-1) 
    {
        if(solve()==0) printf("NIE DA SIE\n");
        else print();
    }
    else
    {
        ins(1,n-3);
        print();
    }
    return 0;
}

题解:
我的第一感觉,b操作的意义是能把某些数留在前三个位置
开始我想把1留在前三个位置,然后把2转到他旁边。再把1,2留在前三个位置,然后把3转到他们旁边。再。。似乎长度大于等于3就留不住了。。
那1..k留在前三个,把k+1转过去不行,就试试把k+1留在前三个,把1..k转过去吧。。
也就是说,核心在于我们已经在某处构造出了1..k,现在想把k+1加入,就用b操作把k+1留在前三个,然后1..k转一圈接上去。这是容易构造的。
注意这种构造方法是要用前三个位置做缓冲的,所以不破坏已经构造出的序列我们只能造出n-2的状态
此时序列要么是
n-2,n-1,n,1,…,n-3(这已经合法了)
要么是
n-2,n,n-1,1,…,n-3
一开始,我觉得此时必定不合法,因为再用b操作会破坏1..n-2的顺序。
于是自信一WA
下载数据后,发现这样一种情况
6 8 7 1 2 3 4 5是有解的!
看6a 2b 6a 2b 6a 2b后会发生什么
5 6 7 8 1 2 3 4
我们观察out的构造方法(捂脸)
把n-1放在第三个位置,然后(n-2)a让n-1去到第一个位置,再用2b把他放到第三个位置。相比操作前,n-1位置不变,而不看第三个位置所有位置右移n-2位,即左移2位。若干次的目的就是通过不断左移两位把原来在第二位的n移到第四位。
显然第二位可以通过若干次左移两位到达第四位的条件是n%2=0,如果这种方法也不行就是NIE DA SIE了。
这就是整个面向数据设计的算法了。
撒花~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值