Educational Codeforces Round 36

比赛链接

小号打了一场E round,被连hack两道= =

A. Garden

题意:

给n个数和一个m,问n个数中能整除m的并且商最小的那个商是多少。

做法:

傻逼题。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cctype>
using namespace std;

const int N = 110;
int n, m;
int a[N];

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
    sort(a+1, a+1+n);
    for(int i = n; i >= 1; i --)
        if(m%a[i] == 0) {
            printf("%d\n", m/a[i]);
            return 0;
        }
}

B. Browser

题意:

有一条1~n的路,每个点有一盏灯,开始的时候灯都是开着的。
给一个l,r,让你最终使得只有[l,r]范围内的灯是开着的。
有这样几种操作:
1. 向左/右走一步。
2. 把当前位子左/右边的灯全部关掉。
每种操作都要花费1的代价。
问最小代价。

做法:

也挺傻逼的。
肯定最终是走到l,r上面去关灯。
随便贪心一下就可以了。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
using namespace std;

int n, pos, l, r;

int main()
{
    scanf("%d%d%d%d", &n, &pos, &l, &r);
    if(l == 1 && r == n) { puts("0"); return 0; }
    if(l == 1) {
        printf("%d\n", abs(pos-r)+1); return 0;
    } else if(r == n){
        printf("%d\n", abs(pos-l)+1); return 0;
    }
    if(l <= pos && pos <= r) {
        printf("%d\n", r-l+min(r-pos, pos-l)+2);
        return 0;
    }
    if(pos < l) {
        printf("%d\n", r-pos+2);
    } else printf("%d\n", pos-l+2);
    return 0;
}

C. Permute Digits

题意:

给一两个数A,B,现在让你把A里的数字随便排列(不能有前导零),求最大的不超过B的数字。

做法:

这题被hack了!TAT
首先直接的贪心是错的。
比如一个这样的数据:
123
212
直接贪心会使得A的前两位放21于是第三位就没数可以放了。
于是一个简单的想法就是dfs。
但是你dfs太多层会炸。。
所以每次只dfs两层,找两个不同的并且是最大和次大的数,dfs下去就可以了。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

const int N = 50;
char a[N], b[N];
int l1, l2;
int s1[N], vis[N], ans[N];

inline void dfs(int k, int flag)
{
    if(k > l1) {
        for(int i = 1; i < k; i ++) printf("%d", ans[i]);
        exit(0);
    }
    int now = b[k]-'0';
    int q[20], top = 0;
    for(int i = l1; i >= 1; i --) if(!vis[i]) {
        if(!flag) q[++ top] = i;
        else if(s1[i] <= now) q[++ top] = i;
    }
    if(!top) return; int sum = 0;
    vis[q[1]] = 1; ans[k] = s1[q[1]]; dfs(k+1, flag && (ans[k] == now)); vis[q[1]] = 0;
    for(int i = 2; i <= top; i ++) if(s1[q[i]] != s1[q[1]]) {
        vis[q[i]] = 1; ans[k] = s1[q[i]];
        dfs(k+1, 0);
        vis[q[i]] = 0;
        break;
    }
}
int main()
{
    scanf("%s%s", a+1, b+1);
    l1 = strlen(a+1), l2 = strlen(b+1);
    for(int i = 1; i <= l1; i ++) s1[i] = a[i]-'0';
    sort(s1+1, s1+1+l1);
    if(l1 < l2) {
        for(int i = l1; i >= 1; i --) printf("%d", s1[i]);
        return 0;
    }
    dfs(1, 1);
    return 0;
}

D. Almost Acyclic Graph

题意:

给一张有向无环图。问你是否能去掉一条边使得这个图变成一个dag。

做法:

又被hack了TAT!!
原先的想法是说你dfs找出所有的环,然后判断是否有一条边能被所有环覆盖。
然后发现那个dfs是假的(逃
所以不要管上面那坨扯淡的东西。。

来说正解,你先做一个拓扑排序。假如这个时候已经没有环了就yes。
否则,枚举每个点i,in[i]–,表示去掉一条指向i的边。然后你再去拓扑排序,如果没有环了就yes.

代码:

//两遍拓扑
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<queue>
using namespace std;

const int N = 510, M = 100010;
int n, m, cnt, l, r, ll, rr;
int head[N], in[N], d[N], q[M];
struct edge{
    int to, nxt;
    edge() {}
    edge(int x, int y) { to = x, nxt = y; }
}e[M];

inline void addedge(int x, int y) { e[++ cnt] = edge(y, head[x]); head[x] = cnt; }
inline bool tp(int x)
{
    ll = l; rr = r; q[++ r] = x;
    for(int j = 1; j <= n; j ++) d[j] = in[j]; d[x] --;
    while(l <= r) {
        int u = q[l ++];
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            d[v] --;
            if(!d[v]) q[++ r] = v;
        }
    }
    bool flag = (r == n);
    l = ll, r = rr;
    return flag;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i ++) {
        int x, y; scanf("%d%d", &x, &y);
        addedge(x, y); in[y] ++;
    }
    l = 1, r = 0;
    for(int i = 1; i <= n; i ++) if(!in[i]) q[++ r] = i;
    while(l <= r) {
        int u = q[l ++];
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            in[v] --;
            if(!in[v]) q[++ r] = v;
        }
    }
    if(r == n) { puts("YES"); return 0; }// puts("orz");
    for(int i = 1; i <= n; i ++) if(in[i] == 1) {
        if(tp(i)) { puts("YES"); return 0; }
    }
    puts("NO");
    return 0;
}

E. Physical Education Lessons

题意:

有一个全为1的数组。
操作1,把l,r全覆盖成0。操作2,把l,r全覆盖成1。
问最后数组中1的个数。

做法:

线段树动态开点。(也可以离散化
注意卡内存。
注意lazy tag的之后L[o]和R[o]不存在要新建点。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cctype>
using namespace std;
typedef long long LL;

inline LL read()
{
    char ch = getchar(); LL x = 0; int op = 1;
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') op = -1;
    for(; isdigit(ch); ch = getchar()) x = x*10+ch-'0';
    return x*op;
}

const int N = 15000010;
int n, m, rt, tot;
int sum[N], tag[N], L[N], R[N];

inline void pushdown(int o, int l, int r)
{
    if(tag[o] == -1) return;
    if(!L[o]) L[o] = ++ tot;
    if(!R[o]) R[o] = ++ tot;
    tag[L[o]] = tag[R[o]] = tag[o];
    int mid = l+r>>1;
    sum[L[o]] = (mid-l+1)*tag[o];
    sum[R[o]] = (r-mid)*tag[o];
    tag[o] = -1;
}
inline void cover(int &o, int l, int r, int x, int y, int v)
{
    //printf("%d %d %d %d\n", l, r, x, y);
    if(!o) o = ++ tot;
    if(l == x && r == y) {
        sum[o] = (r-l+1)*v; tag[o] = v;
        return;
    }
    pushdown(o, l, r);
    int mid = l+r>>1;
    if(y <= mid) cover(L[o], l, mid, x, y, v);
    else if(x > mid) cover(R[o], mid+1, r, x, y, v);
    else { cover(L[o], l, mid, x, mid, v); cover(R[o], mid+1, r, mid+1, y, v); }
    sum[o] = sum[L[o]] + sum[R[o]];
}
int main()
{
    n = read(), m = read();
    memset(tag, -1, sizeof tag);
    while(m --) {
        int l = read(), r = read(), opt = read();
        cover(rt, 1, n, l, r, 2-opt);
        printf("%d\n", n-sum[rt]);
    }
    return 0;
} 

F. Imbalance Value of a Tree

题意:

定义两点之间的价值是这两点路径上最大边权-最小边权。
求一棵树上任意两点的价值的和。

做法:

先求所有路径的最大值的和,再求所有路径最小值的和然后相减就可以了。
先将边从小到大排序,用并查集维护,每加一条边都是一条最大的边,计算这条边对答案的贡献。
同理再从大到小排序,算出最小边的贡献。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cctype>
using namespace std;
typedef long long LL;

const int N = 2000010;
const int mod = 1e9 + 7;
int n, k, tot;
int mu[N], p[N], pw[N], vis[N], ans[N];

inline void upd(int &x, int y) { x += y; while(x > mod) x -= mod; }
inline void prepare()
{
    mu[1] = 1;
    for(int i = 2; i < N; i ++) {
        if(!vis[i]) { p[++ tot] = i; mu[i] = -1; }
        for(int j = 1; j <= tot && (LL)p[j]*i < N; j ++) {
            vis[i*p[j]] = 1;
            if(i%p[j] == 0) break;
            mu[i*p[j]] = -mu[i];
        }
    }
}
inline int ksm(int x, int p)
{
    int ret = 1;
    for(; p; p >>= 1, x = (LL)x*x%mod) if(p&1) ret = (LL)ret*x%mod;
    return ret;
}
int main()
{
    prepare();
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= k; i ++) pw[i] = ksm(i, n);
    for(int i = 1; i <= k; i ++)
        for(int j = i; j <= k; j += i) {
            int w = ((LL)mu[i]*pw[j/i]%mod+mod)%mod;
            upd(ans[j], w);
            upd(ans[j+i], mod-w);
        }
    int ret = 0;
    for(int i = 1; i <= k; i ++) {
        upd(ans[i], ans[i-1]);
        upd(ret, ans[i]^i);
    }
    printf("%d\n", ret);
    return 0;
}

G. Coprime Arrays

题意:

给一个n,k。
对于1~k中的每一个i,求:
用1~i中的所有数构成一个长为n的数组,使得这n个数的gcd为1的方案数。
输出每一个ans[i]^i的和%1e9+7。

做法:

先来理解一波莫比乌斯反演。
它的本质是一个容斥,可以把mu看成奇性函数容斥的一个工具,然后你用mu[i]乘上一堆东西,就可以实现容斥,计算出答案。

这个题首先对于一个k,可以知道它的公式为sigma(mu[i]*([k/i])^n)(1<=i<=k).[x]表示不超过x的最大整数.(同样用容斥去理解)
然后我们现在需要的是1~k的答案。假设ans[i]为数的范围在1~i的答案。
发现对于每一个i,ans[1..k]里面所有系数为mu[i]的项,ans[1..i-1]中的项是一样的,ans[i..2×i-1]中的项是一样的,……。
所以就可以枚举每一段然后差分一下最后再统计答案。
时间复杂度O(nlogn).

易错点:

由于mu[i]有可能是负的,有的地方处理的时候要+mod再%mod,转化为正的,否则会出锅。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cctype>
using namespace std;
typedef long long LL;

const int N = 2000010;
const int mod = 1e9 + 7;
int n, k, tot;
int mu[N], p[N], pw[N], vis[N], ans[N];

inline void upd(int &x, int y) { x += y; while(x > mod) x -= mod; }
inline void prepare()
{
    mu[1] = 1;
    for(int i = 2; i < N; i ++) {
        if(!vis[i]) { p[++ tot] = i; mu[i] = -1; }
        for(int j = 1; j <= tot && (LL)p[j]*i < N; j ++) {
            vis[i*p[j]] = 1;
            if(i%p[j] == 0) break;
            mu[i*p[j]] = -mu[i];
        }
    }
}
inline int ksm(int x, int p)
{
    int ret = 1;
    for(; p; p >>= 1, x = (LL)x*x%mod) if(p&1) ret = (LL)ret*x%mod;
    return ret;
}
int main()
{
    prepare();
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= k; i ++) pw[i] = ksm(i, n);
    for(int i = 1; i <= k; i ++)
        for(int j = i; j <= k; j += i) {
            int w = ((LL)mu[i]*pw[j/i]%mod+mod)%mod;
            upd(ans[j], w);
            upd(ans[j+i], mod-w);
        }
    int ret = 0;
    for(int i = 1; i <= k; i ++) {
        upd(ans[i], ans[i-1]);
        upd(ret, ans[i]^i);
    }
    printf("%d\n", ret);
    return 0;
}

总结:

比赛时通过ABE,Rank 583,rating +57.
如果不被hack会好很多啊,哪怕少hack一题也好。。。
还是要加强思维练习,以及实现能力。
(顺便g题还是我第一道莫比乌斯反演==

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值