Rank 663/3064. AC 7/12.
A. 阿宁的签到题
题意
根据分数输出等级。
思路
一门编程语言的基础之基础。
时间复杂度:
对应AC代码
import java.io.*;
import java.math.*;
import java.util.*;
import java.util.concurrent.atomic.*;
public class Main{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long x = scanner.nextLong();
if(1 <= x && x <= 7) System.out.println("very easy");
else if(x <= 233) System.out.println("easy");
else if(x <= 10032) System.out.println("medium");
else if(x <= 114514) System.out.println("hard");
else if(x <= 1919810) System.out.println("very hard");
else System.out.println("can not imagine");
}
}
送分还不写快点啊((
B. 阿宁的倍数
题意
给定一个长度为
操作分为两种:
- 修改操作:数组末尾增加一个数
。 - 询问操作:对于所有
,输出有多少 是 的倍数。
思路
我们考虑维护两个数组
其中,
那么,对于每次查询的
我们来考虑一下这两个数组如何构建:
我们可以从前往后遍历,枚举
在修改操作时,我们只需在加入新加的数
时间复杂度:
对应AC代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 400010;
int a[N], tot[N], pre[N];
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, q;
cin >> n >> q;
for(int i=1;i<=n;i++){
cin >> a[i];
for(int j=1;j<=a[i]/j;j++){
if(a[i] % j == 0){
tot[j] ++;
if(j != a[i] / j) tot[a[i] / j] ++;
}
}
pre[i] = tot[a[i]];
}
while(q --){
int op, x;
cin >> op >> x;
if(op == 1){
a[++ n] = x;
for(int j=1;j<=a[n]/j;j++){
if(a[n] % j == 0){
tot[j] ++;
if(j != a[n] / j) tot[a[n] / j] ++;
}
}
pre[n] = tot[a[n]];
}else cout << tot[a[x]] - pre[x] << '\n';
}
return 0;
}
比较暴力但又不太暴力的做法
C. 阿宁的大背包
题意
给定背包的数量
合并方式:
思路
我们直接来考虑
我们不妨留意一下从第二次合并后每项的系数,没错,就是杨辉三角。
于是,构建数组就非常明显了:我们只要按 中间向两侧递减 排列即可。
接着,考虑到数据范围并不大,于是下列两种方法均可行:
- 暴力合并;
- 计算出杨辉三角,作为系数和数组相乘。
考虑到暴力合并更不用脑子,这边采取方案
时间复杂度:
对应AC代码
import java.io.*;
import java.math.*;
import java.util.*;
import java.util.concurrent.atomic.*;
public class Main{
public static void main(String[] args) throws Exception{
Console console = new Console();
int n = console.nextInt();
long mod = 1000000007;
int[] a = new int[n];
for(int i=0;i<n/2;i++) a[i] = i * 2 + 1;
if(n % 2 == 1) a[n / 2] = n;
for(int i=0;i<n/2;i++) a[n - i - 1] = (i + 1) * 2;
List<Integer> ans = new ArrayList<>();
for(int i=0;i<n;i++) ans.add(a[i]);
for(int t=n;t>=2;t--){
List<Integer> now = new ArrayList<>();
for(int i=0;i<t-1;i++) now.add((int)(((long) ans.get(i) + ans.get(i + 1)) % mod));
ans = now;
}
console.println(ans.get(0));
for(int i=0;i<n;i++) console.print(a[i] + " ");
console.close();
}
//快读模板,此处略去
//public static class Console implements Closeable {}
}
优雅的暴力
D. 阿宁的毒瘤题
题意
给定一个字符串
思路
不是
首先,如果不删掉字符的话,做法就是很简单的
反而,这题是一道偏模拟的前缀和。
我们分别考虑删掉一个
- 对于一个
,它的价值为 $u{pre} \times u{suf}$; - 对于一个
,它的价值为 $ud{pre} + du{suf}$。
于是,我们可以考虑前缀和的方法,统计正方向第
注意,不止有
时间复杂度:
对应AC代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200010, inf = 0x3f3f3f3f;
int pre[N], ps[N], ss[N];
string s;
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> s;
int n = s.length();
int cu = 0;
for(int i=0;i<n;i++) if(s[i] == 'u') cu ++;
int maxx = 0, maxi = 0;
for(int i=0;i<n;i++){
if(i > 0) {
pre[i] = pre[i - 1];
ps[i] = ps[i - 1];
}
if(s[i] == 'u'){
pre[i] ++;
}else if(s[i] == 'd'){
if(maxx < pre[i] * (cu - pre[i])){
maxx = pre[i] * (cu - pre[i]);
maxi = i;
}
ps[i] += pre[i];
}
}
for(int i=n-1;i>=0;i--){
ss[i] = ss[i + 1];
if(s[i] == 'd') ss[i] += (cu - pre[i]);
else if(s[i] == 'u'){
if(maxx < ps[i] + ss[i]){
maxx = ps[i] + ss[i];
maxi = i;
}
}
}
for(int i=0;i<n;i++) cout << (maxi == i ? 'a' : s[i]);
}
做了一个小时dp,最后10分钟才大彻大悟,人快哭出来力
E. 阿宁的生成树
待补充
F. 阿宁的二进制
题意
给定一个长度为
对于独立的
每次询问输出答案后,数组
思路
我们不妨来考虑
对于最大值
继续操作,剩下最多
继续操作,剩下
也就是说,对于任意
换句话说,题给
那么,我们不妨先将所有询问都读入,然后桶排序一下,再枚举操作
时间复杂度:
对应AC代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200010;
pair<int, int> qs[N];
int ans[N];
int F(int x) {
int cnt = 0;
while (x != 0) {
if ((x & 1) == 1) cnt++;
x >>= 1;
}
return cnt;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, q;
cin >> n >> q;
priority_queue<int> pq;
for(int i=0;i<n;i++) {
int t;
cin >> t;
pq.emplace(t);
}
for(int i=0;i<q;i++){
int t;
cin >> t;
qs[i] = {t, i};
}
sort(qs, qs + q, [](pair<int, int> o1, pair<int, int> o2){return o1.first < o2.first;});
int i = 0, to = 0;
while(to < q){
int t = pq.top();
if(t == 1) break;
pq.pop();
i ++;
pq.push(F(t));
while(to < q && qs[to].first == i) ans[qs[to ++].second] = pq.top();
}
for(int t=0; t < q; t++) {
if(ans[t] == 0) cout << 1 << '\n';
else cout << ans[t] << '\n';
}
}
自从上次做过某题后,老想着会不会可以收敛((
G. 阿宁的整数配对
题意
给定一个长度为
思路
排一个序,从两端取即可。
时间复杂度:
对应AC代码
import java.io.*;
import java.math.*;
import java.util.*;
import java.util.concurrent.atomic.*;
public class Main{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(), k = scanner.nextInt();
long[] a = new long[n];
for(int i=0;i<n;i++) a[i] = scanner.nextInt();
Arrays.sort(a);
int l = 0, r = n - 1;
long ans = 0;
for(int i=0;i<k;i++){
if(a[l] * a[l + 1] > a[r] * a[r - 1]){
ans += a[l] * a[l + 1];
l += 2;
}else{
ans += a[r] * a[r - 1];
r -= 2;
}
}
System.out.println(ans);
}
}
打卡打卡~
H. 阿宁讨伐虚空
题意
给定
思路
如题,分类讨论算一下概率即可。
时间复杂度:
对应AC代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100010;
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int x, l, r;
cin >> x >> l >> r;
if(l > x - 1) cout << 0;
else if(r < x) cout << 1;
else cout << ((double) (x - l) / (double) (r - l + 1));
}
简简单单签到题
I. 阿宁前往沙城
题意
给定一个无向图,定义操作为选定两条边,将一条边删除,并将另一条边的长度改为
思路
很显然,从第二条路开始,我们直接把前面的路毁掉即可。
所以我们不妨直接把所有边改成
但得到的答案会出现一种特殊情况:最短路将所有边都覆盖了。
在该情况下,第一条只能用原来的长度替代了。
因此,直接套板子然后略微修改一下即可。
时间复杂度:
对应AC代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200010;
struct edge {
int v, w;
};
struct node {
int dis, u;
bool operator>(const node& a) const { return dis > a.dis; }
};
vector<edge> e[N];
int dis[N], vis[N], cnt[N];
priority_queue<node, vector<node>, greater<> > q;
void dijkstra(int s) {
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
q.push({0, s});
while (!q.empty()) {
int u = q.top().u;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1;
q.push({dis[v], v});
}
}
}
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
int minn = 0x3f3f3f3f;
for(int i=0;i<m;i++){
int u, v, w;
cin >> u >> v >> w;
if(u == 1 || v == 1) minn = min(minn, w);
e[u].push_back({v, 1});
e[v].push_back({u, 1});
}
dijkstra(1);
cout << (cnt[n] < m ? cnt[n] : dis[n] + minn - 1);
}
oi-wiki的板子真好用(划掉
J. 阿宁指指点点
待补充
K. 阿宁大战小红
待补充
L. 阿宁睡大觉
题意
给定一个
注意,
思路
这题有一个很明显的特点:障碍数远小于总格子数。
不考虑噩梦格子的话,总方案数很好求,即为
有没有一种算法,可以用类似于取补集的方法来大大降低时间复杂度呢?
也许我们可以枚举不能走的路径,但暴力枚举也是不行的。
这里需要用到 容斥 。
对于两个噩梦格子,它们之间的方案数可以用组合数来求:
每条路径的节点数量是一致的,为
于是,我们只需枚举所有选择即可,这里我们可以考虑用二进制进行状态压缩,直接用二进制位是否是
还有一个问题,若我们每次都算一遍两个节点的方案数,未免有些复杂,所以我们可以用
容斥
我们来考虑三个集合,它们两两相交,且有一部分三个相交在一起,如下图:
对,这是一个韦恩图,而且我们可以很容易的得到下面这个式子:
推广之后,对于
因而,对于所有噩梦格子的走法,利用容斥即可解决。
线性复杂度的组合数求法
显然,当数据量过大的时候,每次都用一遍
由于存在除法取模,我们需要用到乘法逆元,将除法取模转化为乘法取模。
逆元有一个很简单的求法,即费马小定理:对于
但每次用一遍快速幂也会提高时间复杂度,因而我们考虑线性求逆元,用到如下递推式:
对于阶乘的逆元,满足
满足上述条件后,
线性求逆元的证明
时间复杂度:
对应AC代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
const int N = 400010, mod = 1e9 + 7;
pii a[N], b[N];
int n, m, inv[N], fac[N], facInv[N], pow2[N];
//O1复杂度求组合数
int c(int n, int m){
return fac[n] * facInv[m] % mod * facInv[n - m] % mod;
}
signed main() {
ios::sync_with_stdio(0);
cin >> n >> m;
for(int i=0;i<m;i++) cin >> a[i].first >> a[i].second;
inv[1] = fac[0] = fac[1] = facInv[0] = facInv[1] = pow2[0] = 1;
for(int i=2;i<=n*2;i++){
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
fac[i] = fac[i - 1] * i % mod;
facInv[i] = facInv[i - 1] * inv[i] % mod;
}
for(int i=1;i<=n;i++) pow2[i] = pow2[i - 1] * 2 % mod;
int ans = pow2[n - 1];
for(int i=1;i<(1 << m);i++){
int t = 0;
b[t ++] = {1, 1};
int sign = 1;
for(int j=0;j<m;j++){
if((i >> j) & 1){
b[t ++] = a[j];
sign = -sign;
}
}
sort(b, b + t);
int now = pow2[n - 1 - (b[t - 1].first + b[t - 1].second - 2)];
for(int j=1; j < t; j++){
if(b[j - 1].second <= b[j].second){
int x = b[j].first - b[j - 1].first + 1, y = b[j].second - b[j - 1].second + 1;
now = now * c(x + y - 2, x - 1) % mod;
}else{
now = 0;
break;
}
}
ans += sign * now;
}
ans = (ans % mod + mod) % mod;
cout << ans << '\n';
return 0;
}
有点震撼的说…
- 本文链接 https://floating-ocean.github.io/blog_old/posts/3918832662/
- 版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!