分享一下赛中的想法和一部分的思路,没有验证对不对,有大佬看到错误滴滴我~~
国一不就有手就行
目录
试题 A: 2022(DP)
os:202210应该要跑很久吧,那可怎么办呀,不回来了呀。哦,好像可以DP~
思路
定义 dpi,x,y 表示第 i 个数等于 x 且前 i 个数和为 y 的方案数。
状态转移参看代码,时间复杂度 O( n4 ),得跑几秒,多等等~
记得开 ll
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[15][2500][2500];
int main(){
for(int i = 1; i <= 2022; i++) dp[1][i][i] = 1;
for(int i = 2; i <= 10; i++){
for(int j = 1; j<= 2022; j++){
for(int z = j; z <= 2022; z++){
for(int k = j+1; k+z <= 2022; k++){
dp[i][k][k+z] += dp[i-1][j][z];
}
}
}
}
ll sum = 0;
for(int i = 0; i <= 2022; i++) sum += dp[10][i][2022];
cout << sum << endl;
return 0;
}
// 379187662194355221
试题 B: 钟表(模拟)
os:00:00:00不就是答案么,垃圾题。
正在阅读理解的我,看到了公告:00:00:00除外
os:*****
思路
一个小时是 30°,一分钟是 6°,一秒是 6°
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
int res = 0;
for(int i = 0; i <= 6; i++){
for(int j = 0; j < 60; j++){
for(int z = 0; z < 60; z++){
double s = 30.0 * (i+(j*60.0+z)/3600.0);
double f = 6.0 * (j+z/60.0);
double m = z * 6.0;
double a = fabs(f - s); if(a > 180) a = 360 - a;
double b = fabs(f - m); if(b > 180) b = 360 - b;
if(a == 2 * b){
cout << i << " " << j << " " << z << " " << a << " " << b << endl;
res++;
}
}
}
}
cout << res << endl;
return 0;
}
// 4 48 0
试题 C: 卡牌(二分check)
os:嗯~,二分大水题
思路
二分能凑出的套牌数,二分check即可~~
时间复杂度 O( n*log(1e18) )
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5 + 5;
ll a[maxn], b[maxn];
ll n, m;
bool check(ll cnt){
ll sum = m;
for(int i = 1; i <= n; i++){
if(a[i] >= cnt) continue;
ll num = cnt - a[i];
if(num <= b[i] && num <= sum){
sum -= num;
continue;
}
return 0;
}
return 1;
}
int main(){
scanf("%lld %lld", &n, &m);
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for(int i = 1; i <= n; i++) scanf("%lld", &b[i]);
ll res = 0;
ll l = 0, r = 1e18;
while(l <= r){
ll mid = l + r >> 1;
if(check(mid)){
res = mid;
l = mid + 1;
}
else r = mid - 1;
}
cout << res << endl;
return 0;
}
/*
4 5
1 2 3 4
5 5 5 5
3
*/
试题 D: 最大数字(dfs)
os:能加一定要加,得判断能不能减。不会是数位DP吧,***,算了,暴搜吧
思路
由题得,优先给高位变成大于原来的数。最好变成 9,但可能存在加操作不够的情况,尽量大即可。显然,也可以通过减操作变成 9。
综上,对于每一个数位都有两种选择,是加到 9 还是减到 9。
直接 dfs 暴力枚举每一种情况,加一个记忆化标记一下不重复搜索。时间复杂度 O(2|n|),|n| 表示 n 的位数。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
bool v[100][105][105];
ll c[100];
ll res, p[100];
void dfs(ll now, int pos, ll a, ll b){ // a+ b-
if(v[pos][a][b]) return;
v[pos][a][b] = 1;
if(a == 0){
for(int i = pos; i >= 1; i--){
if(c[i]+1 <= b){
now = now + (9-c[i]) * p[i-1];
b -= c[i] + 1;
}
}
res = max(res, now);
return;
}
res = max(res, now);
if(pos == 0) return;
int tmp = min((9-c[pos]), a);
dfs(now + tmp * p[pos-1], pos-1, a-tmp, b);
if(c[pos]+1 <= b) dfs(now + (9-c[pos]) * p[pos-1], pos-1, a, b-c[pos]-1);
}
int main(){
ll n, a, b;
scanf("%lld %lld %lld", &n, &a, &b);
p[0] = 1;
for(int i = 1; i <= 20; i++) p[i] = p[i-1] * 10;
res = n;
int m = 0;
while(n){
c[++m] = n % 10;
n /= 10;
}
dfs(res, m, a, b);
cout << res << endl;
return 0;
}
/*
123 1 2
933
*/
试题 E: 出差(dij)
os:嗯~,最短路裸题,你就拿这个考验选手,那个选手经不起这样的考验
思路
把点权当边权跑 dij 即可,res = dis[n] - c[n]。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
vector<pair<int, ll> > ma[1005];
ll c[1005];
typedef struct Node{
int to;
ll w;
} node;
bool operator < (node A, node B){
return A.w > B.w;
}
priority_queue<node> qu;
ll dis[1005];
int main(){
int n, m;
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%lld", &c[i]);
for(int i = 1; i <= m; i++){
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
ma[u].push_back(make_pair(v, w));
ma[v].push_back(make_pair(u, w));
}
for(int i = 1; i <= n; i++) dis[i] = 2e18;
dis[1] = 0;
qu.push({1, 0});
while(!qu.empty()){
int now = qu.top().to;
qu.pop();
for(auto i : ma[now]){
int to = i.first;
ll nval = dis[now] + i.second + c[to];
if(nval < dis[to]){
dis[to] = nval;
qu.push({to, nval});
}
}
}
cout << dis[n]-c[n] << endl;
return 0;
}
/*
4 4
5 7 3 4
1 2 4
1 3 5
2 4 3
3 4 5
13
*/
试题 F: 费用报销(dp)
os:这搞毛呀,总不能枚举吧。先塞进去365张口头支票~
思路
观察数据,总金额不能超过 5000,一年也就 365 天。故而 365 * 5000 的 dp 妥妥可以过。
首先,这些支票的时间不是连续的,先塞进去 365+k 张支票(金额为零)使得时间连续。
定义:dpx,y 表示前 x 天是否可以得到金额为 y 的方案。
状态转移方程参看代码~
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef struct Node{
int id;
ll w;
} node;
node v[2005];
bool cmp(node A, node B){
if(A.id == B.id) return A.w < B.w;
return A.id < B.id;
}
int M[20] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int f(int a, int b){
int res = b;
for(int i = 1; i < a; i++) res += M[i];
return res;
}
ll dp[400][5005];
int main(){
int n, m, k;
cin >> n >> m >> k;
for(int i = 1; i <= n; i++){
int a, b, c;
cin >> a >> b >> c;
v[i].id = f(a, b);
v[i].w = c;
}
for(int i = 1; i <= 365+k; i++){
v[n+i].id = i;
v[n+i].w = 0;
}
n += 365+k;
v[0].id = 0;
sort(v+1, v+1+n, cmp);
for(int i = 1; i <= n; i++){
if(v[i].id != v[i-1].id && v[i].id-k > 0){ // 新的一天,把前k天的状态转移过来
for(int j = 1; j <= m; j++)
dp[v[i].id-k][j] |= dp[v[i].id-k-1][j];
}
if(v[i].w == 0) continue;
dp[v[i].id][v[i].w] |= 1;
if(v[i].id <= k) continue;
for(int j = 0; j <= m; j++){
if(dp[v[i].id-k][j] && j + v[i].w <= m){
dp[v[i].id][j + v[i].w] |= 1;
}
}
}
int res = 0;
for(int i = 0; i <= m; i++) if(dp[365][i]) res = i;
cout << res << endl;
return 0;
}
/*
4 16 3
1 1 1
1 3 2
1 4 4
1 6 8
10
*/
试题 G: 故障(概率题)
os:这啥呀,概率题我咋能会,都是数论队友的题,猜结论吧
思路
就是硬猜的结论,看代码吧,一看知道咋算了
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll p[100];
ll v[100][100];
ll a[100], c[100], d[100];
typedef struct Node{
int id;
double w;
} node;
node res[2005];
bool cmp(node A, node B){
if(fabs(A.w - B.w) < 1e-3) return A.id < B.id;
return A.w > B.w;
}
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> p[i];
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> v[i][j];
if(v[i][j] != 0) a[i] |= (1<<(j-1));
}
}
int k, flag = 0;
cin >> k;
for(int i = 1; i <= k; i++) cin >> c[i], d[c[i]] = 1, flag |= (1<<(c[i]-1));
double sum = 0;
for(int i = 1; i <= n; i++){
res[i].w = p[i];
res[i].id = i;
for(int j = 1; j <= m; j++){
if(d[j] == 1) res[i].w *= 1.0 * v[i][j];
else res[i].w *= 1.0 * (100-v[i][j]);
}
sum += res[i].w;
}
for(int i = 1; i <= n; i++) res[i].w = res[i].w / sum*100;
sort(res+1, res+1+n, cmp);
for(int i = 1; i <= n; i++) printf("%d %.2lf\n", res[i].id, res[i].w);
return 0;
}
/*
3 5
30 20 50
0 50 33 25 0
30 0 35 0 0
0 0 0 25 60
1
3
2 56.89
1 43.11
3 0.00
*/
试题 H: 机房(lca)
os:树剖?emmm,lca,你就拿这个考验选手,那个选手经不起这样的考验
思路
dfs处理一下各个点的祖先和从根节点到 u 结点的点权和。
u != v时:resu,v = sum[u] + sum[v] - sum[fa] * 2 + w[fa]
u == v时:resu,v = sum[u] - sum[fa]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5;
vector<int> ma[maxn];
int w[maxn];
int f[maxn][21];
int dep[maxn];
void dfs(int u, int fa){
w[u] = w[fa] + ma[u].size();
dep[u] = dep[fa] + 1;
f[u][0] = fa;
for(int i = 1; i < 21; i++) f[u][i] = f[f[u][i-1]][i-1];
for(auto v : ma[u]){
if(v == fa) continue;
dfs(v, u);
}
}
int lca(int x, int y){
if(dep[x] < dep[y]) swap(x, y);
if(x == y) return f[y][0];
for(int i = 20; i >= 0; i--){
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
}
for(int i = 20; i >= 0; i--){
if(f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[y][0];
}
int main(){
int n, m;
cin >> n >> m;
for(int i = 2; i <= n; i++){
int x, y;
cin >> x >> y;
ma[x].push_back(y);
ma[y].push_back(x);
}
dfs(1, 0);
for(int i = 1; i <= m; i++){
int x, y;
cin >> x >> y;
int fa = lca(x, y);
if(x != y) cout << w[x] + w[y] - 2 * w[fa] + ma[fa].size() << endl;
else cout << w[x] - w[fa] << endl;
}
return 0;
}
/*
4 3
1 2
1 3
2 4
2 3
3 4
3 3
5
6
1
*/
试题 I: 齿轮(数学题and调和级数)
os:擦,这不是高中的题,不记得了
思路
通过角速度和线速度的关系推到,可以发现,边上两个轮的半径比就是前后角速度的比。
多次查询,肯定要预处理。
如何能快速的处理出来任意两个轮子的半径比。
我当时用的是 n * n0.5 的写法。但是可以在调和级数(n * log)的时间复杂度内解决。
n * n0.5 的写法:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e6 + 5;
int r[maxn], v[maxn], res[maxn];
int main(){
int n, q;
scanf("%d %d", &n, &q);
for(int i = 1; i <= n; i++) scanf("%d", &r[i]), v[r[i]]++;
for(int i = 1; i <= n; i++){
for(int j = 2; j * j <= r[i]; j++){
if(r[i] % j == 0){
if(v[j]) res[r[i]/j] = 1;
if(v[r[i]/j]) res[j] = 1;
}
}
if(v[r[i]] > 1) res[1] = 1;
}
if(v[1]){
for(int i = 1; i <= n; i++) res[r[i]] = 1;
}
while(q--){
int x;
cin >> x;
if(x > 2e5 || res[x] == 0) cout << "NO" << endl;
else cout << "YES" << endl;
}
return 0;
}
/*
5 3
4 2 3 3 1
2
4
6
YES
YES
NO
*/
调和级数的写法
for(int i = 1; i <= n; i++){
if(v[i] == 0) continue;
for(int j = 2*i; j <= n; j += i){
if(v[j]){
res[j/i] = 1;
}
}
if(v[i] > 1) res[1] = 1;
}
试题 J: 搬砖(dp)
os:好难呀
思路
观察数据,会发现,这可能是一个大水题。
砖的重量不大,并且一个砖上放的砖头的重量和要小于这个砖。所以01背包即可。
ps:当时为了稳妥偏分,对于n比较小的情况作了二进制枚举,然后对于 n 比较大的情况写了dp。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef struct Node{
int v;
int w;
} node;
node v[2005];
bool cmp(node A, node B){
if(A.w == B.w) return A.v > B.v;
return A.w < B.w;
}
int dp[2005*20];
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i++) cin >> v[i].w >> v[i].v;
sort(v+1, v+1+n, cmp);
if(n <= 20){
int res = 0;
for(int k = 1; k <= (1<<n); k++){
int sum = 0, val = 0;
for(int i = 0; i < n; i++){
if((k>>i) & 1){
if(v[i].w >= sum){
sum += v[i].w;
val += v[i].v;
}
else break;
}
}
res = max(res, sum);
}
cout << res << endl;
}
else{
for(int i = 1; i <= n; i++){
for(int j = v[i].w; j >= 0; j--){
dp[j+v[i].w] = max(dp[j+v[i].w], dp[j] + v[i].v);
}
}
int res = 0;
for(int i = 1; i <= n*20; i++) res = max(res, dp[i]);
cout << res << endl;
}
return 0;
}
/*
4 4
1 1
5 2
5 5
4 3
10
*/