2015-2016 Northwestern European Regional Contest (NWERC 2015) 补题

这篇博客探讨了如何将管道问题转化为判断二分图,并介绍了利用并查集判断二分图的方法。此外,还讨论了代码调试问题,通过对比dp和记忆化搜索,解释了为何记忆化搜索在解决此类问题上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C.Cleaning Pipes

题意:给出n条管道(线段),任意两个管道之间之多有一个交点,忽略一个管道头部和其他管道的交点,问能否选出一个不相交的管道集合来使得所有交点都被这些管道覆盖。

思路:将管道抽象成点,两个管道的交点抽象成边,那么问题转化成判断新建的图是否是一个二分图。

判断二分图最常用的方法就是染色法,本题当然也就可以用,然而我看dalao的代码还学了另一种方法:利用并查集判断,感觉这种方法的本质就是利用二分图不含奇环的性质(我自己yy的)。

具体做法就是将点集扩大两倍,i,j之间有边就merge(i, j + n),merge(i + n, j).最后判断一下i和i + n是否属于同一个集合。如果有一个属于同一个集合,则说明该图不是二分图。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 2020;
struct P{
    int x, y;
    P(int _x = 0, int _y = 0) : x(_x), y(_y) {}
    bool operator == (P &b)
    {
        return x == b.x && y == b.y;
    }
}well[MAXN], L[MAXN], R[MAXN];
//计算向量p1p2与向量p1p3的叉积,若p1p3在p1p2的逆时针方向,则返回>0,顺时针方向返回<0
int mul(P p1, P p2, P p3)
{
    return (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y);
}
bool intersect(P p1,P p2,P q1,P q2)
{
//快速排斥
    if(max(p1.x,p2.x)<min(q1.x,q2.x)||
       max(q1.x,q2.x)<min(p1.x,p2.x)||
       max(p1.y,p2.y)<min(q1.y,q2.y)||
       max(q1.y,q2.y)<min(p1.y,p2.y))
    return 0;
//跨立试验
    if(1ll * mul(p1,p2,q1)*mul(p1,p2,q2) <= 0 && 1ll * mul(q1,q2,p1)*mul(q1,q2,p2) <= 0)
    return 1;
    return 0;
}
int f[MAXN];
int getf(int k)
{
    return k == f[k] ? k : f[k] = getf(f[k]);
}
void merge(int x, int y)
{
    x = getf(x); y = getf(y);
    f[x] = y;
}
int main()
{
    int n, m, t;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    scanf("%d %d", &well[i].x, &well[i].y);
    for(int i = 1; i <= m; i++)
    {
        scanf("%d %d %d", &t, &R[i].x, &R[i].y);
        L[i] = well[t];
        f[i] = i;
        f[m + i] = m + i;
    }
    int x, y;
    for(int i = 1; i <= m; i++)
    for(int j = i + 1; j <= m; j++)
    {
        if(L[i] == L[j]) continue;
        if(intersect(L[i], R[i], L[j], R[j]))
        {
            merge(i, j + m);
            merge(i + m, j);
        }
    }
    for(int i = 1; i <= m; i++)
    if(getf(i) == getf(i + m))
    {
        cout << "impossible"; return 0;
    }
    cout << "possible";
}

D.Debugging

题意:要调试一个n行的代码,有两种方法:

1. 在任意行加printf语句,花费p时间

2. 运行代码,花费r时间

问在你采取最优策略的情况下,最坏情况需要花费多少时间。

思路:一上来就想到了那个扔鸡蛋的问题。。感觉特别相似。。

队友想了一个 n^2 的 dp转移,还用尽办法用线段树优化到n(logn)^2,然而还是t了,归根结底是因为用dp转移的话会有很多无用的状态被计算,想要避免这些无用计算,就要把for循环dp换成记忆化搜索。

基本转移方程:

设dp[n] 表示在最坏的情况下debug  n行代码所需要的最少的时间.
dp[n] = min((i-1)*p+dp[ceil(n/i)])+r;
也就是枚举将n尽量等分成i段,bug出现在划分出的最长的段里,然后进行递归计算。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll r, p, dp[1000010];
ll ceil(ll n, ll m)// n / m 取上整数
{
    return (n + m - 1) / m;
}
ll dfs(ll n)
{
    if(dp[n] != -1) return dp[n];
    ll ans = 1e18;
    for(int i = 2; i <= n; i++)
    ans = min(ans, 1ll * (i - 1) * p + dfs(ceil(n, i)) + r);
    return dp[n] = ans;
}
int main()
{
    ll n;
    memset(dp, -1, sizeof(dp));
    dp[0] = dp[1] = 0;
    cin >> n >> r >> p;
    cout << dfs(n);
}
这样求解已经很快了,但是大佬们还想出了更强的优化方法: 点击打开链接
疯狂Orz

G.Guessing Camels
题意:给出三个序列,问这三个序列当中有多少对数是同样偏序的。
思路:逆向思维,用总数减去在三个序列中不是同样偏序的数对的数量。
现在就是考虑怎么求不满足的对数.
  我们可以发现不满足偏序性质的这一对,一定是在某两个序列里偏序关系是相同的,那么我们可以对每两个序列求一次不满足题意的偏序对的个数.但是这样算的话,同一个数对会被计算两次,所以把最后的答案除以2就是最终的结果了.
然后问题变成怎么求两个序列中不同偏序的数对个数。
将第一个数组看成标准序列,另一个数组看成一种标准序列的排列,那么问题就又转化成了类似求逆序对的个数,用树状数组搞搞就好了。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 200020;
int a[MAXN], b[MAXN], c[MAXN], pos[MAXN], bit[MAXN];
int n;
int sum(int i)
{
    int res = 0;
    while(i)
    {
        res += bit[i];
        i -= i & -i;
    }
    return res;
}
void add(int i, int delta)
{
    while(i <= n)
    {
        bit[i] += delta;
        i += i & -i;
    }
}
ll solve(int *x, int *y)
{
    memset(bit, 0, sizeof(bit));
    for(int i = 1; i <= n; i++) pos[x[i]] = i;
    ll ans = 0;
    for(int i = n; i >= 1; i--)
    {
        ans += sum(pos[y[i]]);
        add(pos[y[i]], 1);
    }
    return ans;
}
int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) scanf("%d", a + i);
    for(int i = 1; i <= n; i++) scanf("%d", b + i);
    for(int i = 1; i <= n; i++) scanf("%d", c + i);
    ll ans = 1ll * (n - 1) * n / 2;
    ans -= (solve(a, b) + solve(b, c) + solve(c, a)) / 2;
    cout << ans << endl;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值