2016 香港网络赛

A - A+B Problem(传送门)

题意

给定一个数组,让你找出满足如下式子的元组个数,其中下标可以升序也可以降序

ai+aj=ak

其中 N<200000

解题思路

FFT ,将负数处理为正数,正对重复的情况进行处理:

  1. 当选中的数字中有 0 时会造成重复
  2. 当选中了两个相等的数字的时候会造成重复

将这两种情况分开处理即可

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

#define FIN freopen("input.txt", "r", stdin)

using namespace std;

typedef long long LL;
const int MAXN = (1 << 18) + 5;
const int SIZE = 5e4 + 5;
const double PI = acos(-1.0);

struct complex {
    double r, i;
    complex(double _r = 0.0, double _i = 0.0) {
        r = _r;
        i = _i;
    }
    complex operator +(const complex &b) {
        return complex(r + b.r, i + b.i);
    }
    complex operator -(const complex &b) {
        return complex(r - b.r, i - b.i);
    }
    complex operator *(const complex &b) {
        return complex(r * b.r - i * b.i, r * b.i + i * b.r);
    }
};

void change(complex y[], int len) {
    int i, j, k;
    for(i = 1, j = len / 2; i < len - 1; i++) {
        if(i < j)swap(y[i], y[j]);
        k = len / 2;
        while(j >= k) {
            j -= k;
            k /= 2;
        }
        if(j < k) j += k;
    }
}

void fft(complex y[], int len, int on) {
    change(y, len);
    for(int h = 2; h <= len; h <<= 1) {
        complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h));
        for(int j = 0; j < len; j += h) {
            complex w(1, 0);
            for(int k = j; k < j + h / 2; k++) {
                complex u = y[k];
                complex t = w * y[k + h / 2];
                y[k] = u + t;
                y[k + h / 2] = u - t;
                w = w * wn;
            }
        }
    }
    if(on == -1)
        for(int i = 0; i < len; i++)
            y[i].r /= len;
}

int n;
LL B[MAXN];
LL A[MAXN];
complex x[MAXN];

int main() {
    //FIN;
    int t;
    while(~scanf("%d", &n)) {
        int len = 1 << 18;//必须为2的指数幂
        memset(A, 0, sizeof(A));
        int cnt = 0;
        for(int i = 0; i < n; i ++) {
            scanf("%lld", &B[i]);
            A[SIZE + B[i]] ++;
            if(B[i] == 0)cnt ++;
        }
        for(int i = 0; i < len; i ++) {
            x[i] = complex(A[i], 0);
        }
        fft(x, len, 1);
        for(int i = 0; i < len; i ++) {
            x[i] = x[i] * x[i];
        }
        fft(x, len, -1);
        LL ans = 0;
        for(int i = 0; i < len; i ++) {
            A[i] = (LL)(x[i].r + 0.5);
        }
        for(int i = 0; i < n; i ++) {
            A[B[i] * 2 + SIZE * 2] --;
        }
        for(int i = 0; i < n; i ++) {
            if(B[i] != 0) {
                ans += A[B[i] + SIZE * 2];
                ans -= cnt * 2;
            } else {
                ans += A[B[i] + SIZE * 2];
                ans -= (cnt - 1) * 2;
            }
        }
        printf("%lld\n", ans);
    }
    return 0;
}

B - Boxes(传送门)

题意

给定几个方块和他们的包含与被包含的关系,找出给定的几个方块中包含了多少个方块

解题思路

直接暴力,判断两个方块是否有包含关系,如果有,去掉那个被包含的方块,接着统计数目

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
const int MAXN = 2e5 + 5;

int n;

int par[MAXN];
vector<int>B[MAXN];

void init(int n){
    for(int i = 0;i <= n;i ++){
        par[i] = i;
    }
    for(int i = 0;i <= n;i ++) B[i].clear();
}

void B_merge(int u, int v){
    par[u] = v;
}

bool _find(int u, int v){
    if(u == v) return true;
    if(par[v] == v) return false;
    return _find(u, par[v]);
}

int sum(int u){
    int ret = 1;
    for(int i = 0;i < B[u].size();i ++){
        ret += sum(B[u][i]);
    }
    return ret;
}

int K[MAXN];

int main() {
    while(~scanf("%d", &n)){
        init(n);
        int x;
        for(int i = 1;i <= n;i ++){
            scanf("%d", &x);
            if(x == 0) continue;
            B_merge(i, x);
            B[x].push_back(i);
        }
        int q, m;
        scanf("%d", &q);
        while(q --){
            scanf("%d", &m);
            for(int i = 0;i < m;i ++){
                scanf("%d", &K[i]);
            }
            for(int i = 0;i < m;i ++){
                for(int j = i + 1;j < m;j ++){//去掉被包含
                    if(_find(K[i], K[j])){
                        K[j] = -1;
                    }
                    else if(_find(K[j], K[i])){
                        K[i] = -1;
                    }
                }
            }
            int ans = 0;
            for(int i = 0;i < m;i ++){
                if(K[i] == -1) continue;
                ans += sum(K[i]);
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}

C - Classrooms(传送门)

题意

给定n个活动和 k 个班级,限定了第i个活动的开始时间 si 和结束时间 ti ,每一个班级每一时刻多有只能进行一个活动,求最多可以进行多少个活动

解题思路

针对每一个班级,处理结束时间靠前并且开始时间离自己近的活动,这是为了让其他的班级更好的拥有更多的时间去进行其它的活动,用 multimap 来处理数据就可以了

代码

#include <cstdio>
#include <cstring>
#include <queue>
#include <map>
#include <algorithm>


#define FIN freopen("input.txt", "r", stdin)

using namespace std;

typedef pair<int, int> PII;
const int MAXN = 4e5 + 5;

int n, k;

struct o {
    int l, r, sz;
    bool operator < (const o &p) const {
        return r < p.r;
    }
} O[MAXN];

multimap<int,int>MT;

int main() {
    //FIN;
    while(~scanf("%d%d", &n, &k)) {
        MT.clear();
        for(int i = 0; i < n; i ++) {
            scanf("%d%d", &O[i].l, &O[i].r);
        }
        sort(O, O + n);
        for(int i = 0;i < k;i ++){
            MT.insert(make_pair(0, 0));//初始化每一个班级的状态
        }
        multimap<int,int>::iterator it;
        for(int i = 0;i < n;i ++){
            o &p = O[i];
            it = MT.lower_bound(p.l);//找离自己最近的
            if(it == MT.begin()) continue;
            it --;
            PII np = PII(p.r, it -> second + 1);
            MT.erase(it);
            MT.insert(np);
        }
        int sum = 0;
        for(it = MT.begin();it != MT.end();it ++){
            sum += it -> second;
        }
        printf("%d\n", sum);
    }
    return 0;
}

D - Curious Cupid(传送门)

题意

给定有 n 个男人和n个女人,每一个人只会说一种语言,给定一个区间 [L,R] ,求解这个区间可以说上话即会说同一种话的人的匹配数目

解题思路

莫队算法处理离散区间问题时间复杂度为 O(N(N)) ,他的操作仅仅只是改变了操作数据的顺序,先将 L 限定在一个区间,然后按照R进行排序,具体详情,请读者学习

代码

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>

#define FIN freopen("input.txt", "r", stdin)
using namespace std;
const int MAXN = 5e4 + 5;
const int MAXM = 1e6 + 5;

struct o {
    static int unit;
    int l, r, idx;
    bool operator < (const o &p) const {
        if(l / unit != p.l / unit) return l / unit < p.l / unit;
        return r < p.r;
    }
} O[MAXN];

int o::unit = 1;

int A[MAXN], B[MAXN];
int ans[MAXN];
int numA[MAXM], numB[MAXM];

int sum;

void add(int pos) {
    if(A[pos] == B[pos]) {
        sum += 1;
        numA[A[pos]] ++, numB[B[pos]] ++;
        return;
    }
    sum -= min(numA[A[pos]], numB[A[pos]]);
    sum -= min(numB[B[pos]], numA[B[pos]]);
    numA[A[pos]] ++;
    numB[B[pos]] ++;
    sum += min(numA[A[pos]], numB[A[pos]]);
    sum += min(numB[B[pos]], numA[B[pos]]);
}

void remove(int pos) {
    if(A[pos] == B[pos]) {
        sum -= 1;
        numA[A[pos]] --, numB[B[pos]] --;
        return;
    }
    sum -= min(numA[A[pos]], numB[A[pos]]);
    sum -= min(numB[B[pos]], numA[B[pos]]);
    numA[A[pos]] --;
    numB[B[pos]] --;
    sum += min(numA[A[pos]], numB[A[pos]]);
    sum += min(numB[B[pos]], numA[B[pos]]);
}

int N, M, K;
int main() {
    //FIN;
    while(~scanf("%d%d%d", &N, &M, &K)) {
        memset(numA, 0, sizeof(numA));
        memset(numB, 0, sizeof(numB));
        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 = 0; i < M; i ++) {
            scanf("%d%d", &O[i].l, &O[i].r);
            O[i].l ++;
            O[i].r ++;
            O[i].idx = i;
        }
        o::unit = (int)sqrt(N);
        sort(O, O + M);
        int L = 1, R = 0;
        sum = 0;
        //[]L,R代表左闭右闭区间
        for(int i = 0; i < M; i ++) {
            while(L < O[i].l) {
                remove(L ++);
            }
            while(L > O[i].l) {
                add(-- L);
            }
            while(R < O[i].r) {
                add(++ R);
            }
            while(R > O[i].r) {
                remove(R --);
            }
            ans[O[i].idx] = sum;
        }
        for(int i = 0; i < M; i ++) {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}

F - Crazy Driver(传送门)

题意

在一条线上有若干个门,每一个门之间有一条路,从一个门开车到另外一个门的时间为 1 小时,但是花费为C[i],对于每一个门而言,如果开车者想要通过这个门那么就必须等到时间 T[i] 之后才能开过这个门,同时还限制了开车者热情饱满,必须一直开车不能停下来,求最终通过最后一个门的最短时间

解题思路

  1. 方法一就是单纯的模拟,个人提供的代码就是这个,模拟真实开车者取得最有状态时的开车路径即可,针对当前这条路,不断检测接着往下走是否会更优

  2. 方法二就是贪心,针对沿途遇到的最小边进行来回走动即可,认真处理这个事件 1A 没问题的

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

#define FIN freopen("input.txt", "r", stdin)
#define FOUT freopen("output.txt", "w", stdout);
typedef long long LL;
using namespace std;

const int MAXN = 1e5 + 5;
const int MAXM = 1e6 + 5;
const LL INF = 1e14 + 5;

int n;
LL T[MAXN];
LL C[MAXN];
LL dp[MAXN], mindp[MAXN], _rank[MAXN], cdp[MAXN], sum[MAXN];
/*
dp[i]表示到达i这个门时的最小时间花费
mindp[i]表示到i这个门最小花费的边是哪一条
_rank[i]表示到i这个门最小花费的边的前一个门是哪一个
cdp[i]表示到i这个门总花费最小为多少
sum[i]表示从1门到i门的花费前缀和
*/
int main() {
    //FIN;
    //FOUT;
    while(~scanf("%d", &n)) {
        for(int i = 1; i < n; i ++) {
            scanf("%lld", &C[i]);
        }
        for(int i = 1; i <= n; i ++) {
            scanf("%lld", &T[i]);
        }
        sum[1] = 0;
        for(int i = 2;i <= n;i ++){
            sum[i] = sum[i - 1] + C[i - 1];
        }
        for(int i = 0;i <= n;i ++){
            dp[i] = 0;
            mindp[i] = INF;
            cdp[i] = INF;
            _rank[i] = i;
        }
        for(int i = 2;i <= n;i ++){
            T[i] = max(T[i], T[i - 1] + 1);
            if(mindp[i] > C[i - 1]){
                mindp[i] = C[i - 1];
                _rank[i] = i - 1;
            }
            if(mindp[i] > mindp[i - 1]){
                mindp[i] = mindp[i - 1];
                _rank[i] = _rank[i - 1];
            }
        }
        dp[1] = cdp[1] = 0;
        for(int i = 2;i <= n;i ++){
            LL ct = T[i] - dp[_rank[i]];
            if(ct <= i - _rank[i]){
                cdp[i] = cdp[_rank[i]] + sum[i] - sum[_rank[i]];
                dp[i] = dp[_rank[i]] + i - _rank[i];
            }
            else{
                ct -= i - _rank[i];
                if(ct & 1) ct ++;
                cdp[i] = cdp[_rank[i]] + sum[i] - sum[_rank[i]] + mindp[i] * ct;
                dp[i] = dp[_rank[i]] + ct + i - _rank[i];
            }
        }
        printf("%lld\n", cdp[n]);
    }
    return 0;
}

J - 0-1 Sequences(传送门)

题意

给定一个由 0,1,? 三种字符组成的字符串,求让这个字符串变为不降序的操作和为多少

解题思路

从后面往前面进行遍历不断的找寻零的个数,当遇到 1 ,这些后面的0就都需要移动到这个 1 <script type="math/tex" id="MathJax-Element-1245">1</script>的前面,如此反复即可

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
const int MAXN = 5e5 + 5;
char S[MAXN];
int main() {
    while(~scanf("%s", S)) {
        int len = strlen(S);
        LL ans = 0, s0 = 0, sc = 1;
        for(int i = len - 1;i >= 0;i --){
            if(S[i] == '?'){
                ans = (ans * 2LL + s0) % mod;
                s0 = (s0 * 2LL + sc) % mod;
                sc = sc * 2LL % mod;
            }
            else if(S[i] == '1'){
                ans = (ans + s0) % mod;
            }
            else{
                s0 = (s0 + sc) % mod;
            }
        }
        printf("%I64d\n", ans);
    }
    return 0;
}

K - Take Two Stones(传送门)

题意

搬石头,意思自己懂的

解题思路

不想说了,看代码就知道多简单了,尴尬

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int MAXN = 2e5 + 5;

int n;

int main() {
    while(~scanf("%d", &n)){
        if(n & 1) printf("Alice\n");
        else printf("Bob\n");

    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值