总题目传送门
A. Deadline
分析
- 题意
- 给我们一个表达式 x + d/(x + 1) 让求它的最小值,
- 思路
- 基本不等式: ,当且仅当 a 、b>0, 且 a == b 的时候
- 对 原式变形为:[x + 1 + d/(x+1)] - 1, 之后对中括号部分应用基本不等式
代码
#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod 998244353
const int mxn = 2e5 + 10;
int n, d;
int main()
{
/* fre(); */
int T;
scanf("%d", &T);
while(T --)
{
scanf("%d %d", &n, &d);
if(ceil(2 * sqrt(d) - 1) <= n)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
B. Yet Another Meme Problem
分析
- 题意
- 给我们两个数 A、B(1<= A、B<=1e9),又给我们两个数变量a,b,这两个变量的取值范围是: ,
- 定义 ,举例子conc(12,34)=1234,
- 问使这个: 成立的对应的a、b的数量
- 思路
- 这么大的范围肯定不是暴力的,一定有规律在里面,而且给我们的是一个等式,我们可以通过对这个等式进行变形,获得更多有用的条件,变形如下:
- ,这里0的数量位b的十进制位有几位
- ,从这里可以推测出等式与a的值无关,所以a去任意值都可以(前提是:1<=a<=A),我们考虑b的取值为:b=9、b=99、b=999、b=999…,可以看出b的取值与B限制有关,
- 最终答案就是:A*b的取值个数
代码
#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod 998244353
const int mxn = 2e5 + 10;
int n, d;
int main()
{
/* fre(); */
int T;
scanf("%d", &T);
while(T --)
{
ll a; string s;
cin >> a >> s;
ll b = s.size();
int fg = 0;
for(auto x : s)
{
if(x != '9')
{
fg = 1;
break;
}
}
b -= fg;
printf("%lld\n", a * b);
}
return 0;
}
C. Two Arrays
分析
- 题意
- 给我们两个值 n、m
- 让我们找出两个序列a、b,它们的长度均为m,元素的范围均为[1,n]
- 且a[I]<=b[I],1<=I<=m
- a序列位非严格递增,b序列为非严格递减
- 问这样的a、b序列存在多少对?
- 思路
- 首先我们将b序列进行翻转(即: ),这样b就变成了非严格递增,且b[i]>=a[m] 因此我们可以a、b拼接成一个 非严格递增的序列c,那么c中有2*m个元素,且它们的范围均在1~n之间,
- 思路1——利用c序列的递增性,进行dp
1. 我们定义dp[i][j]的含义是:长度为i,以j作为结尾元素的序列,
2. 状态转移方程: ,dp[i][j]的状态转移有两部分:第一部分:规定dp[i][j]所代表的序列的最后一位(即:第i为)为j,那么剩下从第1位~到第i-1位的方案数是dp[i-1][j]
第二部分:规定dp[i][j]所代表的序列的最后一位不是j,那么总的1~i位的方案数位dp[i][j-1]
这两部分组成了dp[i][j]的方案数的全集 - 思路二——组合数学
- 通过对a、b拼接之后产生了一个有2m个元素的c序列,那么问题就行相当于转化为了,从n个数字中选择2m个元素,每个元素可以被重复选择,
- 翻译一下:就是从把2*m个物品放入n个抽屉中,有的抽屉可以一个物品放,
- 我们在转化一下:我们先把每个抽屉中,先提前放入一个物品,这样问题就转化成了:我们把2*m+n个物品翻入n个抽屉中,且每个抽屉至少放入一个物品,
- 这样我们在利用插板法,把这个2m+n个分隔成n块,2m+n个物品有2m+n-1个空隙,我们从这个2m+n-1个空隙中选择n-1空隙插入板子,
- 此时答案为: ,最后注意 求解的时候,同时存在除法和取模 所以别忘了 乘以 1e9+7的逆元
代码(dp)
#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)
const int mxn = 3e5 + 10;
int dp[25][1005];
int main()
{
/* fre(); */
int n, m;
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++)
dp[1][i] = 1;
m *= 2;
for(int i = 2; i <= m; i ++)
for(int j = 1; j <= n; j ++)
dp[i][j] = (dp[i][j - 1] + dp[i - 1][j]) % mod;
int res = 0;
for(int i = 1; i <= n; i ++)
res = (res + dp[m][i]) % mod;
printf("%d", res);
return 0;
}
代码(组合数学)
#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)
const int mxn = 3e5 + 10;
ll q_pow(ll a, ll b)
{
ll res = 1;
while(b)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll cal(ll a, ll b)
{
ll f[mxn];
f[0] = 1;
for(int i = 1; i <= 1025; i ++)
f[i] = f[i - 1] * i % mod;
ll up = f[a];
ll down = f[b] * f[a - b] % mod;
return up * q_pow(down, mod - 2) % mod;
}
int main()
{
/* fre(); */
int n, m;
scanf("%d %d", &n, &m);
printf("%lld", cal(2 * m + n - 1, n - 1));
return 0;
}
D. Minimax Problem
分析(思维+二分+二进制枚举状态)
- 题意
- 给我们n(1<=n<=3e5)个数组
,这些数组的长度均为:m(
),现在让我们从中选择两个序列i、j出来,使得产生一个新的序列c(
),问我们通过合理的选择i、j来使c中的
最小的元素尽可能的大
,输入i,j (注意:i可以与j相同)
- 思路
-
这一题的思路不得不让人觉得奇妙!!!
-
看到:
最小的元素尽可能的大
,其实我们可以考虑一下二分,这题的如果用二分,我们肯定是在[1,1e9]的范围内枚举c中最小元素的值
设为md,能否通过在n中合理的选择两个序列来实现这个最小的枚举值md?我们肯定不可以用两层for循环暴力枚举合适的序列 ,之后在用一层for循环枚举判读每个 位置,这里有两个难点:1不能不能暴力枚举ai、aj序列、2不能对枚举出来的两个序列进行一位一位的枚举判读,这样两个难点是我们要优化的地方
-
于是我们就见到了神奇的
二进制枚举
, 那么怎么枚举呢?- 举个例子:
-
两个序列:a1 = { 1, 2, 3 ,4}、a2{2、1、3、2},我们假设m = 4,枚举最小值md=2,我们下面要这两个序列要做操作是如果 序列中的某个元素>=md,它所对应的二进制位1,否则0,,,那么转化完之后对应的为两个二进制数字:a1->b1=0111,a2->b2=1011,
通过这样的转化我们可以将n个a数组,转化位n个二进制数,而这样的二进制数最多有 t=1<<m个,t<=256个,这样我们就可以两次for循环暴力枚举这个不超过256个状态了,这个过程的复杂度最大位256*256,这就解决了第1个难题,
接下来我们看看第二个难题是怎么解决的:我们判断: 这个等式是否成立,如果成立就意味着: ,否则会出现例如 等等情况,都表明b1、b2的对应的二进制位上出现同时存在0的情况,那么翻译到 两个序列中的意思是,就是这两个序列中的某些个位置上的元素同时出现了<md的情况,(通过这个 | 操作元素符以O(1)的时间完成了对两个二进制数的判读,间接的完成了对序列a1、a2的元素的判断,这就解决了第2个难题了
)
-
通过这个例子我们大致明白了,二进制枚举的过程,其它的就是代码问题,剩下直接看代码
总结
1.对于我们时候二分法枚举答案的时候,对与 数据量大、所有状态难以一一暴力比较的时候,我可以考虑用 二进制枚举,来优化 暴力枚举时的状态过多的问题
代码
#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)
const int mxn = 3e5 + 10;
int ar[mxn][10];
int n, m;
int a1, a2;
void rd(int & x)
{
int ans = 0, f = 1; char ch = getchar();
while(! isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }
while( isdigit(ch)) { ans *= 10; ans += ch - '0'; ch = getchar(); }
x = ans;
}
bool judge(int md)
{
vector<int> bit(1 << m, -1); //n个数列最多产生,1 << m 种情况(每种情况 经过二进制转化之后 对应一个 bit容器下标),初始化为-1:表示 数对应的情况不存在
//遍历统计 哪些情况出现过
for(int i = 0; i < n; i ++)
{
int idx = 0;
for(int j = 0; j < m; j ++)
if(ar[i][j] >= md)
idx ^= (1 << j);
bit[idx] = i; //bit容器存储对应情况的 数组下标
}
//特殊情况(作为答案的两个数组 是同一个数组)
for(int i = 0; i < (1 << m); i ++)
if(bit[(1 << m) - 1] != -1)
{
a1 = a2 = bit[(1 << m) - 1];
return true;
}
//暴力枚举255 x 255 种情况
for(int i = 0; i < (1 << m); i ++)
for(int j = 0; j < (1 << m); j ++)
{
if(bit[i] != -1 && bit[j] != -1 && (i | j) == (1 << m) - 1)
{
a1 = bit[i], a2 = bit[j]; return true;
}
}
return false;
}
int main()
{
/* fre(); */
/* rd(n), rd(m); */
scanf("%d %d", &n, &m);
for(int i = 0; i < n; i ++)
for(int j = 0; j < m; j ++)
/* rd(ar[i][j]); */
scanf("%d", &ar[i][j]);
int l = 0, r = 1e9;
while(l <= r)
{
int md = l + r >> 1;
if(judge(md))
l = md + 1;
else
r = md - 1;
}
printf("%d %d", a1 + 1, a2 + 1);
return 0;
}
E. Messenger Simulator(树状数组/线段树)
分析
- 题意
- 给我们一个长度位n的序列a:1,2,3…n,又给我们m个在0~n范围的内的数的序列b,对与bi个数,我们将a中值位bi的元素挪到 第一个位置,在bi之前的元素均向后挪1位,当我们i从1到m进行m次这样的操作以后,问我们a中的每个元素,出先过的最大、最小位置分别是几?
- 思路
- 这一题中如果我们用暴力的模拟方法的话我们发现,占用复杂度较高的过程是:
把某个元素a中的某个元素bi移动到首位置之后,我们要把所以有在bi之前的元素都向后平移1位,由于a中的元素非常多,我们不可能一位一位的向后平移
, - 而在这一题中,我们通过巧妙的 在a序列的前面预留m个位置这样如果有元素bi被平移到a首位置的时候,我们直接放到提前放到 预留的m个空位中的一个,这样就避免了 『将bi之前所有的元素向后平移1位的过程了』,
- 其次用 我们用 树状数组/线段树 来快速的位置 每个元素的前面的 有多少个元素
举例
a数组:1 2 3 4
b数组:3 2
我们预留空位之后a数组变为:0 0 1 2 3 4,
在第一次操作之前:我们 用树状数组统计维护将要被操做元素3之前的元素个数ct,并且维护3的最大位置 r[3]=max(3, ct+1)
第一次操作:我们把3平移到首位:0 3 1 2 0 4,
在第二次操作之前:我们 用树状数组统计维护将要被操做元素2之前的元素个数ct,并且维护2的最大位置 r[2]=max(2, ct+1)
第二次操作:我们包2平移的首位:2 3 1 0 0 4,
最后我们 在对所有元素跑一遍 树状数组,统计每个元素的前面有几个元素,并且维护:对应每个元素 位置最大值
对于a中某个元素,位置最小值,只要这个元素在b中出现过,那么就是1,
代码
#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)
const int mxn = 6e5 + 10; //因为要预留空间,所以数组c要开2倍大
int c[mxn];
int l[mxn], r[mxn];
int pos[mxn];
int n, m;
int lowbit(int x) { return x & (-x); }
void add(int p, int t)
{
while(p < mxn) { c[p] += t; p += lowbit(p); }
}
int ask(int p)
{
int sum = 0;
while(p) { sum += c[p]; p -= lowbit(p); }
return sum;
}
int main()
{
/* fre(); */
int n, m;
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++)
{
l[i] = r[i] = i;
add(i + m, 1);
pos[i] = i + m;
}
int last = m;
while(m --)
{
int x;
scanf("%d", &x);
l[x] = 1;
int num = ask(pos[x]);
r[x] = max(r[x], num); //维护最大值
if(num == 1) continue; //如果时在第1位直接跳过
add(pos[x], -1); //移出
pos[x] = last; //添加到第1位,所在的位置
add(pos[x], 1); //添加到第1位
last --; //第一位 所在的位置往前挪1
}
//重新维护一下所有数的位置
for(int i = 1; i <= n; i ++)
r[i] = max(r[i], ask(pos[i]));
for(int i = 1; i <= n; i ++)
printf("%d %d\n", l[i], r[i]);
return 0;
}