AtCoder Grand Contest 013 题解

50 篇文章 0 订阅
18 篇文章 0 订阅

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 ,使得再也找不到后继点了
将之前这条路径经过的点存在一个栈里,将栈内元素翻转
这样变成了一条S 1 的路径,
并且所有与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 次操作后,盒子里还剩余j R 的不同序列数
但是显然,不同的初始状态可能指向同一个颜色序列
所以上面这个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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值