2021牛客多校三补题(待更
链接:2021牛客暑期多校训练营3_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com)
F题
题意:
给n个(1 <= n <= 4)个1到13的数组,可以添加、加减乘除以及括号,使其最后的值等于m。问n个数的所有解都出现了分数的解的这种组合有几种,并且输出。
题解:
模拟;
当n = 1, 2,一定不可能运算过程中有分数,因为最后的值m一定是整数;
当n=3时,只有可能 (整数/整数)*整数 = 整数 ,这种情况,可以调换位置,就可以有没有整数的解
eg. 8 / 3 * 9 = 24 ==> 9 / 3 * 8 = 24,所以不满足
所以n一定等于4(这算一个优化?
总体思路时枚举数字排列(非递减的排列)
再将四个数字+三个运算符 排列组合 成为后缀表达式 来运算
当这种排列只有出现分数的解,就算进答案
几个坑点
1.我推测出现分数解的情况只有可能是所有运算符号最多出现一次,就在dfs的时候加了这个条件
实际上可能一个没有出现分数的解是多次用到一个运算符的eg 1*(3 * 5 + 9) ==(5 / 3 + 1)* 9
2.后缀运算符用stack时间太久了
用数组模拟栈自测时间:
栈自测时间:
这也差太多了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<string>ans;
int pai[5];
ll tmp[8];
bool vis1[5];
bool vis2[5];
int fcnt, icnt;
int m;
const double eps = 1e-6;
void dfs(int deep, int cnt) {
if(icnt != 0) return;
if(deep == 7) {
stack<double>sta;
bool flag = 0;
for(int i = 0; i < 7; i++) {
if(tmp[i] < 14) {
sta.push(tmp[i] * 1.0);
}
else {
if(sta.size() < 2) return;
double b = sta.top(); sta.pop();
double a = sta.top(); sta.pop();
if(tmp[i] == 14) sta.push(a + b);
else if(tmp[i] == 15) sta.push(a - b);
else if(tmp[i] == 16) sta.push(a * b);
else if(tmp[i] == 17 && abs(a) < eps) return;
else if(tmp[i] == 17 && abs(round(b / a) - b / a) > eps) {
flag = 1;
sta.push(b / a);
}
else {
sta.push(b / a);
}
}
}
if(sta.size() != 1) return;
else {
if(abs(sta.top() - m * 1.0) < eps) {
if(flag) fcnt++;
else icnt++;
}
return;
}
}
if(cnt < 4) {
for(int i = 0; i < 4; i++) {
if(vis1[i] == 0) {
vis1[i] = 1;
tmp[deep] = pai[i];
dfs(deep + 1, cnt + 1);
vis1[i] = 0;
}
}
}
if(deep - cnt < 3) {
for(int i = 0; i < 4; i++) {
//一定不能用vis不然可能一组答案其实有没有分数解例如1*(3 * 5 + 9)
tmp[deep] = i + 14;
dfs(deep + 1, cnt);
}
}
}
int main() {
ios::sync_with_stdio(false);
int n;
cin >> n >> m;
if(n != 4) {
cout << 0 << endl;
return 0;
}
for(int a = 1; a <= 13; a++) {
for(int b = a; b <= 13; b++) {
for(int c = b; c <= 13; c++) {
for(int d = c; d <= 13; d++) {
//还有三个运算符没有加进去!
//排列--> dfs
pai[0] = a, pai[1] = b, pai[2] = c, pai[3] = d;
memset(vis1, 0, sizeof vis1);
fcnt = 0, icnt = 0;
dfs(0, 0);
if(fcnt && !icnt) {
string str = to_string(a) + " " + to_string(b) + " " + to_string(c) + " " + to_string(d);
ans.push_back(str);
}
}
}
}
}
cout << ans.size() << endl;
for(auto x : ans) {
cout << x << endl;
}
}
E题
题意:
问有多少对不同的(x,y)满足(x^2 + y^2) 整除 (xy + 1)
题解:
dmy的证明:
根据下面的网站找公式,注意一下判溢出
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAX = 1e6 + 10;
const ll INF = 1e18 + 100;
vector<ll> ans;
void init() {
ans.push_back(1);
for(ll k = 2; k < MAX; k++) {
ll tmp1 = k;
ll tmp2 = k * k * k;
while(tmp2 < INF) {
ans.push_back(tmp2);
ll tmp = tmp2;
if(INF / k / k < tmp2) break;//判溢出
tmp2 = k * k * tmp2 - tmp1;
tmp1 = tmp;
}
}
}
int main() {
init();
sort(ans.begin(), ans.end());
//ans.erase(unique(ans.begin(), ans.end()), ans.end());
int T;
scanf("%d", &T);
while(T--) {
ll x;
scanf("%lld", &x);
ll res = upper_bound(ans.begin(), ans.end(), x) - ans.begin();
printf("%lld\n", res);
}
}
// 1 8 27 30 64 112 125 216 240 343 418 512 729 1000 1020 1331 1560
// 1728 2133 2197 2744 3120 3375 4096 4913 5822 5832 6859 7770 8000 9261
B题
题意
n*m的矩阵,每个点上的值可以由给的公式算出
当一个矩阵的四个顶点有三个顶点时,第四个顶点可以被自动补全染色,
问把所有点都染色的最小权值和为多少
题解
经过一些操作转化为最小生成树问题
用并查集来做kruskal;
坑点
1.有n+m个点,数组和初始化的for循环要开两倍
2.sort会T,要用桶排序优化,因为sort的n是1e7
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 5e3 + 10;
const ll INF = 1e9 + 7;
const int maxmax = 1e5 + 10;
ll n, m, a, b, c, d, p;
int fa[MAX * 2];
int sizee[MAX * 2];
struct node {
int a, b, w;
} aa[MAX*MAX];
vector<pair<int, int> > ans[maxmax];
void init() {
ll tmp = a;
int cnt = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
tmp = ((tmp * tmp % p) * b % p + tmp * c % p + d) % p;
aa[cnt].a = i;
aa[cnt].b = j + n;
aa[cnt].w = tmp;
cnt++;
}
}
for (int i = 0; i < MAX * 2; i++) {
fa[i] = i;
sizee[i] = 1;
}
}
int find(int x) {
if (x != fa[x]) return fa[x] = find(fa[x]);
return fa[x];
}
void merge(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return;
if (sizee[a] > sizee[b]) swap(a, b);
sizee[a] += sizee[b];
fa[a] = b;
}
ll kruskal(int n, int m) {//n条边,m条点
ll cnt = 0, res = 0;
for (int i = 0; i < n && cnt != m - 1; i++) {
if (find(aa[i].a) != find(aa[i].b)) {
merge(aa[i].a, aa[i].b);
res += aa[i].w;
cnt++;
}
}
if (cnt < m - 1) {
//森林
}
return res;
}
int main() {
scanf("%lld %lld %lld %lld %lld %lld %lld", &n, &m, &a, &b, &c, &d, &p);
init();
int maxx = -1;
for(int i = 0; i < n * m; i++) {
ans[aa[i].w].push_back(make_pair(aa[i].a, aa[i].b));
maxx = max(maxx, aa[i].w);
}
int cnt = 0;
for(int i = 0; i < maxx; i++) {
for(auto x : ans[i]) {
aa[cnt].a = x.first;
aa[cnt].b = x.second;
aa[cnt++].w = i;
}
}
// sort(aa, aa + n * m, [](node a, node b) {
// if (a.w != b.w) return a.w < b.w;
// });
ll ans = kruskal(n * m, n + m);
printf("%lld", ans);
}
小结
补题未完成ing
在思维上确实有些题目没有想到怎么解,比如B题
然后数据给的也非常巧妙(就是会卡T
补题都卡了好久,赛场上就更加可能就做不出来了
但是并不是完全不可做的,心态要摆正,
F题我总觉得会有一些我不懂的性质,就不敢做,哎