A. Sonya and Queries
分类:思维、map容器应用
1.题意概述
- 定义一个集合A,你有三种操作
- + ai —— 将该元素加入集合当中(允许相同元素重复)
- − ai —— 将该元素从集合中删除(保证删除合法性)
-
? s
—— 统计符合条件的元素个数
- 其中
s
是01串,为0表示该位是偶数,为1表示该位为奇数,如果
s 的位数比某元素低,那么前缀自动补0
- 其中
s
是01串,为0表示该位是偶数,为1表示该位为奇数,如果
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][j−1],dp[i−1][j]+abs(a[i]−b[i]))
- 回到原问题,要求严格单调递增,可以转化成不严格单调递增情况:
- 怎么做?让原序列每个数 a[i] 减去i
- 因为: ai<ai+1⇔ai≤ai+1−1⇔ai−i≤ai+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.相关题型
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[i−1][j],dp[i][j−1],dp[i−1][j−1])
- 那么我们对于每个询问区间 (x1,y1) 、 (x2,y2) ,我们考虑二分答案:对于每个mid,我们在区间 (x1+mid−1,y1+mid−1) ~ (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。显然最终答案 ans≤M 。
- 我们考虑二分答案
ans
,因为答案肯定小于最大值,也就是这个最优策略一定满足:
1,n
直接有人面向对方相对着走(左右覆盖)。
- 假设其他人都是顺时针边走边提凳子,那么我们容易发现最优解中ID为1和2之间一定有人向左走!否则,例如3才向左走,那么1对答案没有贡献(因为n和1间距最大,1如果向右走,n还是得走到头游戏才结束。以此类推,以三个人为周期去递推:
- 我们枚举
、1、2
谁向左走,用
dp[i]
表示前
i
个人一共覆盖(搬走)多少个凳子,对于每个
i ,转移方程是:
-
i
向右走,那么要求
dp[i−1]≥ai−1 ,这时候可以用 ai+ans 新答案。 -
i
向左走,那么要求
dp[i−1]≥ai−ans−1 ,这时候可以用 ai 更新答案。 -
i
向左走,但是玩家
(i−1) 依然向左走,那么要求 dp[i−2]≥ai−ans−1 ,同时用 ai−1+ans 更新答案。
-
i
向右走,那么要求
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;
}