AtCoder Grand Contest 014做题记录

这场AtCoder的题目以结论题为主
代码都很短,想法也都很巧妙

按照题目描述暴力即可
关键在于时间复杂度的证明
对于三个数 a b c ,不妨设abc,变换等价于对 0,ba,ca 三个数变换之后再加上 a ,即三个数减去最小值后对步数没有影响
不妨令减去a之后的三个数为 0 b c ,变换后三个数变成b+c2,b2,c2,同样做一次减去最小值操作,得到 c2,0,cb2
对这三个数两两做差求和,变化前的和为 2c ,变化后的和为 c ,这个值缩小了一倍,当这个值为0的时候终止,至多只需要进行log2N次操作

#include <bits/stdc++.h>
using namespace std;
int main() {
    int a,b,c,ans = 0;
    cin >> a >> b >> c;
    for (;;) {
        if ((a&1) || (b&1) || (c&1)) { printf("%d\n",ans); break; }
        if (a == b && b == c) { puts("-1"); break; }

        ans++;
        int na = (b + c) >> 1;
        int nb = (a + c) >> 1;
        int nc = (a + b) >> 1;
        a = na, b = nb, c = nc;             
    }
    return 0;
}
B - Unplanned Queries

原问题相当于对树上一条路径的边异或1
注意到是树边而不是点,单次操作(a,b)可以分解为(rt,a),(rt,b)两个操作,rt为树根,这两次异或操作对LCA(a,b)以上的的边的影响可以抵消。
结论:存在一棵符合题意的树当且仅当所有形如(rt,x)的操作中,每个x出现的次数都为偶数
充分性:所有的操作都做了偶数次,第 2i 次操作撤销第 2i1 次操作,最后这棵树上的边权值一定都为初始值0;
必要性:若存在一种合法的方案,对某个点进行了奇数次操作并且存在一棵合法的树,取所有的被操作了奇数次点中深度最大的点,它的子树中除了它所有的点都被操作了偶数次,而该点被操作了奇数次,那么改点和它父亲的连边被操作了奇数次,矛盾。
最终解法只需要 O(N) 扫一遍判断每个操作是否进行了偶数次即可

#include <bits/stdc++.h>
#define N 1000500
using namespace std;

inline int rd() {int r;scanf("%d",&r);return r;}
int n,m,d[N];
int main() {
    n = rd(), m = rd();
    for (int i=1;i<=m;i++) d[rd()]++, d[rd()]++;
    int ans = 1;
    for (int i=1;i<=n;i++) if (d[i]&1) ans = 0;
    puts(ans?"YES":"NO");
    return 0;
}
C - Closed Rooms

操作相当于先移动K步,然后每次开K个门移动K步
先爆搜出从起点移动K步能到达的所有的点,这一步可以在 O(NM) 完成。
后面每次允许开K个门并且移动K步,相当于移动不受地形限制,朝四条边中距离它最近的一条边直线行走一定是最优的方案。对于一开始能移动到的没一个点都判断一次,同样这一步能够在 O(NM) 完成。
总时间是 O(NM)

#include <bits/stdc++.h>
#define N 1805
using namespace std;

const int fx[] = { 0, 1, 0,-1};
const int fy[] = { 1, 0,-1, 0};

int n,m,k,sx,sy;
char s[N];
bool vis[N][N],mp[N][N],flag;
queue<int> qx,qy,qs;
int main() {
    scanf("%d%d%d",&n,&m,&k);
    for (int i=1;i<=n;i++) {
        scanf("%s",s+1);
        for (int j=1;j<=m;j++) mp[i][j] = s[j]!='#';
        for (int j=1;j<=m;j++) if (s[j]=='S') sx=i, sy=j;
    }

    if (sx == 1 || sx == n || sy == 1 || sy == m) return puts("0"), 0;

    vis[sx][sy] = 1;
    qx.push(sx), qy.push(sy), qs.push(0);
    while (!qx.empty()) {
        int ux = qx.front(); qx.pop();
        int uy = qy.front(); qy.pop();
        int us = qs.front(); qs.pop();
        if (us == k) continue;
        if (ux == 1 || ux == n || uy == 1 || uy == m) flag = 1;

        for (int i=0;i<4;i++) {
            int vx = ux + fx[i], vy = uy + fy[i];
            if (vx<1 || vx>n || vy<1 || vy>m) continue;
            if (!vis[vx][vy] && mp[vx][vy]) {
                vis[vx][vy] = 1;
                qx.push(vx), qy.push(vy), qs.push(us+1);
            }
        }
    }

    if (flag) return puts("1"), 0;

    int ans = 2147483647;   
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) if (vis[i][j]) {
            int t = min( min(i-1,j-1) , min(n-i,m-j) );
            int cur = (t+k-1) / k;
            ans = min(ans, cur+1);
        }
    cout << ans << endl;
    return 0;
}
D - Black and White Tree

结论:后手必胜当且仅当树存在完备匹配
充分性:对于先手每一次操作染色的点 u ,找到它的匹配点v染成另外一种颜色,最后每一个白点都存在一个相邻的匹配点是黑色的。
必要性:先手每次找到最深的一个未染色点 x ,它的父亲为y,先手染色 y
y仅有一个孩子,那么先手染色 y 后手被动染色x,否则下一轮先手染色 x 后先手获胜,将x y 结点从树中移除后不改变匹配性质,也不影响游戏结果。
y有大于等于2个孩子,那么显然所有的孩子都是叶子结点,先手染色 y 之后,后手至多只能染色y的一个儿子,此时先手只需要再染色另一个 y 的儿子即可获胜
复杂度取决于判断树是否存在完备匹配,用从叶子结点贪心的方法可以做到O(N)

#include <bits/stdc++.h>
#define N 1000500
using namespace std;
inline int rd() {int r;scanf("%d",&r);return r;}
vector<int> e[N];
int p[N],n;
void link(int a,int b) {e[a].push_back(b), e[b].push_back(a);}

void dfs(int u,int f) {
    for (int i=0;i<(int)e[u].size();i++) {
        int v=e[u][i]; if (v==f) continue;
        dfs(v,u);
    }
    if (!p[u] && !p[f]) p[u] = p[f] = 1; 
}

int main() {
    n = rd();
    for (int i=1;i<n;i++) link(rd(),rd());
    p[0] = 1;
    dfs(1,0);
    int ans = 1;
    for (int i=1;i<=n;i++) if (!p[i]) ans = 0;
    puts(!ans?"First":"Second");
    return 0;
}
E - Blue and Red Tree

若将一条蓝边删去,原来的树将被划分成两个连通块,新连的红边又将连接这两个连通块,并且这条红边是唯一连接这两个连通块的边。
每次选择两个连通块,满足这两个连通块间有且仅有一条蓝边和红边,连接这两个连通块。
用队列维护需要被处理的两个连通块,map维护连通块之间的边数,vector存边。启发式合并这些信息。
启发式合并一个log,map一个log
时间复杂度 O(Nlog22N)

#include <bits/stdc++.h>
#define x first
#define y second
#define N 100050
using namespace std;
typedef pair<int,int> pii;
map<pii,int> mp;
vector<int> e[N];
queue<pii> q;
int fa[N],siz[N],tot,n;

pii MP(int a,int b) {if (a>b) swap(a,b); return make_pair(a,b);}

inline int rd() {int r;scanf("%d",&r);return r;}

void link(int a,int b) {
    e[a].push_back(b);
    e[b].push_back(a);
    mp[MP(a,b)]++;
}

int gf(int u) {return fa[u]==u?u:gf(fa[u]);}

int main() {
    n = rd();
    for (int i=1;i<=n;i++) fa[i] = i;
    for (int i=1;i<=2*n-2;i++) link(rd(), rd());

    for (map<pii,int>::iterator it=mp.begin();it!=mp.end();it++)
        if (it->y == 2) q.push(it->x);

    while (!q.empty()) {
        pii u = q.front(); q.pop();
        int a = gf(u.x), b = gf(u.y);
        if (a == b) continue;
        ++tot;
        if (siz[a] < siz[b]) swap(a, b);
        for (int i=0;i<(int)e[b].size();i++) {
            int t=gf( e[b][i] );
            if (t == a) continue;
            e[a].push_back(t);
            mp[ MP(b, t) ]--;
            mp[ MP(a, t) ]++;
            if (mp[ MP(a, t) ] == 2) q.push( MP(a, t) ); 
        }
        e[b].clear();
        fa[b] = a;
    }

    puts(tot==n-1?"YES":"NO");  
    return 0;
}

F留坑待填

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值