题目:点击打开链接
这题以上来我就是广搜,然后用字典树判重,写的时候就担心会超内存,结果就真的超了…………然后就没辙了……
这题用到守恒定律,看了别人讲的很好理解。
下面部分摘自:这里这里这里
我们分析一下这个题目。
操作是有先后顺序之分的。比如先对a2操作,再操作a3;先对a3操作,再
操作a2,结果就有天壤之别。
观察Sample:
(1 6 9 4 2 0)à(1 6 13 -4 6 0)à
(1 6 13 2 -6 6)à(7 -6 19 2 -6 6)
数字杂乱无章没有规律。仔细观察一下操作规则:
(ai-1, ai, ai+1)à(ai-1+ai, -ai, ai+ai+1)
直观的看,相当于把中间的数分别加到两边去,然后取反。容易发现,操作
前后的总和是不变的!
我们可能很激动的猜想:只要两个序列和相等,他们就能通过操作互达。
但是第二个sample 很快否定了这个想法:(1 2 3), (1 3 2)是不可达的。
因为(1 2 3)能进行的操作仅仅是:(3 -2 5),再进行一次操作回到(1 2 3)。所
以永远不能变成(1 3 2)。
总和虽然不行,我们可以试着考察局部和,(即前n项和)。
(ai-1, ai, ai+1) (ai-1+ai, -ai, ai+ai+1)
S1=ai-1 S1’=ai-1+ai
S2=ai-1+ai S2’=ai-1
S3=ai-1+ai+ai+1 S3’=ai-1+ai+ai+1
很容易看出S3=S3’,(S1,S2)=(S2’,S1’)。也就是说把(S1,S2,S3)中的S1和S2交换
位置就能得到(S1’,S2’,S3’)。
稍微推广一下:设Si=a1+a2+…+ai,对(ai-1, ai, ai+1)操作本质上和交换Si-1, Si
是等价的。(因为Si-2是固定不变的)
比如第一个Sample:
(1 6 9 4 2 0)à(1 6 13 -4 6 0)à
(1 6 13 2 -6 6)à(7 -6 19 2 -6 6)
转化成和之后:
(1 7 16 20 22 22)à(1 7 20 16 22 22)à
(1 7 20 22 16 22)à(7 1 20 22 16 22)
(加粗的是交换的两个数)
比如第二个Sample:
(1 2 3)àS(1 3 6)
(1 3 2)àS(1 4 6)
对(1 2 3)的操作实质上是不断的交换S(1 3 6)中的1, 3。无论如何也不可
能变成(1 4 6),因为4 根本没在S(1 3 6)中出现过!
另外还有一点,参与交换的只有S1~Sn-1,Sn是雷打不动的。所以我们算法出
来了:
1、判断Sn是否相等。
2、判断{S1, S2 ,…, Sn-1}是否相等。
O(nlogn)的时间复杂度(排序复杂度)。
一个看似繁琐的题目被很轻松的解决了。
如上文所言,操作的变化过程是纷繁复杂的,可以说没任何规律。如果从
每一次操作对总体的贡献入手研究,会碰得头破血流。
但是我们把原来的数列进行了一个小小的转化:求和。正是这个求和,使得
操作的本质浮出水面,操作由令人头晕目眩的(ai-1, ai, ai+1)à(ai-1+ai, -ai, ai+ai+1)、
变成了简单的“交换Si-1和Si”。
这是一个应用守恒的经典例子。其中的守恒量是{S1,S2, …,Sn-1},这个集合始
终保持不变,不会有新的元素生成、也不会无缘无故有元素消失;有的只是顺序
上的交换。
抛开琐碎的细节,我们抓住了本质。
为什么会想到求和呢?
因为(ai-1, ai, ai+1)à(ai-1+ai, -ai, ai+ai+1)的结构很容易让人发现它的总和守恒;进
一步联想到局部和。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int a[1100], b[1100];
int cmp(const void *a, const void *b)
{
return (*(int *)a - *(int *)b);
}
int main (void)
{
int n;
scanf("%d", &n);
while(n --)
{
int m, i;
scanf("%d", &m);
for(i = 0; i < m; i++)
scanf("%d", &a[i]);
for(i = 0; i < m; i++)
scanf("%d", &b[i]);
for(i = 1; i < m; i++)
{
a[i] += a[i - 1];
b[i] += b[i - 1];
}
qsort(a, m , sizeof(a[0]), cmp);
qsort(b, m, sizeof(b[0]), cmp);
int f = 0;
for(i = 0; i < m; i++)
{
if(a[i] != b[i])
{
f = 1;
break;
}
}
if(f == 1)
printf("No\n");
else
printf("Yes\n");
}
return 0;
}
广搜超内存代码:
#include <stdio.h>
#include <string.h>
#include <queue>
using namespace std;
struct node
{
int s[1010];
};
struct tree
{
int i;
tree* next[2010];
tree()
{
for(i = 0; i < 2010; i++)
next[i] = NULL;
}
};
queue <node> q;
node start, end;
int n;
int vis(tree* head, node e)
{
tree * temp = head;
int i;
int t, ok = 1;
for(i = 0; i < n; i++)
{
t = e.s[i] + 1000;
if(temp ->next[t] == NULL)
{
ok = 0;
tree *x = new tree;
temp->next[t] = x;
}
temp = temp ->next[t];
}
return ok;
}
int cmp(int a[], int b[])
{
int i;
for(i = 0; i < n; i++)
{
if(a[i] != b[i])
return 0;
}
return 1;
}
int bfs()
{
tree * head = new tree;
int i;
q.push(start);
int ok = vis(head, start);
while(!q.empty())
{
node e = q.front();
q.pop();
for(i = 1; i < n - 1; i++)
{
node e1 = e;
e1.s[i - 1] = (e1.s[i]) + (e1.s[i - 1]);
e1.s[i + 1] = (e1.s[i]) + (e1.s[i + 1]);
e1.s[i] = -(e1.s[i]);
if(vis(head, e1) == 0)
{
if(cmp(e1.s, end.s) == 1)
return 1;
q.push(e1);
}
}
}
return 0;
}
int main (void)
{
int t;
scanf("%d", &t);
while(t --)
{
int i;
while(!q.empty())
q.pop();
scanf("%d", &n);
for(i = 0; i < n; i++)
scanf("%d", &start.s[i]);
for(i = 0; i < n; i ++)
scanf("%d", &end.s[i]);
if(bfs() == 1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}