AtCoder Grand Contest 014做题记录

来源:互联网 发布:mysql修改root密码 编辑:程序博客网 时间:2024/06/07 19:39

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

按照题目描述暴力即可
关键在于时间复杂度的证明
对于三个数abc,不妨设abc,变换等价于对0,ba,ca三个数变换之后再加上a,即三个数减去最小值后对步数没有影响
不妨令减去a之后的三个数为0bc,变换后三个数变成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 1000500using 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 1805using 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后先手获胜,将xy结点从树中移除后不改变匹配性质,也不影响游戏结果。
y有大于等于2个孩子,那么显然所有的孩子都是叶子结点,先手染色y之后,后手至多只能染色y的一个儿子,此时先手只需要再染色另一个y的儿子即可获胜
复杂度取决于判断树是否存在完备匹配,用从叶子结点贪心的方法可以做到O(N)

#include <bits/stdc++.h>#define N 1000500using 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 100050using 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
原创粉丝点击