xdoj思维练

今天来看两道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;
}

不难发现,这两题都是看起来很容易,然后加入一些小细节之后,就出现了很微妙的变化,最后经过一系列思维转换后又可以顺利写出,可以说确是好题

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值