AtCoder Grand Contest 013 题解

来源:互联网 发布:tc编程怎么收费 编辑:程序博客网 时间:2024/05/21 17:18

A:

给出一个长度为N的数组A
需要将A划分为若干个连续的子串
使得每个子串要么单调不增要么单调不降
问最少分成多少个
1N1051Ai109

solution A:

贪心。。。显然是贪心。。。
因为一个数被划分到前面并不影响后面的决策
所以每次尽量长地划分就行了

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int maxn = 1E5 + 10;int n,Ans,A[maxn];bool vis[maxn];void Check(int k){    int a,b; a = b = 1;    for (int i = k + 1; i <= n; i++)    {        if (A[i] > A[i - 1]) {a = i - k; break;}        if (i == n) a = n + 1 - k;    }    for (int i = k + 1; i <= n; i++)    {        if (A[i] < A[i - 1]) {b = i - k; break;}        if (i == n) b = n + 1 - k;    }    int c = max(a,b);    for (int i = 0; i < c; i++)        vis[k + i] = 1;}int main(){    #ifdef DMC        freopen("DMC.txt","r",stdin);    #endif    cin >> n;    for (int i = 1; i <= n; i++) scanf("%d",&A[i]);    for (int i = 1; i <= n; i++)        if (!vis[i]) Check(i),++Ans;    cout << Ans << endl;    return 0;}

B:

给出一张N个点M条边保证连通的简单无向图(无重边无自环)
请找出任意一条符合以下条件的路径:

  • 至少包含两个点
  • 经过每个点恰好一次
  • 所有与路径端点有边直接相连的点都必须在路径里

可以证明一定存在合法解
2N1051M105

solution B:

做法挺傻逼的,就是卡了有点久。。
1号点出发随机dfs出一条路径
将沿途所有点都标记,规定如果一个点被标记了,那么再也不能经过
这样,一定会走到一个点S,使得再也找不到后继点了
将之前这条路径经过的点存在一个栈里,将栈内元素翻转
这样变成了一条S1的路径,
并且所有与S有边直接相连的点都在路径里
同理,再从1出发任意找到一条路径就好了
两条路径拼起来显然合法

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int maxn = 1E5 + 10;int n,m,tp,stk[maxn];bool vis[maxn];vector <int> v[maxn];void Dfs(int x){    for (int i = 0; i < v[x].size(); i++)    {        int to = v[x][i];        if (vis[to]) continue;        vis[to] = 1; stk[++tp] = to;        Dfs(to); return;    }}int main(){    #ifdef DMC        freopen("DMC.txt","r",stdin);    #endif    cin >> n >> m;    while (m--)    {        int x,y; scanf("%d%d",&x,&y);        v[x].push_back(y); v[y].push_back(x);    }    stk[tp = 1] = 1; vis[1] = 1; Dfs(1);    reverse(stk + 1,stk + tp + 1); Dfs(1); cout << tp << endl;    for (int i = 1; i < tp; i++) printf("%d ",stk[i]); cout << stk[tp] << endl;    return 0;}

C:

将一个周长为L的圆用L个点均分成L
L个点按照顺时针标号为0L1
在这L个点上分布着N只蚂蚁,第i只蚂蚁此时在点xi
每只蚂蚁初始时都朝着顺时针或逆时针方向
每一秒钟,每只蚂蚁都向自己面对的方向移动1的距离
如果有两只蚂蚁在某一时刻碰到对方,那么它们瞬间改变自己的朝向
请回答过了T秒后每只蚂蚁的坐标
1N1051L,T1090xi<Li<j,xi<xj

solution C:

关于蚂蚁的问题有一个通用的套路。。
两只相撞的蚂蚁不妨将它们视为穿过对方
可以发现,这样转换后最终每只蚂蚁出现的位置的集合是不变的
那么根据每只蚂蚁初始的朝向和总的时间
就能确定出T秒以后哪些位置上会有蚂蚁存在
因为蚂蚁们相撞后会掉头,所以它们的相对位置始终不变
因此,只需要确定第一只蚂蚁最后的坐标,其它蚂蚁的坐标也就确定了
一开始,第一只蚂蚁的坐标是所有蚂蚁中最小的
每当有蚂蚁从L1顺时针走到0,一号蚂蚁的坐标就向后一位
每当有蚂蚁从0逆时针走到L1,一号蚂蚁的坐标就向前一位
算一下以上两种情况发生的次数就行了

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int maxn = 1E5 + 10;int N,L,T,d,A[maxn];int pre(int x) {return x == 1 ? N : x - 1;}int nex(int x) {return x == N ? 1 : x + 1;}int main(){    #ifdef DMC        freopen("DMC.txt","r",stdin);    #endif    cin >> N >> L >> T;    for (int i = 1; i <= N; i++)    {        int x,w; scanf("%d%d",&x,&w);        if (w == 1)        {            int dt = T;            if (L - x <= dt)                dt -= (L - x),++d,x = 0;            x += dt; d += dt / L; x %= L;        }        else        {            int dt = T;            if (x + 1 <= dt)                dt -= (x + 1),--d,x = L - 1;            x -= dt; d -= dt / L; x %= L;            if (x < 0) x += L;        }        A[i] = x; d %= N;    }    int pos = 1; d %= N;    while (d < 0) pos = pre(pos),++d;    while (d > 0) pos = nex(pos),--d;    sort(A + 1,A + N + 1);    for (int i = pos; i <= N; i++) printf("%d\n",A[i]);    for (int i = 1; i < pos; i++) printf("%d\n",A[i]);    return 0;}

D:

你有一个装着红色或蓝色砖块的盒子
初始的时候红色和蓝色砖块共有N块,数量随机
需要连续做下面的操作M轮:

  • 从盒子里随机拿一个砖块
  • 往盒子里放入一个红色砖块和一个蓝色砖块
  • 从盒子里随机拿一个砖块

将每次拿出的砖块摆成一行,这样就得到了一个长度为2M的颜色序列
询问不同的颜色序列的个数,答案对109+7取模
1N,M3000

solution D:

以下为了叙述方便,将红砖块简记为R,蓝砖块简记为B
本质不同的操作只有四种:

  • RR,需要此时至少有一个R,完成后R1B+1
  • RB,需要此时至少有一个R,完成后不发生改变
  • BB,需要此时至少有一个B,完成后R+1B1
  • BR,需要此时至少有一个B,完成后不发生改变

这就引向一个简单的dp
f[i][j]为执行了i次操作后,盒子里还剩余jR的不同序列数
但是显然,不同的初始状态可能指向同一个颜色序列
所以上面这个dp方程会算重
定义一次操作为特殊的,当操作结束后或者操作过程中R的数量达到最小
那么定义三维状态f[i][j][k]指向是否已经有过特殊操作
显然,任意一种颜色序列有且仅有一种初始砖块分布
使得每次R取到最小值时其值都为0
因此O(NM)dp就行了

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int N = 3003;typedef long long LL;const LL mo = 1000000007;int n,m,Ans;LL f[N][N][2];int main(){    #ifdef DMC        freopen("DMC.txt","r",stdin);    #endif    cin >> n >> m; f[0][0][1] = 1;    if (n == 1)    {        Ans = 2;        for (int i = 1; i <= m; i++)            Ans = Ans * 2 % mo;        cout << Ans << endl; return 0;    }    for (int i = 1; i <= n; i++) f[0][i][0] = 1;    for (int i = 1; i <= m; i++)    {        f[i][0][1] = (f[i - 1][0][1] + f[i - 1][1][0] + f[i - 1][1][1]) % mo;        f[i][1][0] = (f[i - 1][1][0] + f[i - 1][2][0]) % mo;        f[i][1][1] = (f[i - 1][0][1] + f[i - 1][1][0] + 2LL * f[i - 1][1][1] + f[i - 1][2][1]) % mo;        f[i][n][0] = (f[i - 1][n - 1][0] + f[i - 1][n][0]) % mo;        f[i][n][1] = (f[i - 1][n - 1][1] + f[i - 1][n][1]) % mo;        for (int j = 2; j < n; j++)        {            f[i][j][0] = (f[i - 1][j - 1][0] + 2LL * f[i - 1][j][0] + f[i - 1][j + 1][0]) % mo;            f[i][j][1] = (f[i - 1][j - 1][1] + 2LL * f[i - 1][j][1] + f[i - 1][j + 1][1]) % mo;        }    }    for (int i = 0; i <= n; i++)        Ans += f[m][i][1],Ans %= mo;    cout << Ans << endl;    return 0;}

E:

现在有一根长度为n的木棍
木棍上有m个标记,第i个标记距离左端点为xi
需要在这根木棍上摆放一些正方形,满足:

  • 每一个正方形的边长都是正整数
  • 每一个正方形都要有一条边紧贴着木棍
  • 木棍必须被完全覆盖,正方形的边界也不能超出木棍
  • 两个相邻正方形的交界处不能有标记

定义每种方案的美丽度为所有正方形面积的乘积
求所有合法方案的美丽度的和,答案对109+7取模
1N1090M1051x1<x2<<xmN1

solution E:

将原问题的模型稍微修改一下
现在有n个连续的空格,其中左边界和右边界必须放置隔板
可以在两个相邻空格摆放隔板,可以在空格内放红球和蓝球
需要统计,有多少种不同的方案,满足:

  • 每相邻的两块隔板之间都恰好有一个红球和一个蓝球
  • 隔板不能摆放在有标号的地方

两种方案不同,当隔板的摆放不同或隔板间红蓝球分布不同
隔板的摆放对应正方形的划分,红蓝球的摆放对应正方形的面积
因此总方案数对应着美丽度之和
那么定义f[i][j]为考虑前i个空格,
从最后一块隔板到现在已经摆放了j个球的方案数
不同的空格只有三类,每类可以写出固定的转移方程
第二维状态只有3种,因此可以使用矩阵乘法优化转移
O(33Mlog2N)

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int N = 3;const int maxn = 1E5 + 10;typedef long long LL;const LL mo = 1000000007;inline int Mul(const LL &x,const LL &y) {return x * y % mo;}inline int Add(const int &x,const int &y) {return x + y < mo ? x + y : x + y - mo;}struct data{    int a[N][N]; data(){memset(a,0,sizeof(a));}    data operator * (const data &B)    {        data c;        for (int k = 0; k < N; k++)            for (int i = 0; i < N; i++)                for (int j = 0; j < N; j++)                    c.a[i][j] = Add(c.a[i][j],Mul(a[i][k],B.a[k][j]));        return c;    }}A,B,C,D;int n,m,X[maxn];void Pre_Work(){    for (int i = 0; i < N; i++) A.a[i][i] = 1;    B.a[0][0] = 2; B.a[0][1] = 1; B.a[0][2] = 1;    B.a[1][0] = 2; B.a[1][1] = 1; B.a[1][2] = 2;    B.a[2][0] = 1; B.a[2][1] = 0; B.a[2][2] = 1;    C.a[0][0] = 1; C.a[0][1] = 1; C.a[0][2] = 1;    C.a[1][0] = 0; C.a[1][1] = 1; C.a[1][2] = 2;    C.a[2][0] = 0; C.a[2][1] = 0; C.a[2][2] = 1;    D.a[0][0] = 1; D.a[0][1] = 0; D.a[0][2] = 0;    D.a[1][0] = 2; D.a[1][1] = 0; D.a[1][2] = 0;    D.a[2][0] = 1; D.a[2][1] = 0; D.a[2][2] = 0;}inline data ksm(int y){    data ret,x = B;    for (int i = 0; i < N; i++) ret.a[i][i] = 1;    for (; y; y >>= 1)    {        if (y & 1) ret = ret * x;        x = x * x;    }    return ret;}int main(){    #ifdef DMC        freopen("DMC.txt","r",stdin);    #endif    cin >> n >> m; Pre_Work();    for (int i = 1; i <= m; i++) scanf("%d",&X[i]);    for (int i = 1; i <= m; i++)        A = A * ksm(X[i] - X[i - 1] - 1),A = A * C;    A = A * ksm(n - X[m] - 1); A = A * D;    cout << A.a[0][0] << endl;    return 0;}

F:

n张卡片,第i张卡片正面写着数字Ai背面写着数字Bi
另有n+1张卡片,第i张卡片正面写着数字Ci,背面没有数字
Q次询问,第i次给出DiEi,将卡片(Di,Ei)加入第一类卡片
将第一类卡片和第二类卡片逐一配对
每张第一类卡片选择正面或反面
一个配对合法,当且仅当第一类卡片选择的那一面的数字不超过和它配对的第二类卡片的数字
称一组配对的价值,为配对中第一类卡片选择正面的数量
每次询问,给出最大的价值,若无解,输出1
1N,Q1051Ai,Bi,Ci,Di,Ei109

solution F:

记第i张一类卡片选择的数字为xi,如何判断一组决策是否合法?
一个简单的办法,将xi,ci升序排好,若i,xici,则肯定能成功配对
这里不采用这种办法,而使用另一种形式
将所有的ci离散化,对应到[1,N]
把每个ai,bi,di,ei对应到第一个大于等于它的ci
假设现在有一个初始值全为0的数组f
那么对于每个xi,将[xi,N]加上1
对于每个ci,将[ci,N]减去1
若配对合法,必有i[1,N],f[i]0
对于每个xi,只有大于等于它的ci才能配对,
否则前面的部分就变成负数了
先不考虑询问,令所有xi=ai,然后处理出f数组
考虑如果改变一张卡片的状态,则等价于[bi,ai)加上1
那么对于一个询问,可以枚举它的状态,假设为yi
只需满足j<yi,fj0,jyi,fj1
如果能预处理达到上述状态的最优决策,就能快速回答询问了
记上面这样的状态为Ansyi
初始的f数组肯定有很多位置为负数
需要满足任意一个位置的前置条件是f数组的每一位不小于1
从右到做枚举每个位置,若发现fi=k<1
则我们需要选择k1张包含位置i的卡片改变状态
贪心地想,每次取左端点尽量靠左的区间就好了
然后为了处理Ans数组,还需从左往右扫一遍
每次贪心取右端点尽量右的区间就行了
用个堆维护,复杂度O(NlogN)

#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<vector>#include<queue>#include<set>#include<map>#include<stack>#include<bitset>#include<ext/pb_ds/priority_queue.hpp>using namespace std;const int INF = ~0U>>1;const int maxn = 1E5 + 10;struct d1{    int l,r,Num; d1(){}    d1(int l,int r,int Num): l(l),r(r),Num(Num){}    bool operator < (const d1 &B) const {return l > B.l;}};struct d2{    int l,r,Num; d2(){}    d2(int l,int r,int Num): l(l),r(r),Num(Num){}    bool operator < (const d2 &B) const {return r < B.r;}};int n,q,N,A[maxn],B[maxn],C[maxn],D[maxn],E[maxn],Ans[maxn],s[maxn];bool vis[maxn];vector <pair<int,int> > L[maxn],R[maxn];priority_queue <d1> Q1;priority_queue <d2> Q2;void Calc(){    for (int i = 1; i <= n; i++)        for (int j = A[i]; j <= N; j += j&-j) ++s[j];    for (int i = 1; i <= n + 1; i++)        for (int j = C[i]; j <= N; j += j&-j) --s[j];    int Cnt = 0;    for (int i = N; i > 0; i--)    {        int sum = 0;        for (int j = 0; j < R[i].size(); j++)            Q1.push(d1(R[i][j].first,i,R[i][j].second));        for (int j = i; j > 0; j -= j&-j) sum += s[j];        if (sum >= -1) continue; sum = -sum - 1;        for (int l = 0; l < sum; l++)        {            d1 k = Q1.top(); Q1.pop();            ++Cnt; vis[k.Num] = 1;            for (int j = k.l; j <= N; j += j&-j) ++s[j];            for (int j = k.r + 1; j <= N; j += j&-j) --s[j];        }    }    Ans[0] = Cnt;    for (int i = 1; i <= N; i++)    {        int sum = 0;        for (int j = 0; j < L[i].size(); j++)            if (!vis[L[i][j].second]) Q2.push(d2(i,L[i][j].first,L[i][j].second));        for (int j = i; j > 0; j -= j&-j) sum += s[j];        if (sum >= 0) {Ans[i] = Cnt; continue;} sum = -sum;        bool pass = 1;        for (int l = 0; l < sum; l++)        {            if (Q2.empty()) {pass = 0; break;}            d2 k = Q2.top(); Q2.pop(); ++Cnt;            if (k.r < i) {pass = 0; break;}            for (int j = k.l; j <= N; j += j&-j) ++s[j];            for (int j = k.r + 1; j <= N; j += j&-j) --s[j];        }        if (!pass)        {            for (int j = i; j <= N; j++) Ans[j] = INF;            break;        }        Ans[i] = Cnt;    }}void Pre_Work(){    for (int i = 1; i <= n; i++)    {        A[i] = lower_bound(D + 1,D + N + 1,A[i]) - D;        B[i] = lower_bound(D + 1,D + N + 1,B[i]) - D;        E[i] = min(A[i],B[i]);        if (B[i] < A[i])        {            L[B[i]].push_back(make_pair(A[i] - 1,i));            R[A[i] - 1].push_back(make_pair(B[i],i));        }    }    for (int i = 1; i <= n + 1; i++)        C[i] = lower_bound(D + 1,D + N + 1,C[i]) - D;    sort(C + 1,C + n + 2); sort(E + 1,E + n + 1);    int now = 1;    for (int i = 1; i <= n; i++)    {        while (now <= n + 1 && C[now] < E[i]) ++now;        if (now == n + 2)        {            while (q--) puts("-1");            exit(0);        }        ++now;    }}int main(){    #ifdef DMC        freopen("DMC.txt","r",stdin);    #endif    cin >> n;    for (int i = 1; i <= n; i++) scanf("%d%d",&A[i],&B[i]);    for (int i = 1; i <= n + 1; i++) scanf("%d",&C[i]),D[i] = C[i];    cin >> q; sort(D + 1,D + n + 2); N = 1;    for (int i = 2; i <= n + 1; i++)        if (D[i] != D[i - 1]) D[++N] = D[i];    D[++N] = INF; Pre_Work(); Calc();    while (q--)    {        int X,Y; scanf("%d%d",&X,&Y);        X = lower_bound(D + 1,D + N + 1,X) - D;        Y = lower_bound(D + 1,D + N + 1,Y) - D;        int now = max(n + 1 - Ans[X - 1],n - Ans[Y - 1]);        printf("%d\n",now < 0 ? -1 : now);    }    return 0;}
0 0