Codeforces Round #371(Div.1)

来源:互联网 发布:chrome json 插件 编辑:程序博客网 时间:2024/05/19 16:50

A. Sonya and Queries

题目链接

分类:思维、map容器应用

1.题意概述

  • 定义一个集合A,你有三种操作
    1. + ai —— 将该元素加入集合当中(允许相同元素重复)
    2.  ai —— 将该元素从集合中删除(保证删除合法性)
    3. ? s —— 统计符合条件的元素个数
      • 其中s是01串,为0表示该位是偶数,为1表示该位为奇数,如果s的位数比某元素低,那么前缀自动补0

2.解题思路

  • 因为不需要具体到每个元素是谁,因此我们只关心每个元素特征。因此我们考虑统计每个加入、删除的元素。对于元素每一位:

    • 如果是偶数,则它这位为0
    • 如果是奇数,则它这位是1

    最后结果既可以用二进制表示,也可以用十进制表示,考虑到数很大,但是操作次数不多,可以用map容器维护这个特征值。

3.AC代码

#include <bits/stdc++.h>#define INF 0x3f3f3f3f#define maxn 100010#define lson root << 1#define rson root << 1 | 1#define lent (t[root].r - t[root].l + 1)#define lenl (t[lson].r - t[lson].l + 1)#define lenr (t[rson].r - t[rson].l + 1)#define N 1111#define eps 1e-6#define pi acos(-1.0)#define e exp(1.0)using namespace std;const int mod = 1e9 + 7;typedef long long ll;typedef unsigned long long ull;map<ll, int> vis;int main(){#ifndef ONLINE_JUDGE    freopen("in.txt", "r", stdin);    freopen("out.txt", "w", stdout);    long _begin_time = clock();#endif    int t;    char opt[3];    ll x;    vis.clear();    scanf("%d", &t);    while (t--)    {        scanf("%s%I64d", opt, &x);        int pos = 0, tmp = 0;        //printf("%d : ", x);        while (x)        {            int dig = (x % 10) & 1;            if (dig)                tmp += pow(2, pos);            pos++;            x /= 10;        }        //printf("%d : %d\n", tmp, pos);        if (opt[0] == '+')            vis[tmp]++;        else if (opt[0] == '-')        {            vis[tmp]--;            if (!vis[tmp])                vis.erase(tmp);        }        else            printf("%d\n", vis[tmp]);    }#ifndef ONLINE_JUDGE    long _end_time = clock();    printf("time = %ld ms.", _end_time - _begin_time);#endif    return 0;}

B. Searching Rectangles

题目链接

分类:二分、构造

1.题意概述

  • 给你一个r×c的矩阵,其中有两个不相交的长方形(可能是点的形式),你可以最多询问200次,某个区域内完整包含的长方形个数。需要你确定这两个长方形的坐标(左上角和右下角),有意思点在于这是人机交互题,必须用flush进行。

2.解题思路

  • 先考虑单独在坐标格纸中只有一个长方形情况,对于每一条边,我们询问策略可以考虑二分地进行:
    • 例如右侧边,我们可以固定左上角坐标(1,1),二分地寻找右下角坐标(x,n),判断一个坐标合法只需看人机交互返回结果为0还是为1。
  • 下面回到原问题,因为题目保证长方形直接不会交叉,那么我们总能找到边界线把它们分成两个单独的区域,使得问题转化为上面那种简单的模式。容易观察出来,这个分界线不是平行于x轴就是y轴。

3.AC代码

#include <bits/stdc++.h>#define INF 0x3f3f3f3f#define maxn 100010#define lson root << 1#define rson root << 1 | 1#define lent (t[root].r - t[root].l + 1)#define lenl (t[lson].r - t[lson].l + 1)#define lenr (t[rson].r - t[rson].l + 1)#define N 1111#define eps 1e-6#define pi acos(-1.0)#define e exp(1.0)using namespace std;const int mod = 1e9 + 7;typedef long long ll;typedef unsigned long long ull;struct node{    int x1, y1, x2, y2;    node() {}    node(int a, int b, int c, int d)    {        x1 = a; y1 = b;        x2 = c; y2 = d;    }};vector<node> side, res;int query(int x1, int y1, int x2, int y2){    if (x1 > x2 || y1 > y2)        return 0;    printf("? %d %d %d %d\n", x1, y1, x2, y2);    fflush(stdout);    int ans;    scanf("%d", &ans);    return ans;}void get_line(int n){    int l = 0, r = n + 1;    // parell y    while (l < r)    {        int mid = l + r >> 1;        int ans1 = query(1, 1, mid, n);        int ans2 = query(mid + 1, 1, n, n);        if (ans1 == 1 && ans2 == 1)        {            side.push_back(node(1, 1, mid, n));            side.push_back(node(mid + 1, 1, n , n));            return;        }        else if (ans1 == 0 && ans2 == 0)            break;        else if (ans1 > 0)            r = mid;        else if (ans2 > 0)            l = mid + 1;    }    // parellx    l = 0, r = n + 1;    while (l < r)    {        int mid = l + r >> 1;        int ans1 = query(1, 1, n, mid);        int ans2 = query(1, mid + 1, n, n);        if (ans1 == 1 && ans2 == 1)        {            side.push_back(node(1, 1, n, mid));            side.push_back(node(1, mid + 1, n, n));            return;        }        else if (ans1 == 0 && ans2 == 0)            break;        else if (ans1 > 0)            r = mid;        else if (ans2 > 0)            l = mid + 1;    }}void solve(int x1, int y1, int x2, int y2){    int u, d, l, r;    int x, y, mid;    x = 0, y = y2 - y1 + 2;    while(x < y)    {        mid = x + (y - x) / 2;        int ans = query(x1, y1, x2, y1 + mid - 1);        if(ans == 1)            y = mid;        else            x = mid + 1;    }    u = y1 + x - 1;    x = 0, y = y2 - y1 + 2;    while(x < y)    {        mid = x + (y - x) / 2;        int ans = query(x1, y1 + mid, x2, y2);        if(ans == 0)            y = mid;        else            x = mid + 1;    }    d = y1 + x - 1;    x = 0, y = x2 - x1 + 2;    while(x < y)    {        mid = x + (y - x) / 2;        int ans = query(x1 + mid, y1, x2, y2);        if(ans == 0)            y = mid;        else            x = mid + 1;    }    l = x1 + x - 1;    x = 0, y = x2 - x1 + 2;    while(x < y)    {        mid = x + (y - x) / 2;        int ans = query(x1, y1, x1 + mid - 1, y2);        if(ans == 1)            y = mid;        else            x = mid + 1;    }    r = x1 + x - 1;    res.push_back(node(l, d, r, u));}int main(){#ifndef ONLINE_JUDGE    freopen("in.txt", "r", stdin);    freopen("out.txt", "w", stdout);    long _begin_time = clock();#endif    int n;    res.clear();    side.clear();    scanf("%d", &n);    get_line(n);    solve(side[0].x1, side[0].y1, side[0].x2, side[0].y2);    solve(side[1].x1, side[1].y1, side[1].x2, side[1].y2);    printf("! %d %d %d %d %d %d %d %d\n", res[0].x1, res[0].y1, res[0].x2, res[0].y2, res[1].x1, res[1].y1, res[1].x2, res[1].y2);    fflush(stdout);#ifndef ONLINE_JUDGE    long _end_time = clock();    printf("time = %ld ms.", _end_time - _begin_time);#endif    return 0;}

C. Sonya and Problem Wihtout a Legend

题目链接

分类:dp、思维、排序

1.题意概述

  • 给你一个长度为n的序列,可以进行若干次操作,每次操作让它+1或者-1,问你最少需要经过多少次操作能够使得它严格单调递增。

2.解题思路

  • 首先考虑不是严格递减的情况:
    • 不严格递减最后修改完成后的各个数一定是原序列中的某一个数——这个大概可以这么理解:原序列,从左到右扫过去,如果左边的大于右边的,要嘛左边的减掉使其等于右边的,要嘛右边的加上使其等于左边的。
    • 考虑动态规划:dp[i][j]表示序列前i个数都单调递增且第i个数更改为不大于原序列中第j个数的最少代价,那么转移方程是:
    • dp[i][j]=min(dp[i][j1],dp[i1][j]+abs(a[i]b[i]))
  • 回到原问题,要求严格单调递增,可以转化成不严格单调递增情况:
    • 怎么做?让原序列每个数a[i]减去i
    • 因为:ai<ai+1aiai+11aiiai+1(i+1)的01矩阵,在给定正方形区域(x1,y1) ~ (x2,y2)内由1构成的正方形矩阵的最大边长。

    2.解题思路

    • 容易想到的是dp[i][j]表示以(i,j)为右下角的正方形的边长,那么很容易得到转移方程:
      • dp[i][j]=min(dp[i1][j],dp[i][j1],dp[i1][j1])
    • 那么我们对于每个询问区间(x1,y1)(x2,y2),我们考虑二分答案:对于每个mid,我们在区间(x1+mid1,y1+mid1) ~ (x2,y2)内是否存在边长为mid的矩形即可。
    • 但是考虑到询问次数很大,我们可以考虑2D sparse table预处理出以每个点为右下角的区间最值。

    3.AC代码

    #include <bits/stdc++.h>#define INF 0x3f3f3f3f#define maxn 100010#define lson root << 1#define rson root << 1 | 1#define lent (t[root].r - t[root].l + 1)#define lenl (t[lson].r - t[lson].l + 1)#define lenr (t[rson].r - t[rson].l + 1)#define N 1001#define eps 1e-6#define pi acos(-1.0)#define e exp(1.0)using namespace std;const int mod = 1e9 + 7;typedef long long ll;typedef unsigned long long ull;int dp[N][N][11][11], st[N];void ST(int n, int m){    st[0] = -1;    for (int i = 1; i <= max(n, m); i++)        st[i] = (i & (i - 1)) == 0 ? st[i - 1] + 1 : st[i - 1];    for (int a = 0; a <= st[n]; a++)        for (int b = 0; b <= st[m]; b++)            if (a + b)            {                for (int i = 1; i + (1 << a) - 1 <= n; i++)                    for (int j = 1; j + (1 << b) - 1 <= m; j++)                        if (a)                            dp[i][j][a][b] = max(dp[i][j][a - 1][b], dp[i + (1 << a - 1)][j][a - 1][b]);                        else                            dp[i][j][a][b] = max(dp[i][j][a][b - 1], dp[i][j + (1 << b - 1)][a][b - 1]);            }}int rmq(int x1, int y1, int x2, int y2){    int k1 = st[x2 - x1 + 1];    int k2 = st[y2 - y1 + 1];    x2 -= (1 << k1) - 1;    y2 -= (1 << k2) - 1;    return max(max(dp[x1][y1][k1][k2], dp[x1][y2][k1][k2]), max(dp[x2][y1][k1][k2], dp[x2][y2][k1][k2]));}int main(){#ifndef ONLINE_JUDGE    freopen("in.txt", "r", stdin);    freopen("out.txt", "w", stdout);    long _begin_time = clock();#endif    int n, m, q;    scanf("%d%d", &n, &m);    for (int i = 1; i <= n; i++)        for (int j = 1; j <= m; j++)        {            int x;            scanf("%d", &x);            if (x)                dp[i][j][0][0] = min(dp[i - 1][j][0][0], min(dp[i][j - 1][0][0], dp[i - 1][j - 1][0][0])) + 1;        }    ST(n, m);    scanf("%d", &q);    while (q--)    {        int x1, x2, y1, y2;        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);        int ans = 0, l = 0, r = min(x2 - x1, y2 - y1) + 1;        while (l <= r)        {            int mid = l + r >> 1;            if (rmq(x1 + mid - 1, y1 + mid - 1, x2, y2) >= mid)            {                ans = mid;                l = mid + 1;            }            else r = mid - 1;        }        printf("%d\n", ans);    }#ifndef ONLINE_JUDGE    long _end_time = clock();    printf("time = %ld ms.", _end_time - _begin_time);#endif    return 0;}

    E. Sonya Partymaker

    题目链接

    分类:二分、dp

    1.题意概述

    • 有n个人,m个凳子,其中凳子被编号为1…m围成环形。1和m、2相邻,2和1、3相邻,以此类推…现在每个人可以选择顺时针/逆时针地走动,边走边把当前位置凳子给抽走,问你最短时间将凳子都抽完。

    2.解题思路

    • 考虑1到n所有间隙的最大值,假设n和1的间隙是最大的,设为M。显然最终答案ansM
    • 我们考虑二分答案ans,因为答案肯定小于最大值,也就是这个最优策略一定满足:1,n直接有人面向对方相对着走(左右覆盖)。
      • 假设其他人都是顺时针边走边提凳子,那么我们容易发现最优解中ID为1和2之间一定有人向左走!否则,例如3才向左走,那么1对答案没有贡献(因为n和1间距最大,1如果向右走,n还是得走到头游戏才结束。以此类推,以三个人为周期去递推:
    • 我们枚举12谁向左走,用dp[i]表示前i个人一共覆盖(搬走)多少个凳子,对于每个i,转移方程是:
      1. i向右走,那么要求dp[i1]ai1,这时候可以用ai+ans新答案。
      2. i向左走,那么要求dp[i1]aians1,这时候可以用ai更新答案。
      3. i向左走,但是玩家(i1)依然向左走,那么要求dp[i2]aians1,同时用ai1+ans更新答案。

    3.AC代码

    #include <bits/stdc++.h>#define INF 0x3f3f3f3f#define maxn 100010#define lson root << 1#define rson root << 1 | 1#define lent (t[root].r - t[root].l + 1)#define lenl (t[lson].r - t[lson].l + 1)#define lenr (t[rson].r - t[rson].l + 1)#define N 1111#define eps 1e-6#define pi acos(-1.0)#define e exp(1.0)using namespace std;const int mod = 1e9 + 7;typedef long long ll;typedef unsigned long long ull;int n, m, a[maxn], dp[maxn];bool check(int x){    for (int sta = 0; sta < 2 && sta < n; sta++)    {        dp[sta] = sta ? max(a[0] + x, a[1]) : a[0];        for (int i = sta + 1; i < n; i++)        {            dp[i] = dp[i - 1];            if (dp[i - 1] >= a[i] - x - 1) // left                dp[i] = max(dp[i], a[i]);            if (dp[i - 1] >= a[i] - 1) // right                dp[i] = max(dp[i], a[i] + x);            if (i >= 2 && dp[i - 2] >= a[i] - x - 1)                dp[i] = max(dp[i], a[i - 1] + x);        }        if (dp[n - 1] >= min(m - 1, m + a[sta] - x - 1))            return 1;    }    return 0;}int main(){#ifndef ONLINE_JUDGE    freopen("in.txt", "r", stdin);    freopen("out.txt", "w", stdout);    long _begin_time = clock();#endif    scanf("%d%d", &m, &n);    for (int i = 0; i < n; i++)        scanf("%d", &a[i]);    sort(a, a + n);    pair<int, int> best(a[0] + m - a[n - 1], 0);    for (int i = 1; i < n; i++)        best = max(best, make_pair(a[i] - a[i - 1], i));    rotate(a, a + best.second, a + n);    for (int i = n - 1; i >= 0; i--)    {        a[i] -= a[0];        if (a[i] < 0)            a[i] += m;    }    int l = 0, r = a[0] + m - a[n - 1] - 1;    int ans = r;    while (l <= r)    {        int mid = l + r >> 1;        if (check(mid))        {            ans = mid;            r = mid - 1;        }        else l = mid + 1;    }    printf("%d\n", ans);#ifndef ONLINE_JUDGE    long _end_time = clock();    printf("time = %ld ms.", _end_time - _begin_time);#endif    return 0;}
原创粉丝点击