NOIP 2005 Senior 3

来源:互联网 发布:祛痘消炎的药膏知乎 编辑:程序博客网 时间:2024/05/19 00:17

传送门(OpenJudge)
过了很久重拾此题,发现正好用到了昨天做的题 ( (93) sequence(JZOJ) ) 中的差分判断思想。又发现好像我过去连题意都理解错了。。。

首先是构造一个合法序列。很容易得知,如果一个人要和自己相邻,或者有多于两个人要与某个人相邻,那么一定是不合法的。然后我们就可以直接用循环代替DFS在“图”中找一个环。如果找到的环的大小不为n,说明一定不能满足,否则一定有解。(用循环找,有没有可能找到了一条σ形状的环呢?不可能!如果有,那么闭合处的点的度为3,已经被排除了)

于是我们得到了一个序列,代表最终的合法序列。当然,根据题意,这个序列应该是一个环,也就是说如果我们要用链来表示,它将有n个等价的表示。另外根据题意,这个环还可以反着来,也就是说总共有2n个线型序列来表示这个合法的环。
然后就是根据题意来换了。观察题意后发现,这不就是某某年又某某年初赛的某某题吗,首先如果位置对了的人就不用换了,剩下要换的交换顺序一定构成一个圈(如 1换2,2换4,4换1),这正好和题目中的操作一样。又因为操作的代价按人头算,因此题目实际上是要求 (n - 位置对了的人的数量的最大值)。
朴素算法就是把所有合法序列拿来和(1,2,3,…,n)一一对比,时间复杂度O(n^2)。有没有更快的方法呢?

我们换个旋转方法。假设合法的序列(设为A[1 ~ n])不动,原序列(设为B[1 ~ n])做形如(1,2,3,…,n)到(2,3,4,…,n,1)的变化。
我们在A中假设只留下位置已经正确了的。虽然不知道是哪些,但是我们知道,如果这些正确的为A[a1]A[a2]A[a3]...A[ak],对应在B中为B[b1]B[b2]B[b3]...B[bk],那么ai+1ai=bi+1bi如果有负数或者超过n,进行类似求余的计算使范围在1 ~ n 即可。(这里仔细想想是不是,实在不懂的,画两条线段等距离地点几个点就一目了然了)

然后你就知道了:

ai+1bi+1=aibi

对于这道题,等价于:
ai+1(i+1)=aii

也就是说,它们可能值不一样,但是如果差一样,就能通过旋转B来得到一样的。
所以统计差值。出现次数最大的差值的次数即为位置对了的人的数目的最大值。

参考代码

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>#include <bitset>typedef int INT;using std::cin;using std::cout;using std::endl;inline INT readIn(){    INT a = 0;    bool minus = false;    char ch = getchar();    while (!(ch == '-' || (ch >= '0' && ch <= '9'))) ch = getchar();    if (ch == '-')    {        minus = true;        ch = getchar();    }    while (ch >= '0' && ch <= '9')    {        a = a * 10 + (ch - '0');        ch = getchar();    }    if (minus) a = -a;    return a;}inline void printOut(INT x){    char buffer[15];    INT length = 0;    bool minus = x < 0;    if (minus) x = -x;    do    {        buffer[length++] = x % 10 + '0';        x /= 10;    }    while (x);    if (minus) buffer[length++] = '-';    do    {        putchar(buffer[--length]);    }    while (length);    putchar('\n');}const INT maxn = 50005;INT n;INT a[2][maxn];INT in[maxn];INT seq[maxn];bool vis[maxn];INT ans;INT sub[maxn];void calc(){    memset(sub, 0, sizeof(sub));    for(int i = 1; i <= n; i++)    {        INT x = seq[i] - i;        if(x <= 0) x += n;        sub[x]++;        ans = std::min(ans, n - sub[x]);    }}void run(){    n = readIn();    for(int i = 1; i <= n; i++)    {        in[a[0][i] = readIn()]++;        in[a[1][i] = readIn()]++;        if(a[0][i] == i || a[1][i] == i || in[a[0][i]] > 2 || in[a[1][i]] > 2)        {            printOut(-1);            return;        }    }    INT person = 1;    while(seq[0] < n)    {        seq[++seq[0]] = person;        vis[person] = true;        if(!vis[a[0][person]]) person = a[0][person];        else if (!vis[a[1][person]]) person = a[1][person];        else if(seq[0] < n)        {            printOut(-1);            return;        }    }    ans = n;    calc();    std::reverse(seq + 1, seq + 1 + n);    calc();    printOut(ans);}int main(){    run();    return 0;}
原创粉丝点击