AtCoder Grand Contest 016做题记录

来源:互联网 发布:python 交易 行情平台 编辑:程序博客网 时间:2024/05/19 10:55

比赛的时候去打CodeM了
赛后补得题目
模拟赛的题还没改完,所以做的比较慢
感觉题目质量还是一如既往的高

A - Shrinking

题意:
对字符串s进行一次变换的规则如下:

  • 长度为n的字符串s变换成为长度为n-1的字符串t,对于变换后的t的每一位,都有ti=sisi1

给定字符串s,求将其变成由一个字母组成的字符串的最少步数
|s|100
解答:
预处理每一位向右离它最近的26个字母的分别的位置
爆枚最后存在的那一个字母,扫一遍字符串判断最早什么时候被该字符覆盖或者被删除。
时空复杂度:O(26N)

#include <bits/stdc++.h>#define N 1005using namespace std;char s[N];int n,R[N][30];int main() {    scanf("%s",s+1); n = strlen(s+1);    for (int _=n;_>=1;_--) {        for (int i=1;i<=26;i++) R[_][i] = R[_+1][i];        R[_][s[_]-'a'+1] = _;    }    int ans = 2147483647;    for (int _=1;_<=26;_++) {        int cur = -1;        for (int i=1;i<=n;i++) {            int t = n-i+1;            if (R[i][_]) t = min(t, R[i][_] - i);            cur = max(cur, t);        }        ans = min(ans, cur);    }    cout << ans << endl;    return 0;}

B - Colorful Hats

题意:
有N只戴帽子的猫,每只猫看不到自己的帽子颜色,但是能看得见别的猫的帽子颜色。现在第i只猫告诉你它看到了ai种颜色,问是否存在一种合法的情况。
N105aiN
解答:
令总帽子颜色数为T,对于每一只猫,若它的帽子颜色唯一则他所看到的颜色数为T-1,否则为T,我们称帽子颜色的唯一的猫是孤独的。
故若ai的最大值和最小值的差大于等于2则无解
下面对最大值和最小值的差进行分类讨论:

  • 若max=min,那么此时每只猫帽子的颜色为T或者T-1。如果每只猫的帽子的颜色都是T-1,则每只猫都是孤独的,此时T=n-1,否则每只猫所看到的帽子的颜色都是T,由于不存在孤独的猫,所以T至多为n2。综上所述,当max=min时,T=n1Tn2
  • 若max=min+1,此时我们可以统计孤独的猫的个数x,类似上面的推论,颜色数至少为x+1,至多为x+(n-x)/2。故当max=min+1时,x+1Tx+nx2

时空复杂度:O(N)

#include <bits/stdc++.h>using namespace std;inline int rd() {    int x=0,f=1;char ch=getchar();    while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}    while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}    return x*f;}int n,a[1000500];int main() {    n = rd();    for (int _=1;_<=n;_++) a[_] = rd();    int x = a[1], y = a[1];    for (int _=1;_<=n;_++) x = max(a[_], x), y = min(a[_], y);    if (x-y > 1) return puts("No"), 0;    if (x == y) {        if (x == n-1 || x<=n/2) puts("Yes"); else puts("No");    } else {        int tot = 0, sum = 0;        for (int _=1;_<=n;_++) if (a[_] == y) tot++; //alone        for (int _=1;_<=n;_++) if (a[_] == x) sum++; //not alone        if (tot+1 <= x && x <= tot + sum/2) puts("Yes"); else puts("No");    }    return 0;}

C - +/- Rectangle

题意:
给出H,W,h,w,构造一个由109~109矩阵满足:

  • 所有元素的和为整数
  • 任意一个h*w的子矩阵的和为负数

1H,W500
解答:
显然当h|H且w|W时,显然无解。证明方法即将整个矩阵分解成为若干个h*w的小矩阵的和。
否则,一定存在解。若x%h==0&&y%w==0Fx,y=(hw1)T1,否则Fx,y=T,T为足够大的一个数。
时空复杂度:O(HW)

#include <bits/stdc++.h>#define N 1050#define INF 100000000using namespace std;int n,m,a,b,sum,mp[N][N];inline int rd() {int r;scanf("%d",&r);return r;}int main() {    n = rd(), m = rd(), a = rd(), b = rd();    int p1 = INF / (n*m), p2 = -((a*b-1)*p1 + 1);    for (int i=1;i<=n;i++)        for (int j=1;j<=m;j++) if (i%a == 0 && j%b == 0)            mp[i][j] = p2; else mp[i][j] = p1;    for (int i=1;i<=n;i++)        for (int j=1;j<=m;j++)            sum += mp[i][j];    if (sum <= 0) puts("No"); else {        puts("Yes");        for (int i=1;i<=n;i++)            for (int j=1;j<=m;j++) printf("%d%c",mp[i][j],j==m?'\n':' ');    }    return 0;}

D - XOR Replace

题意:
给定长度为N的数列A,B,需要将你用若干次操作使得数列A变成数列B。每次操作的内容是取A所有数的异或和x,然后将A的某一位替换成x。询问是否能将A变成B,若能请输出最小步数。
N1050Ai,Bi109
解答:
这题还是想了蛮久的,结论很多要一步一步推出来。
A数组的异或和为x,现在用x取代Ai,那么新的异或和x=x^x^Ai=Ai,相当于交换了xAi
那我们不妨将最开始的x看成A0,B数列的异或和看成B0
问题转化为对A数组进行操作,操作的内容是交换A0Ai,使得A数列变成B数列
故当构成A数列元素多重集不等于构成B数列的多重集时,无解。
建图:边Bi连向Ai,走这条边表示x的值为Bi时将Ai替换成x并且x变成原来的Ai。每个连通块的一条欧拉路径是这个连通块的最优解。若将AB看成置换(原本相同的元素可以任意标顺序),就能得出结论这样一条欧拉路径一定存在。
故解决A0所在的连通块对答案的贡献为边数E,对于A0所不在的连通块,需要多一步将A0移动过去,对答案的贡献为E+1
综上所述,在建图之后,判处掉无解和0,图的边数为E连通块个数为Cans=E+C1

#include <bits/stdc++.h>#define N 1000500using namespace std;int a[N],b[N],c[N],d[N],fa[N],n,ans,tot;map<int,int> mp;int gf(int x) {return fa[x]==x ? x : fa[x] = gf(fa[x]);}inline int rd() {int r;scanf("%d",&r);return r;}int main() {    n = rd();    for (int _=1;_<=n;_++) a[_] = rd();    for (int _=1;_<=n;_++) b[_] = rd();    for (int _=1;_<=n;_++) a[n+1] ^= a[_], b[n+1] ^= b[_];    ++n;    for (int _=1;_<=n;_++) c[_] = a[_], d[_] = b[_];    sort(c+1,c+n+1); sort(d+1,d+n+1);    for (int _=1;_<=n;_++) if (d[_]!=c[_]) return puts("-1"), 0;    for (int _=1;_<=n;_++) if (a[_] != b[_] || _ == n) {        _<n ? ++ans :0;        !mp[ a[_] ] ? mp[ a[_] ] = ++tot :0;        !mp[ b[_] ] ? mp[ b[_] ] = ++tot :0;    }    if (!ans) return puts("0"), 0;    for (int _=1;_<=tot;_++) fa[_] = _;    for (int _=1;_<n;_++) if (a[_] != b[_])        fa[ gf( mp[a[_]] ) ] = gf( mp[b[_]] );    for (int _=1;_<=tot;_++) ans += fa[_] == _;    cout << ans-1 << endl;    return 0;}

时空复杂度:O(Nα(N)+Nlog2N)
其中反阿克曼函数来自于并查集,log来自于STLmap

E - Poor Turkeys

题意:
N只火鸡,有M次操作。每次操作的对象是AiBi

  • AiBi都还没被吃掉,选一只吃掉
  • AiBi都被吃掉了,则忽略这一次操作
  • AiBi只有一只还没被吃掉,则吃掉这一只

求在操作后有多少对(x,y)火鸡有可能还存活
N400M105

解答:
操作的时间顺序会影响答案,这是一个很重要的性质,这可以帮助我们否决掉很多图论相关的做法。
考虑时光倒流,即从最后一次操作开始考虑。
考虑火鸡x在t轮后存活的条件:

  • 若操作AtBt都不是x,那么显然该操作与火鸡x存活的情况无关
  • 不妨令At=x,若在操作后火鸡x存活,那么在第t轮前Bt存活,并且我们选择在这一轮吃掉它。此时与x一样,它在前t轮不能被吃掉,加入我们所需要“强制存活”的集合。
  • AtBt均为我们要“强制存活”的对象,那么不存在任何一种方案能够使火鸡x活到最后,因为AtBt中我们必须选一只吃掉

这样从时刻M1,我们能统计出每只火鸡的“强制存活”集合。对于一对火鸡xy,他们能同时存活的充要条件是他们都能存活且他们的“强制存活”集合交为空集。证明也很简单,对于他们交集中的一只火鸡x,一定被安排在不同的时间t1t2被吃掉,一定有一只先被吃掉而另外一只就不满足条件了。
代码很简单,先枚举一只火鸡x,再时光倒流枚举边把它的集合求出来;最后再枚举两只能存活的火鸡ij,判断一下他们集合的交是否为空集。
时间复杂度O(N3+NM),空间复杂度O(N2+M)

#include <bits/stdc++.h>#define N 405#define M 100050using namespace std;int F[N][N],imp[N],a[M],b[M],n,m;inline int rd() {int r;scanf("%d",&r);return r; }int main() {    n = rd(), m = rd();    for (int _=1;_<=m;_++) a[_] = rd(), b[_] = rd();    for (int x=1;x<=n;x++) {        F[x][x] = 1;        for (int _=m;_>=1;_--) {            if (!F[x][a[_]] && !F[x][b[_]]) continue;            if (F[x][ a[_] ] && F[x][ b[_] ]) {imp[x] = 1; break;}            if (F[x][ a[_] ])                F[x][ b[_] ] = 1;            else                F[x][ a[_] ] = 1;        }    }    int ans = 0;    for (int i=1;i<=n;i++)        for (int j=i+1;j<=n;j++) if (!imp[i] && !imp[j]) {            int flag = 1;            for (int _=1;_<=n;_++) if (F[i][_] && F[j][_]) flag = 0;            ans += flag;        }    cout << ans << endl;    return 0;}

F - Games on DAG

题意:
给定一个N个点M条边的,无重边的有向无环图,点按照拓扑序编号,求原图2M个子图中有多少个子图满足sg1^sg2不等于0。其中sgx为点x的SG函数值。
N15,MN(N1)2,1ai<biN

解答:
补集转化,即求sg1=sg2的子图个数,最后用2M减去它就是答案
做法是状态压缩动态规划,Fmask表示只考虑状态为mask的点集,满足条件的子图个数。(这里的考虑表示只有点集中的点存在连边的子图)
一个有向无环图G,我们可以将它分解成为入度为0的点和入度非0的点,对于入度为非0的点的导出子图G’,在加上入度为0的点后这个点集内的所有点的SG值会+1
这样我们就可以转移了,将mask分解成为两个集合,分别表示入度为0的点集A和入度为非0的点集B,然后分配这几个点集之中的连有向边E(u,v)。为保证1号点的SG值和2号点的SG值相当,在任意时刻这两个点都应该被划分在同一个集合中。

  • 集合A中不能有任何连边。若u,v都是入度为0的点集A中的点,那么这条边一定不能存在。(否则v的入度至少为1)
  • 对于集合B中的每一个点,它至少向集合A中的点连了一条边(否则该点度数为0,不属于B集合)
  • 集合A中的点连向集合B中的点的边可以任意选或者不选
  • 集合B中的边的方案数为FB

通过枚举子集转移,预处理出点x连接到集合S的边数。
时间复杂度O(3NN),空间复杂度O(2NN)

#include <bits/stdc++.h>#define N 17#define MASK 70000#define mod 1000000007using namespace std;int n,m,tp,sum;int g[MASK][N], F[MASK],e[N*N];inline int lowbit(int x) {return x&(-x);}inline int rd() {int r;scanf("%d",&r);return r;}inline void inc(int &x,int y) {x=(x+y)%mod;}int main() {    n = rd(), m = rd(), tp = (1<<n) - 1;    e[0] = 1;    for (int _=1;_<=m;_++) e[_] = 1LL * e[_-1] * 2 % mod;    sum = e[m];    for (int _=1;_<=m;_++) {        int a = rd(), b = rd();        g[1<<(b-1)][a] = 1;    }    for (int _=1;_<=tp;_++)        for (int i=1;i<=n;i++) {            int x = lowbit(_);            g[_][i] = g[_^x][i] + g[x][i];        }    F[0] = 1;    for (int i=1;i<=tp;i++) if ((i&1) == ((i>>1)&1)){        for (int k=i;k;k=(k-1)&i) if ((k&1) == ((k>>1)&1)){            int j = i^k, cur = F[j], tmp = 1;            for (int x=1;x<=n;x++) if ((1<<(x-1))&i) {                if ((1<<(x-1)) & j)                    tmp = 1LL * tmp * (e[g[k][x]]-1) % mod;                else                    tmp = 1LL * tmp * e[g[j][x]] % mod;            }            inc(F[i], 1LL * cur * tmp % mod);        }    }    int ans = (sum - F[tp]) % mod;    ans < 0 ? ans += mod : 0;    cout << ans << endl;    return 0;}
原创粉丝点击