今天来看两道xdoj比较有意思的题目,来自于第一届新生赛网络赛,题目并不大都是水题,还是有耐人寻味的地方的
Problem I Arch0n's LED
思路:其实这题完全可以转化为二维平面求最大曼哈顿距离的题目,传统做法就是枚举比较,对于n=1e5,直接将O(N^2)的复杂度卡掉,立刻就显得很有意思了
由于这题信息量比较小,研究一下这个公式倒是个比较不错的入手点
|fx - fy| + |bx - by|
绝对值在多数情况下都是一个特别棘手的问题,先考虑把绝对值去掉,那么就分成四种情况了
1. fx - fy + bx - by
2. fx - fy + by - bx
3. fy - fx + bx - by
4. fy - fx + by - bx
移一下项,将相同属性移至一侧
1. (fx + bx) - (fy + by)
2. (fx - bx) - (fy - by)
3. (fy - by) - (fx - bx)
4. (fy + by) - (fx + bx)
绝对值相加一定最大,那么求一下上四式最大值就好了
不难发现,此时只需要维护一下每个点f与b和,然后将最大值与最小值相减即可得到max(1式,4式)
再维护一下每个点f与b差,然后将最大值与最小值相减即可得到max(2式,3式)
由于直接排序可能也会超时,于是遍历一遍找最值,最后将两次维护结果取个最大值就ok了,感觉这个降复杂度的方式确实很巧妙
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <map>
#include <cmath>
#include <string>
#include <queue>
#include <stack>
using namespace std;
const int maxn = 1e5+10;
/*typedef struct LED
{
int f;
int b;
//int fb[42];
int fb1;
int fb2;
}Led;
Led led[maxn];
*/
//int pos;
/*
bool cmp1(const Led &p, const Led &q)
{
return p.fb1 > q.fb1;
}
bool cmp2(const Led &p, const Led &q)
{
return p.fb2 > q.fb2;
}
*/
/*int myabs(int k)
{
if(k < 0)
{
k *= (-1);
}
return k;
}*/
int main()
{
int t;
cin >> t;
while(t--)
{
int n;
cin >> n;
int maxs = -2e9-10,maxd = -2e9-10,mins = 2e9+10,mind = 2e9+10;
int f,b;
for(int i=0;i<n;i++)
{
cin >> f >> b;
if(f+b > maxs)
{
maxs = f+b;
}
if(f+b<mins)
{
mins = f+b;
}
//int di = myabs(f-b);
if(f-b < mind)
{
mind = f-b;
}
if(f-b > maxd)
{
maxd = f-b;
}
/*cin >> led[i].f >> led[i].b;
led[i].fb1 = led[i].f + led[i].b;
led[i].fb2 = led[i].f - led[i].b;*/
/*led[i].fb[2] = - 1 * led[i].fb[0];
led[i].fb[3] = - 1 * led[i].fb[1];*/
}
/*sort(led,led+n,cmp1);
int maxdif = led[0].fb1 - led[n-1].fb1;
for(int i=0;i<42;i++)
{
pos = i;
sort(led,led+n,cmp2);
int re = led[0].fb2 - led[n-1].fb2;
if(re > maxdif)
{
maxdif = re;
}*/
int maxdif = max((maxd - mind),(maxs - mins));
//}
cout << maxdif << endl;
}
return 0;
}
Problem G Two Types of People
思路:这个有点二分图的意味,第一下想到的是建图+bfs+染色,这个倒是个可行的方式,不过这个写起来确实不是很方便,而且,由于可能存在错误信息,还必须把建图顺序记录下来,这个就更困难了
换个思路想想,如果这题改为所给边的两侧人属于同一个种类,那么一个并查集就能轻松解决了,于是就开始考虑,这种情况,能不能稍微修改一下并查集呢?
答案是肯定的,而且并查集按所给顺序模拟,完全可以将错误信息排除。那么,对于同类到异类的改变怎么办呢?
当然想到了异或操作,开一个bool数组记录每个人与其祖先的种类关系,如果祖先不同,当然不能判断;如果祖先相同,就可以根据异或操作,利用共同祖先间接判断了。而对于并查集中间合并查找的的过程也很容易,当每次祖先修改的时候,当然就要修改其与祖先的关系,修改方式如下
对于x, x与先祖先的关系 = x与原祖先的关系 ^ 原祖先与现祖先的关系
这不难发现,这个转化是个三角的转化,中间不能有任何跳跃,于是并查集还是老老实实用递归的写法吧,循环写法虽然效率高,但中间过多简化步骤,显然对上面的公式不适用
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <map>
#include <cmath>
#include <string>
#include <queue>
#include <stack>
using namespace std;
const int maxn = 1e5+10;
int pre[maxn];
bool di[maxn];
string re[3] = {"In the same category.\n","In different category.\n","Not sure yet.\n"};
/*int finds(int p)
{
if(p!=pre[p])
pre[p]=finds(pre[p]);
return pre[p];
}*/
int finds(int k)
{
if(pre[k] == k)
{
return k;
}
else
{
int x = finds(pre[k]);
di[k] = di[k] ^ di[pre[k]];
pre[k] = x;
}
return pre[k];
/*int x = k;
while(pre[x]!=x)
{
x = pre[x];
}
while(pre[k]!=x)
{
int temp = pre[k];
//di[]
pre[k] = x;
k = temp;
}
return x;*/
}
void connect(int x,int y)
{
int tx = finds(x);
int ty = finds(y);
if(tx == ty)
{
return ;
}
pre[tx] = ty;
di[tx] = di[x]^di[y]^1;
return ;
}
int same(int x,int y)
{
if(finds(x) != finds(y))
{
return 2;
}
else
{
return di[x]^di[y];
}
}
int main()
{
int n,m;
/*bool tt = 0^0;
cout << tt << endl;*/
while(cin >> n >> m)
{
for(int i=0;i<=n;i++)
{
pre[i] = i;
di[i] = false;
}
while(m--)
{
int p,x,y;
cin >> p >> x >> y;
if(p == 0)
{
connect(x,y);
}
else
{
cout << re[same(x,y)];
}
}
}
return 0;
}
不难发现,这两题都是看起来很容易,然后加入一些小细节之后,就出现了很微妙的变化,最后经过一系列思维转换后又可以顺利写出,可以说确是好题