Codeforces Round #371(Div.1)

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)

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 3003
#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 a[N], b[N];
ll dp[N][N];
int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    long _begin_time = clock();
#endif
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        a[i] -= i;
        b[i] = a[i];
    }
    sort(b + 1, b + n + 1);
    int cnt = unique(b + 1, b + n + 1) - (b + 1);
    memset(dp, 0x3f, sizeof dp);
    for (int i = 1; i <= cnt; i++)
        dp[1][i] = min(dp[1][i - 1], (ll)abs(a[1] - b[i]));
    for (int i = 2; i <= n; i++)
        for (int j = 1; j <= cnt; j++)
            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j] + abs(a[i] - b[j]));
    printf("%I64d\n", dp[n][cnt]);
#ifndef ONLINE_JUDGE
    long _end_time = clock();
    printf("time = %ld ms.", _end_time - _begin_time);
#endif
    return 0;
}

4.相关题型

POJ-3666

D. Animals and Puzzle

题目链接

分类:二分、ST表

1.题意概述

  • 给你一片 n×m 的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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值