题意:给你两杯水a,b(都放在一个无穷大的容器中),再给你一个杯子体积为c(一次最多能装c的水),问你用这个杯子装水最,少通过多少次可以让原来的这两杯水相同(每次装水的体积可以是小数)
思路:其实就可以看成相差(int)abs(a-b),每次最多可以减小2*c的差值,问你最少多少次可以让差值为0,就直接(int)abs(a-b)/(2*c)上取整即可
代码:
void solve()
{
int a, b, c;
cin >> a >> b >> c;
int temp = (int)abs(a - b);
c <<= 1;
if (temp % c != 0)
cout << temp / c + 1 << endl;
else
cout << temp / c << endl;
}
B - The Corridor or There and Back Again
题意:给出n个地点的坐标(这几个地点会有炸弹),以及他们需要多久才爆炸,问你最远能走到哪个位置能够安全地回来
思路:其实应该想到,对于一个有炸弹的房间,他将会在你到达这个房间的s秒后爆炸,那么你在经过这个房间后最多可以走(s-1)/2步,其实也就是s/2下取整,所以直接暴力遍历一遍看最多能走多远就行(要在所有炸弹都不爆炸的基础上)
代码:
void solve()
{
int n;
cin >> n;
int mx = INT_MAX;
while (n--)
{
int a, b;
cin >> a >> b;
b++;
mx = min(mx, a - 1 + b / 2);
}
cout << mx << endl;
}
题意:给你一段区间的左右端点l,r.看你能否找到两个数a,b满足 l=<(a+b)<=r,同时gcd(a,b)!=1(也就是a和b不互质)
思路:其实容易发现,找偶数会比较方便(但是不能是2),所以我们需要在[l,r]内找一个数使得他是大于2的偶数,然后可以将他分成2和这个数-2,是满足题意的,如果找不到满足条件的偶数,也可以找奇数,但是不能是质数,因为这样分成的两个数肯定互质(可以证明),然后再暴力找一个数a,满足gcd(a,这个数-a)!=1,然后直接输出即可
代码:
const int N = 2e5 + 10;
bool check(int x)
{
for (int i = 2; i * i <= x; i++)
if (x % i == 0)
return false;
return true;
}
void solve()
{
int l, r;
cin >> l >> r; // 找到两个不互素的数 a,b 使得 l=<a+b<=r
int ans = 0;
for (int i = l; i <= r; i++)
{
if (i > 2 && i % 2 == 0)
{
ans = i;
break;
}
else if (!check(i) && i % 2 == 1)
{
ans = i;
break;
}
}
if (ans == 0)
{
cout << -1 << endl;
return;
}
else
{
if (ans % 2 == 0)
{
cout << 2 << " " << ans - 2 << endl;
return;
}
else
{
for (int i = 3; i <= ans; i++)
if (gcd(i, ans - i) != 1)
{
cout << i << " " << ans - i << endl;
return;
}
}
}
}
题意:给你一个长度为n的permutation(指的是[1,n]中的每个数都只在这个permutation中出现一次),再给你两个数x,y,定义一个f=a[x]+a[2*x]+……a[[n/x]*x]-(a[y]+a[2*y]+……a[[n/y]*y]),你可以重新排列这个permutation使得f的值最大,要你输出最大值
思路:贪心的想,肯定是把大的数放到加的里面,小的数放到减的里面,而从样例解释中可以发现,如果下标是x和y的最小公倍数的倍数的,再计算过程中抵消了,所以对f的值并没有贡献,在计算的过程中可以减掉,所以只要计算出n以内有多少个x的倍数,有多少个y的倍数,再减去他们的最小公倍数的倍数的个数,再进行贪心就行(tips:如果直接暴力给会tle,所以直接用等差数列的求和公式即可)
代码:
void solve()
{
int n, x, y;
cin >> n >> x >> y;
int cnt1 = n / x;
int cnt2 = n / y;
int temp = (x * y) / gcd(x, y);
int cnt3 = n / temp;
cnt1 -= cnt3, cnt2 -= cnt3;
int sum1 = 0, sum2 = 0; // (n+n-cnt1+1)*cnt1/2;
sum1 = (2 * n - cnt1 + 1) * cnt1 / 2;
sum2 = (1 + cnt2) * cnt2 / 2;
cout << sum1 - sum2 << endl;
}
题意:给你一列数,再给你一个二进制字符串,字符串的长度和数的长度相同,然后会给你q次询问,询问1: 给你一段区间的左端点和右端点l和r,要你将字符串中对应的下标的进行翻转(0变成1,1变成0),询问2: 给你一个数要求你将字符串中的等于这个数的下标对应到那一列数中,求他们的异或和
思路:其实可以发现,将[l,r]的字符串进行翻转,其实就是在原来的数列中的[l,r]进行异或操作,所以其实只需要对原来的这列数进行操作就行(可以通过处理一个异或前缀和进行处理),不用对字符串进行修改,只要保证在每次进行查询2的之前把相应的1和0的位置的异或和处理出来就行(具体看代码)
代码:
const int N = 1e5 + 10; // 前缀异或和
int n;
int a[N];
string s;
int q;
void solve() // 思路大致是先预处理出异或前缀和数组,再在2操作前将所有的0和1的位置的数的异或和处理出来,1操作的将区间反转实际上就是将这段区间的数再异或一遍,也就是对原来的0的异或和和1的异或和再出处理一遍
{
cin >> n;
vector<int> b(n + 1, 0);
int sum0 = 0, sum1 = 0;
for (int i = 1; i <= n; i++)
cin >> a[i], b[i] = b[i - 1] ^ a[i]; // 维护前缀异或和
cin >> s; // 可以通过b[r]^b[l-1]得到[l,r]这一段区间的异或和
s = " " + s;
for (int i = 1; i <= n; i++)
{
if (s[i] == '1')
sum1 ^= a[i];
else
sum0 ^= a[i];
}
cin >> q;
while (q--)
{
int opt;
cin >> opt;
if (opt == 1) // 对字符串进行翻转操作
{
int l, r;
cin >> l >> r;
sum1 ^= b[r] ^ b[l - 1]; // 将这翻转实际上就是对这一段进行异或
sum0 ^= b[r] ^ b[l - 1];
}
else if (opt == 2) // 对求对应下标数的异或值
{
int g;
cin >> g;
if (g)
cout << sum1 << " ";
else
cout << sum0 << " ";
}
}
cout << endl;
}
题意:给你n组数,其中动物i害怕动物a[i],第i种动物卖出有c[i]的利润,如果一种动物在他害怕的动物之前被卖出,就可以得到双倍利润,让你安排卖出顺序,使得利润最大
思路:显然要先将没有被其他动物害怕的动物先卖掉,这个关系可以看成一个从i到a[i]的图,其实容易看出可以用拓扑排序来完成这个操作,但是他有可能是存在环的,这个拓扑排序的不能处理。对于有环的,可以先dfs找到环中利润值最小的数,再以他害怕的动物作为环的起点遍历整个环,再处理就可以得到最大值
代码:
const int N = 1e5 + 10;
int a[N];
int d[N];
int c[N];
int n;
int id = -1; // 记录环上最小值的标号
int mi = 1e9 + 10; // 记录环上价值最小值
bool st[N]; // 判断正这个点访问了没有n
void dfs(int u)
{
if (st[u])
return; // 访问过的直接返回
st[u] = true; // 拿出和u相邻的点,因为u这个点已经知道了
int j = a[u];
if (c[j] < mi)
mi = c[j], id = j;
dfs(j); // 递归和他相邻的点
}
void solve() // 1 361
{
cin >> n;
queue<int> q; // 拓扑排序的队列
vector<int> ans; // 存答案的
for (int i = 0; i <= n; i++)
st[i] = false, d[i] = 0; // 动态初始化
for (int i = 1; i <= n; i++)
cin >> a[i], d[a[i]]++; // 入度加加
for (int i = 1; i <= n; i++)
cin >> c[i];
for (int i = 1; i <= n; i++)
if (!d[i])
q.push(i); // 让入度为0的点入队
while (q.size())
{
int t = q.front();
ans.push_back(t); // 队列里面的点是满足拓扑序的所以直接放进答案中
q.pop(); // 取出队头元素
st[t] = true; // t已经被访问了
if (--d[a[t]] == 0)
q.push(a[t]); // 遍历和t相邻的点
} // 如果接下来再处理有环的情况
for (int i = 1; i <= n; i++)
if (!st[i])
{
mi = c[i];
id = i; // 初始化
dfs(i); // 从这个点开始找最小价值的动物,再将其害怕的动物放进答案中
// 类似tarjan写一个do while循环遍历环上的点
int x = a[id];
do
{
ans.push_back(x);
x = a[x];
} while (x != a[id]);
}
for (auto it : ans)
cout << it << " ";
cout << endl;
}
题意:给你一列数,你可以进行一次操作,分别选取一段区间的左右端点l,r,然后用这段区间的所有数的乘积代替这个区间的数,要你求可能的最大值,并输出操作区间的左右端点
思路:其实通过观察样例可以发现,可以选取第一个非1的数的位置,以及最后一个非1的位置,然后再分析可以发现这段区间的数的乘积如果大于1e9是可以直接选第一个非1的数和最后一个非1的数即可,如果乘积小于1e9,就可以先将所有的非1的位置储存起来,暴力枚举,贪心得到最大值;
代码:
void solve() // 总的思路其实就是看所有数的乘积是不是大于1e9是的话就是第一个和最后一个非1的数的位置,不是的话就记录每个非1的位置,暴力枚举再贪心
{
int n;
cin >> n;
vector<int> a(n + 1);
vector<int> pos;
for (int i = 1; i <= n; i++)
cin >> a[i];
int l = 1, r = n; // 先找到第一个非0和最后一个非0的位置,如果乘积大于1e9,就是这两个位置不是的话就直接暴力贪心
while (l < r && a[l] == 1)
l++;
while (l < r && a[r] == 1)
r--;
int temp = 1;
int sum = 0;
for (int i = l; i <= r; i++)
{
temp *= a[i];
if (temp > 1e9)
{
cout << l << " " << r << endl;
return;
}
}
// 需要暴力枚举了
for (int i = 1; i <= n; i++)
{
sum += a[i];
if (a[i] > 1)
pos.push_back(i);
}
int l1 = 1, r1 = 1, ans = sum;
for (int i = 0; i < pos.size(); i++)
{
int l = pos[i]; // 左端点
int res = 1; // 求某段区间的乘积
int s = 0;
for (int j = i; j < pos.size(); j++)
{
int r = pos[j];
s += a[r] - 1;
res *= a[r];
int cnt = sum - (r - l + 1) - s + res; // 因为只对l和r这段区间进行操作,所以只需要计算在这段区间中进行操作后的值(r-l+1)个1,以及这段区间a的和就行,再进行比较
// 每次需要得到操作后对原序列的影响。所以用sum
if (cnt > ans) // 而要求最大值,需要更新值,就用另一个代表来搞就行
{
ans = cnt;
l1 = l, r1 = r;
}
}
}
cout << l1 << " " << r1 << endl;
}