2021牛客寒假算法基础集训营2
https://ac.nowcoder.com/acm/contest/9982#question
D-牛牛与整除分块
大意:构造集合S={x: x =N/i, i=1,2,3...N},求n/x在S中第几大(S去重降序排序)
思路:先看n=10时S中有哪些数。
多列出几个数可以发现当
时,
,当
时,x是多少,那么n/x就第几大。
所以当
时,我们先求出n/x在右边集合中排第几,在加上左边的集合大小(左边集合大小为
)就是答案。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <list>
#define ull unsigned long long
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const int N = 500005;
const ll ds = 1e15+7;
const double p1 = 3.141592653589793238462643383;
using namespace std;
void solve()
{
int t,n,x,ans;
cin >> t;
while(t--){
ans = 0;
cin >> n >> x;
int m = sqrt(n);
if(x <= m){
ans = x;
}
else{
int k = n/x;
int h = n/(m+1);
ans = m+h-k+1;
}
cout << ans << endl;
}
}
int main()
{
solve();
return 0;
} E-牛牛与跷跷板:双指针+最短路(bfs/dfs/dijkstra)
思路:看题很容易联想的最短路,但关键是如何将这个图连起来。分成两种情况。
1)两块跷跷板在同一平面,那么比较相邻两块,左边的r等于右边的l就连起来。
2)两块不在同一平面,而是在上下相邻平面,利用双指针l,r, l指向下平面最左边那块,r指向下平面最右边那块,每一次让l往r移动,如果node[l].l >= node[i].r,那么l和l后面的跷跷板都不可能和i连接(可以画图看看),那么再来判断第i+1块,同时让j--,因为第i+1块和第i块是可能连接同一块的。如果node[l].r <= node[i].l,那l后面的跷跷板还是有可能和i连接的。
连接完图后,因为相邻跷跷板的路程是1,bfs,dfs,dij都可以解决最短路。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <list>
#define ull unsigned long long
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const int N = 500005;
const ll ds = 1e15+7;
const double p1 = 3.141592653589793238462643383;
using namespace std;
struct Node{
int y,l,r,num;
}node[N];
vector<int>a[N];
int n,vis[N],d[N];
bool cmp(Node a,Node b)
{
if(a.y == b.y) return a.l < b.l;
return a.y < b.y;
}
void bfs()
{
queue<int>q;
q.push(1);
vis[1] = 1;
d[1] = 0;
while(!q.empty()){
// cout << 1 <<endl;
int t = q.front();
q.pop();
if(t == n) break;
for(int i = 0; i < a[t].size(); i++){
if(vis[a[t][i]]) continue;
vis[a[t][i]] = 1;
q.push(a[t][i]);
d[a[t][i]] = d[t] + 1;
}
}
printf("%d",d[n]);
}
void solve()
{
scanf("%d",&n);
for(int i = 1; i <= n; i++){
int y,l,r;
scanf("%d%d%d",&y,&l,&r);
node[i].y = y;
node[i].l = l;
node[i].r = r;
node[i].num = i;
}
sort(node+1,node+n+1,cmp);
int l = 0,r = 0;
for(int i = 1; i <= n; i++){
if(node[i].r == node[i+1].l && node[i].y == node[i+1].y) {
a[node[i].num].push_back(node[i+1].num);
a[node[i+1].num].push_back(node[i].num);
}
while(l <= n && node[l].y == node[i].y) l++;
r = max(l,r);
while(r <= n && node[r].y == node[l].y) r++;
while(l < r){
//cout << l << endl;
if(node[l].l >= node[i].r){
//l++;
break;
}
if(node[l].r <= node[i].l){
l++;
continue;
}
a[node[l].num].push_back(node[i].num);
a[node[i].num].push_back(node[l].num);
l++;
}
if(l > 1) l--;
}
bfs();
}
int main()
{
solve();
return 0;
} F-牛牛与交换排序:构造数据结构
大意:牛牛有一个数组,里面的元素为1-n的一个排列,规定一个k为区间的大小,牛牛可以去翻转区间大小为k的子数组,但是每一次翻转的区间一定要在前一个区间的右边,问是否存在k,使得经过若干次翻转后,区间变得有序?
思路:因为每一次翻转的区间一定要在前一个区间的右边,并且数组里面的元素为1-n的一个排列,有序后数组满足a[i]=i,所以在a[i]不等于i的时候,一定要把等于i的数交换到位置i上去,否则下一次翻转的时候区间不可能包含位置i。在第一次进行判断的时候就能确定区间的大小k,然后依次判断翻转后a[i]是否等于i,不等于就再判断从a[i+k-]是否等于i(因为区间大小固定为k),不等于则不可能翻转成有序,相等则再继续往后判断。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <list>
#define ull unsigned long long
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const int N = 500005;
const ll ds = 1e15+7;
const double p1 = 3.141592653589793238462643383;
using namespace std;
int xb[1000005],a[1000005],pre[1000005];
void solve()
{
int n,x,k = 0;
scanf("%d",&n);
for(int i = 1; i <= n; i++){
scanf("%d",&x);
xb[x] = i;
a[i] = x;
if(x == i){
pre[i] = pre[i-1]+1;
}
else pre[i] = pre[i-1];
}
for(int i = 1; i <= n; i++){
if(a[i] != i){
k = xb[i]-i+1;//由第一个不相等的地方确定k的大小。
break;
}
}
int flag = 0;
for(int i = 1; i <= n; i++){
if(a[i] != i){
if(i+k-1 > n || a[i+k-1] != i){//不相等答案为no
flag = 1;
break;
}
int l = i,r = i+k-1;
while(l < r){
int temp1,temp2;
temp1 = a[l];
temp2 = xb[a[l]];
xb[a[l]] = xb[a[r]];
xb[a[r]] = temp2;
a[l] = a[r];
a[r] = temp1;
l++;
r--;
}
}
}
if(flag) printf("no\n");
else {
printf("yes\n");
if(k) printf("%d",k);
else printf("%d",k+1);
}
}
int main()
{
solve();
return 0;
} G-牛牛与比赛颁奖:差分+离散化
思路:看完题首先要想到差分,再看数据范围,1e9,不能开这么大的数组,所以考虑用离散化。将m个l,r离散化存入结构体当中。
最后巧妙的运用过题数和过题人数算排名。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <list>
#define ull unsigned long long
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const int N = 100005;
const ll ds = 1e15+7;
const double p1 = 3.141592653589793238462643383;
using namespace std;
struct Node{
int xb,num;
}node[N*2];
int cnt[N],pre[N];cnt[i]表示过了i题有多少人。
bool cmp(Node a,Node b)
{
if(a.xb == b.xb) return a.num < b.num;
return a.xb < b.xb;
}
void solve()
{
int n,m,l,r;
scanf("%d%d",&n,&m);
for(int i = 1; i <= m; i++){
scanf("%d%d",&l,&r);
node[i].xb = l;
node[i].num = 1;
node[i+m].xb = r+1;
node[i+m].num = -1;
}
sort(node+1,node+m+m+1,cmp);
int mmax = 0,sum = 0;
for(int i = 1; i <= m+m; i++){
if(node[i].xb - node[i-1].xb != 0){
cnt[sum] += node[i].xb - node[i-1].xb;
}
sum += node[i].num;
mmax = max(mmax,sum);
}
int j,y,t;
j = y = t = -1;
for(int i = mmax; i >= 0; i--){
pre[i] = pre[i+1] + cnt[i];
if(pre[i] >= (n+9)/10 && j == -1) j = i;
if(pre[i] >= (n+3)/4 && y == -1) y = i;
if(pre[i] >= (n+1)/2 && t == -1) t = i;
// cout << pre[i] << endl;
}
j = max(j,1);
y = max(y,1);
t = max(t,1);
// cout << j << " " << y << " " << t << " " << endl;
printf("%d %d %d",pre[y+1]-pre[mmax],pre[y]-pre[j],pre[t]-pre[y]);
}
int main()
{
solve();
return 0;
} I-牛牛与质因数:dfs/dp/输入筛
大意:定义函数F(x) ,它表示将x做质因数分解后得到的数字从小到大升序排列,然后将其“拼接”成一个大整数。求
思路:当n=10时,F(2) = 2,F(3) = 3,F(4) = 22,F(5) = 5,F(6) = 23,F(7) = 7,F(8) = 222,F(9) = 33,F(10) = 25
其实也就是2-10内的质因数的组合,组合的质因数相乘的结果要小于等于n ,现在考虑如何拼接,假设现在已经拼接成res,把质数x拼接上去,拼接完后res = res*10^cnt[x]+x,其中cnt[x]表示x的位数,而不是直接乘上十。2-n内质因数的组合可以用dfs来实现,剪枝是相乘结果大于n则回溯,即return。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <list>
#define ull unsigned long long
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const int N = 500005;
const ll ds = 1e15+7;
const double p1 = 3.141592653589793238462643383;
using namespace std;
int ans = 0,n,cnt;
ll sum = 0;
int v[10000005],prime[10000005],vis[10000005];
void init()
{
cnt = 0;
for(int i = 2; i <= n; i++){
if(!v[i]){
cnt++;
prime[cnt] = i;
v[i] = i;
}
for(int j = 1; j <= cnt; j++){
if(1ll*prime[j]*1ll*i > n) break;
v[i * prime[j]] = prime[j];
if(i % prime[j] == 0) break;
}
}
}
int ws(int x)
{
int t = 0;
while(x){
t++;
x /= 10;
}
return t;
}
int qpow(int a,int b)
{
int res = 1;
while(b){
if(b&1) res = 1ll*res*a%mod;
b >>= 1;
a = 1ll*a*a%mod;
}
return res;
}
void dfs(int cur,ll res,ll temp)
{
sum = (sum+res)%mod;
// cout << res << " " << temp << endl;
// cout << cur << " " << res << endl;
for(int i = cur; i <= cnt; i++){
if(temp*prime[i] <= n)
dfs(i,(res*qpow(10,ws(prime[i]))+prime[i])%mod,temp*prime[i]);
else break;
}
}
void solve()
{
scanf("%d",&n);
init();
for(int i = 1; i <= cnt; i++){
// cout << prime[i] << "\n";
dfs(i,1ll*prime[i],1ll*prime[i]);
}
printf("%lld\n",sum%mod);
}
int main()
{
solve();
return 0;
} 输入筛
思路:f[1500] = 223555,去最大的素因子,f[1500/5] = f[300] = 22355,所以f[1500] = f[300]*10+5,同理f[300] = f[60]*10+5.
这样在筛素数时,每次将素数i作为最大素因子,利用f[i] = f[j/i]*10^ws(i)+i求f[i]。倘若某个数的最大素因子不是i也没有关系,在后面比i大的素数去筛的时候会更新。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <list>
#define ull unsigned long long
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const int N = 5000005;
const ll ds = 1e15+7;
const double p1 = 3.141592653589793238462643383;
using namespace std;
bool vis[N];
int f[N];
ll ans = 0;
int ws(int x)
{
int t = 0;
while(x){
t++;
x /= 10;
}
return t;
}
int qpow(int a,int b)
{
int res = 1;
while(b){
if(b&1) res = 1ll*res*a%mod;
b >>= 1;
a = 1ll*a*a%mod;
}
return res;
}
void solve()
{
int n;
scanf("%d",&n);
for(int i = 2; i <= n; i++){
if(!vis[i]){
ans = (ans+i)%mod;
f[i] = i;
for(int j = i+i; j <= n; j+=i){
if(f[j/i] != 0) {
int t = qpow(10,ws(i))*f[j/i]+i;
ans = (ans+t)%mod;
f[j] = t;
}
vis[j] = true;
}
}
}
printf("%d\n",ans);
}
int main()
{
solve();
return 0;
}
J-牛牛相成为hacker:构造题
思路:根据题意很容易想到用斐波那契数列来构造,但是题目要求的数的大小不能超过1e9,而斐波那契数列在长度为40是就超过了1e9,所以当n<=39时,构造斐波那契数列就行,那n>=40怎么办呢,同样前面构造斐波那契数列,后面补1,但前面的两个1不要。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <list>
#define ull unsigned long long
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const int N = 500005;
const ll ds = 1e15+7;
const double p1 = 3.141592653589793238462643383;
using namespace std;
ll f[105];
void solve()
{
int n;
f[1] = 1,f[2] = 1;
for(int i = 3; i <= 39; i++){
f[i] = f[i-1]+f[i-2];
}
scanf("%d",&n);
if(n <= 37){
for(int i = 3; i <= n+2; i++){
printf("%lld ",f[i]);
}
}
else{
for(int i = 3; i <= 39; i++){
printf("%lld ",f[i]);
}
for(int i = 40; i <= n+2; i++){
printf("%d ",1);
}
}
}
int main()
{
solve();
return 0;
}

查看1道真题和解析
深信服公司福利 800人发布
