题目链接
更新ing
A 小沙的炉石
题目大意:
n张法术进攻牌,每张消耗一点法力,造成一点基础伤害。
m张法术恢复牌,不消耗法力,每次可以恢复一点法力。一开始有一点法力,法力无上限。
法术伤害初始为0,实施法术后,法术伤害+1.
法术进攻牌的伤害 = 法术伤害+基础伤害。
问能否将敌人的血量恰好变成0?
二分/结论
分析
- 固定攻击次数,那么攻击范围是一个区间。
假设攻击a次,恢复b次,则a <= b + 1。
最小攻击量为:攻击、恢复、攻击、恢复…直到用完所有攻击。则攻击总量 = (1 + 3 + 5 + …+ (2a-1) = a^2;
最大攻击量为:恢复、恢复、恢复…攻击、攻击… 。则攻击总量 = (b + 1) + (b+2)+(b+3)…+(b + a) = ab + (a + 1) * a / 2. 此时b = m
二分写法
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(void)
{
LL n, m, k;
scanf("%lld%lld%lld", &n, &m, &k);
n = std::min(n, m + 1);
while(k --)
{
LL x;
scanf("%lld", &x);
LL l = 1, r = n;
bool flag = false;
//区间左端点 <= x && 区间右端点 >= x
while(l <= r)
{
LL mid = l + r >> 1;
LL minn = mid * mid;
LL maxn = (m * mid) + (mid + 1) * mid / 2;
if(x >= minn && x <= maxn)
{
flag = true;
break;
}
else if(x < minn) r = mid - 1;
else l = mid + 1;
}
if(flag) puts("YES");
else puts("NO");
}
}
结论写法
解释minn: 在满足区间左端点 <= x的情况下,让攻击次数尽可能大,这是检查右区间即可
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(void)
{
LL n, m, k;
scanf("%lld%lld%lld", &n, &m, &k);
n = std::min(n, m + 1);
while(k --)
{
LL x;
scanf("%lld", &x);
LL minn = std::sqrt(x);
//找到当即使所有进攻牌数都用时的最小值且这个值的最小大于等于x
if(minn > n) minn = n;
if(minn * m + (minn + 1) * minn / 2 >= x) puts("YES");
else puts("NO");
}
}
C 小沙的杀球
签到题,能杀就杀,不能杀不杀就完事了。
D 小沙的涂色
题目大意:
模拟/构造
分析
- 如果变长是3的倍数我们无法构造出来。
总格子数n ^2 % 3 == 0, 不能做到仅剩一个格子。 - 染色方法很多种,下图是下面代码的栗子
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int n, idx;
int f[N][N];
void draw3(int x, int y) //1 1 2
{ //1 2 2
for(int j = y; j <= n; j += 3)
{
f[x][j] = f[x][j + 1] = f[x + 1][j] = ++ idx;
f[x][j + 2] = f[x+1][j+1] = f[x+1][j+2] = ++ idx;
}
}
void draw2(int x) //1
{ //1 1 2
for(int i = x; i <= n; i ++)// 2 2
{
if(i & 1) f[i - 1][1] = f[i][1] = f[i][2] = ++ idx;
else f[i-1][3] = f[i][2] = f[i][3] = ++ idx;
}
}
void draw1(int x, int y) //1 1
{ //1 2
for(int i = x; i <= n; i += 3) //2 2
{
for(int j = y; j <= n; j += 2)
{
f[i][j] = f[i][j+1] = f[i+1][j] = ++ idx;
f[i+1][j+1] = f[i + 2][j] = f[i+2][j+1] = ++ idx;
}
}
}
int main(void)
{
scanf("%d", &n);
if(n % 3 == 0)
{
puts("NO");
return 0;
}
if(n == 1)
{
puts("YES");
puts("0");
return 0;
}
if(n % 3 == 1)
{
//满足此条件n最小为4, 先把前四行填满,左下角空着
f[1][1] = f[1][2] = f[2][1] = ++idx;
f[4][3] = f[4][4] = f[3][4] = ++idx;
f[3][1] = f[3][2] = f[4][2] = ++ idx;
f[1][3] = f[1][4] = f[2][4] = ++idx;
f[2][2] = f[2][3] = f[3][3] = ++ idx;
draw3(1, 5);
draw3(3, 5);
if(n & 1)
{//如果n是奇数, draw2可以把前三列剩下的填满,并且有一个空格
draw2(5);
//剩下列数一定是偶数个,且行数是3的倍数,进行draw1操作
draw1(5, 4);
}
else draw1(5, 1); //如果n是偶数, 此时列数是偶数,且行数是3的倍数
}
else //n % 3 == 2
{
f[1][1] = f[1][2] = f[2][2] = ++ idx;
//把前两行填满
draw3(1, 3);
if(n & 1) //奇数
{
//把前三列填满且有一个空,
draw2(3);
// 此时未填的行数是3的倍数,未填的列数是偶数
draw1(3, 4);
}
else draw1(3, 1);//列数是偶数,且未填的行数为3的倍数
}
puts("YES");
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
{
printf("%d ", f[i][j]);
}
puts("");
}
return 0;
}
E 小沙的长路
题目大意:
小沙有一个n个点的完全图(完全图是一个简单的无向图,其中每对不同的顶点之间都恰连有一条边相连),你可以给每条边选择方向,规定每条边只能走一次,请问n个点的完全图的最长路径,现在现在小沙想要知道它的最小值和最大值分别是多少?
图论
分析
- 对于最长路的最小值,我们考虑,如果图上有环,那么我们肯定能尽可能多的走环,这样的话我一定会比我不走环更长,所以我们构造的图要尽可能的没环。在没环的情况下,我们只会经过每个点各一次,所以总长度是n-1
- 最长路的最大值:
我们考虑尽可能的将每一条路都走遍
,我们可以理解为对一个完全图进行删边,我们需要删尽可能少的边,从而使他能够从头走到尾,也就是构造出一个欧拉回路。
欧拉回路:一个图中最多应该只有度为2的奇数点(一笔画问题)。`完全图最多有n * (n - 1)/2条边。对于偶数个点的图,每个点的入度均为奇数,最多有两个入度为奇数的点,所以删去的边数最少为(n - 2)/2条。
#include <bits/stdc++.h>
using namespace std;
int main(void)
{
long long n;
cin >> n;
cout << n - 1 << " ";
long long t = (n % 2 == 0);
cout << n * (n-1) / 2 - t*(n - 2) / 2 << endl;
return 0;
}
F 小沙的算数
题目大意:
算数题中,他们中间的符号都是+或者× ,并且每个题+和×的位置都是一样的。 由于答案数字过大 所以我们对1000000007取模
输入描述:
第一行 给定二个个数n代表有n个数进行计算 q代表有q次询问
第二行 给定一个一个长度为n-1的*+字符串 表示我们要进行的计算符号是什么
第三行 给定n个整数,代表这每个位置上的数字
随后q行每行两个数字x,y
代表着将第x个数字改成y,且x≤n
思维、并查集、逆元
分析
- 乘法优先级大于加法,
每个+会分开一段区间
,每个区间的值互不干扰,所以可以提前将每个区间的信息整合道一个值中,同时id数组记录下标对应的哪个区间(可以用并查集,代码中用id记录了)。在进行修改时,先将未修改的答案ans - 该区间的值, 该区间的值/=修改前的值(需要用到逆元
), 该区间的值*=修改后的值, ans + 该区间的值
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7, N = 1e6 + 10;
int a[N], id[N];
LL f[N];
LL qmi(LL a, LL b)
{
LL res = 1;
while(b)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
int main(void)
{
int n, q;
scanf("%d%d", &n, &q);
string s;
cin >> s;
s = " " + s;
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]), f[i] = 1;
int temp = 1;
for(int i = 1; i <= n; i ++)
{
f[temp] = f[temp] * a[i] % mod;
id[i] = temp;
if(s[i] == '+') temp ++;
}
LL res = 0;
for(int i = 1; i <= temp; i ++)
{
res = (res + f[i]) % mod;
}
while(q --)
{
int i, j;
scanf("%d%d", &i, &j);
res = (res - f[id[i]] + mod) % mod;
f[id[i]] = f[id[i]] * qmi(a[i], mod - 2) % mod;
f[id[i]] = f[id[i]] * j % mod;
a[i] = j;
res = (res + f[id[i]]) % mod;
printf("%lld\n", res);
}
}
H 小沙的数数
题目大意:
二进制运算、思维
分析
a + b = 2 * (a & b) + (a ^ b)
在a + b一定的情况下,要想a ^ b最大,则a & b应该为0。
所以,a + b的二进制中的为1的位在数组中也应只有一个数该二进制位为1,1个1对应n中选择。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
LL n, m;
int main(void)
{
cin >> n >> m;
//m为1的a数组只能有一位为1,
n %= mod;
LL ans = 1;
LL temp = m;
for(int i = 0; i < 63; i ++){
if((temp >> i) & 1){
ans *= n;
ans %= mod;
}
}
cout << ans % mod << endl;
return 0;
}
I 小沙的构造
题目大意:
这题小沙想让你构造出一串字符串,这个字符串有以下几个特点。
1,他是对称串,他关于这个字符串的垂直对称。例如"()",我们将他翻转过来他也是“()”,例如“p“翻转过来就是“q“。
2, 这个串串的所有字符都是除小写字母以外的可见字符(不包括空格)。
现在小沙想让你构造一个长度为nn的不同字符数量为mm的一个字符串。
可见字符如下:
!"#$%&’()+,-./0123456789:;<=>?@[]^_`QWERTYUIOPASDFGHJKLZXCVBNM{}|~
其中具有对称性质的有
"!'±.08:=^_WTYUIOAHXVM|<>/[]{}()
构造、思维
分析
- 具有对称性质的字符是可以放到奇数长度串的中间位置的
需要配对使用的字符用时需要配对比如[], {}(代码中的ab数组的前五个)。 - 当n为奇数时,需要在中间放一个自对称的字符,因为
"
在ab数组的末尾(我们在for循环遍历时,并未检查是否用过下标为k的字符)所以我们填位于最后一个", 使得尽可能满足m的条件 - m也有可能出现奇数的情况, 对应的便是mp.size() + 1 == m, 此时需要填一个自对称的字符,下标>=5
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
char res[N];
set<char> mp;
char a[] = "({[\\<!'*+-.08:=^_WTYUIOAHXVM|\"";
char b[] = ")}]/>!'*+-.08:=^_WTYUIOAHXVM|\"";
int n, m;
int main(void)
{
cin >> n >> m;
if(m == 1) while(n --) putchar('*');
else
{
if(n & 1)
{
res[1 + n >> 1] = ('"');
mp.insert('"');
}
for(int i = 1, k = 0; i <= n / 2; i ++)
{
if(mp.size() + 1 == m) //m为奇数的情况
k = max(k, 5);
res[i] = a[k], res[n - i + 1] = b[k];
mp.insert(a[k]);
mp.insert(b[k]);
if(mp.size() < m) k ++;
k %= 30;
}
puts(mp.size() != m ? "-1" : res + 1);
}
}
K 小沙的步伐
签到题,略。
L 小沙的remake(普通版)
题目大意:
题目链接
有一个顺序数组,需要按顺序选择,且当前选择的不能比已经选择的小,选择的条件:(1)位于第一个或者(2)前bi个以内至少存在一个已被选择。选择的序列,只要有一个不同,就是不同的选法。问能选择的序列有多少种
两点:
找到连续的,上升子序列且下标之差满足某个范围
求方案数
树状数组、DP
思路
- 我们需要存储下标并且排序(先按照权值后下标)
- 排完序后,寻找不大于当前数,并且下标也在它之前的方案数量,存储到当前数字的下标数组里。
- 树状数组:快速求前缀和, 可以修改某个数。
- 树状数组中(原数组的下标)下标对应该段之前的方案数;且可以快速求前缀和
#include<bits/stdc++.h>
namespace GenHelper
{
int z1,z2,z3,z4,z5,u,res;
int get()
{
z5=((z1<<6)^z1)>>13;
z1=((int)(z1&4294967)<<18)^z5;
z5=((z2<<2)^z2)>>27;
z2=((z2&4294968)<<2)^z5;
z5=((z3<<13)^z3)>>21;
z3=((z3&4294967)<<7)^z5;
z5=((z4<<3)^z4)>>12;
z4=((z4&4294967)<<13)^z5;
return (z1^z2^z3^z4);
}
int read(int m) {
u=get();
u>>=1;
if(m==0)res=u;
else res=(u/2345+1000054321)%m;
return res;
}
void srand(int x)
{
z1=x;
z2=(~x)^(0x23333333);
z3=x^(0x12345798);
z4=(~x)+51;
u = 0;
}
}
using namespace GenHelper;
using namespace std;
const int N=2e6+7,mod=1e9+7;
typedef long long LL;
int a[N],b[N];
pair<int, int>x[N];
LL tre[N]; //树状数组
int n;
int lowbit(int x)
{
return x & (-x);
}
void add(int x, int k)
{
while(k <= n)
{
tre[k] = (tre[k] + x) % mod;
k += lowbit(k);
}
}
LL getsum(int k)
{
LL sum = 0;
while(k)
{
sum = (tre[k] + sum) % mod;
k -= lowbit(k);
}
return sum;
}
int main(){
int seed;
scanf("%d %d",&n,&seed);
srand(seed);
for(int i=1;i<=n;i++){
a[i]=read(0),b[i]=read(i);
x[i].first = a[i];
x[i].second = i;
}
sort(x + 1, x + n + 1);
LL sum = 0;
for(int i = 1; i <= n; i ++)
{
LL temp = (getsum(x[i].second) - getsum(x[i].second - b[x[i].second] - 1) + 1 + mod) % mod;
add(temp, x[i].second);
}
cout << getsum(n);
return 0;
}
竞速版的该题也可以用该方法及代码过,此处略