VJ
题意:
酒馆有 n 个人,每个人都有一个固定的进入和离开的时间,然后你会去酒馆交朋友,只要遇到就会成为朋友,即使是在门口相遇,然后你最多只会待 m 个时间,求出最多能交多少朋友。
思路:
模拟,将每个人进入和离开的时间分开存储后进行排序,然后遍历模拟一遍,记录过程中的最大人数。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int cnt = 0;
struct node
{
int x, y;
}tr[N];
bool cmp(node aa, node bb)
{
return aa.x < bb.x;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 0; i < n; i ++)
{
int x, y;
cin >> x >> y;
tr[cnt ++] = {x, 1};
tr[cnt ++] = {y, -1};
}
sort(tr, tr + cnt, cmp);
int num = 0, ans = 0;
for(int i = 0, j = 0; i < cnt; i ++)
{
while(j < cnt && tr[j].x <= tr[i].x + m)
{
if(tr[j].y == 1) num ++;
j ++;
}
ans = max(ans, num);
if(tr[i].y == -1) num --;
}
cout << ans << '\n';
return 0;
}
对于一个排好序的字符串,经过多次交换两个字母,能排列成给定字符串。但是当只有 ai 大于 bi 时才允许交换,如果反向思维去想的话,我们可以把给定的字符串,通过多次交换两个字母的位置来变成排好序的字符串,再把交换的顺序逆向输出即可。时间复杂度允许 n^2,但是要注意的是,每次我们都要去交换最小的那个字母,才能保证反过来之后,它是 ai 大于 bi,满足题目要求。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
const int N = 2e5+5;
typedef pair<int, int>PII;
int T, n, m;
vector<PII>v;
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
string s;
cin >> s;
int t = s.size();
for(int i = 0; i < t; i ++)
{
char c = s[i];
int x;
for(int j = i + 1; j < t; j ++)
{
if(s[j] < c)
{
x = j;
c = s[j];
}
}
if(c == s[i]) continue;
swap(s[i], s[x]);
v.push_back({i, x});
}
for(int i = v.size() - 1; i >= 0; i --)
{
cout << v[i].second+1 << " " << v[i].first+1 << '\n';
}
}
这个题,打训练赛的时候没读懂题意。它是要保证翻转 K 次的,即使已经全部都是正面了,也要继续翻转直到 K 次。
可以考虑用 dp 来进行状态转移,最后加和。
f[ i ][ j ] 用来表示 i 次翻转后,有 j 个硬币朝上,那么 f[ i ][ j ] += f[ i - 1 ][ j ] * 0.5 并且 f[ i ][ j ] += f[ i - 1 ][ j - 1 ] * 0.5。但是对于 j == n 时,由于不可能有 n + 1 个硬币朝上,所以转移时 f[ i ][ n - 1 ] += f[ i - 1][ n ] * 0.5。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
const int N = 2e3+5;
typedef pair<int, int>PII;
int T, n, m;
double f[N][N];
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
f[0][0] = 1;
for(int i = 1; i <= m; i ++)
{
for(int j = 0; j <= n; j ++)
{
// 概率是累加的而不是由上一级直接赋值,是因为由多个状态可以走到当前状态
// 比如f[3][2]可以由f[2][2]和f[2][1]两种状态转移而来
f[i][j] += f[i-1][j] * 0.5;
if(j > 0) f[i][j] += f[i-1][j - 1] * 0.5;
if(j == n) f[i][n - 1] += f[i - 1][n] * 0.5;
}
}
double ans = 0;
for(int i = 1; i <= n; i ++) ans += f[m][i] * i;
printf("%.10lf\n", ans);
return 0;
}
牛客
牛客练习赛121
给定两个数,然后用两种方法构建出这两个数的最小共倍数。当时确实没想出来,点一下就明白了,lcm 一定是 gcd 的 n 倍。所以可以先添加 gcd,通过 gcd 构建 lcm。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int a[N];
PII b[N];
vector<int>v;
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
v.clear();
int cnt = 0;
int x, y;
cin >> x >> y;
int lc = lcm(x, y), gc = gcd(x, y);
int z = gc;
a[++ cnt] = 1;
b[cnt] = {x, y};
a[++ cnt] = 1;
b[cnt] = {x, y};
v.push_back(gc);
while(1)
{
if(gc * 2 > lc) break;
a[++cnt] = 2;
b[cnt] = {gc, gc};
a[++cnt] = 2;
b[cnt] = {gc, gc};
gc *= 2;
v.push_back(gc);
}
v.push_back(Max);
while(gc < lc)
{
int c = lc - gc;
int tt = upper_bound(v.begin(), v.end(), c) - v.begin();
tt --;
a[++cnt] = 2;
b[cnt] = {gc, v[tt]};
gc += v[tt];
}
cout << cnt << '\n';
for(int i = 1; i <= cnt; i ++)
{
cout << a[i] << " " << b[i].first << " " << b[i].second << '\n';
}
}
return 0;
}
牛客周赛 Round 31
这个题比较难想,其实这个题可以用背包轻松写出来,先得出整个数组的和 sum,然后判断 abs(sum)是不是奇数,如果是奇数一定 -1,否则,去做容量为 sum/2 的 01 背包,判断是否有值即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int a[N], b[N], f[N];
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
int sum = 0;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
sum += a[i];
}
int t = abs(sum);
if(t % 2 == 1)
{
cout << "-1\n";
return 0;
}
int cnt = 0;
if(sum < 0)
{
for(int i = 1; i <= n; i ++)
{
if(a[i] < 0) b[++cnt] = - a[i];
}
}
else
{
for(int i = 1; i <= n; i ++)
{
if(a[i] > 0) b[++cnt] = a[i];
}
}
memset(f, 0x3f, sizeof f);
f[0] = 0;
t /= 2;
for(int i = 1; i <= cnt; i ++)
{
for(int j = t; j >= b[i]; j --)
{
f[j] = min(f[j], f[j-b[i]] + 1);
}
}
if(f[t] == Max) cout << "-1\n";
else cout << f[t] << '\n';
}
将 n 个相同的物品,分成 m 份,{1,2} 和 {2,1}是两种分法,{1,1} 和 {1,1}是一种分法,那么将有 C(n - 1,m - 1)种分法。
思路来源:牛客周赛 Round 31 解题报告 | 珂学家 | 设计 + 组合_牛客博客 (nowcoder.net)
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
const int N = 2e5+5;
typedef pair<int, int>PII;
int T, n, m;
const int mod = 1e9 + 7;
int fact[N], infact[N];
int qpow(int a, int b, int mod)
{
int res = 1;
while (b)
{
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void init()
{
fact[0] = infact[0] = 1;
for (int i = 1; i < N; ++ i)
{
fact[i] = fact[i - 1] * i % mod;
infact[i] = qpow(fact[i], mod - 2, mod);
}
}
int ask(int a, int b)
{
if(a < b) return 0;
return fact[a] * infact[b] % mod * infact[a - b] % mod;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
init();
int x, y;
cin >> x >> y;
for(int i = 1; i <= (x + y); i ++)
{
int t1 = i / 2, t2 = i + 1 >> 1;
int tt = ask(x-1, t1-1) * ask(y-1, t2-1) % mod;
int x1 = i + 1 >> 1, x2 = i / 2;
int tz = ask(x-1, x1-1) * ask(y-1, x2-1) % mod;
cout << (tt+tz)%mod << '\n';
}
}
2024牛客寒假算法基础集训营1
题意挺简单,第一眼看过去就是暴搜,但是一直都不太会写递归,dfs,暴搜这种,所以写的很烂,然后开了很多没用的变量和 bool 数组并且跑了很多无用的循环,导致 TLE 了。一直卡在这个题也没有去做其他题,导致整场除了这个题之外就wa了一发依然罚时很高。后来做崩了,去做其他几个签到题却很快就做出来了,以后不能死钻牛角尖才是。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int a[N];
struct node
{
int x, y;
}tr[N];
int ans;
void dfs(int u)
{
if(u >= m)
{
int res = 1;
for(int i = 2; i <= n; i ++)
{
if(a[i] > a[1]) res ++;
}
ans = min(ans, res);
return;
}
a[tr[u].x] += 3;
dfs(u + 1);
a[tr[u].x] -= 3;
a[tr[u].x] += 1;
a[tr[u].y] += 1;
dfs(u + 1);
a[tr[u].x] -= 1;
a[tr[u].y] -= 1;
a[tr[u].y] += 3;
dfs(u + 1);
a[tr[u].y] -= 3;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
ans = Max;
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 0; i < m; i ++)
{
cin >> tr[i].x >> tr[i].y;
}
dfs(0);
cout << ans << '\n';
}
}
一个关于概率的题,题目中有两种生成圆的方法,抓住这两种方法的区别后判断一些有区分度的生成数量即可。
例如:第一种生成圆的方法生成的坐标是均匀分布的,但是第二种方法就不均匀分布。所以可以统计 x 和 y 坐标绝对值都在 70 以内的数量,然后与 141 * 141 / (199 * 199) * n 进行比较即可(141 是 [-70, 70] 有 141 个点,199 是 [-99, 99] 有199个点),在误差范围内就是第一种方法生成的圆,否则就是第二种方法生成的圆。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
int a1 = 0, b1 = 0;
for(int i = 0; i < n; i ++)
{
int x, y, r;
cin >> x >> y >> r;
if(abs(x) <= 70 && abs(y) <= 70) a1 ++;
}
double t1 = 141 * 141.0 / 199 / 199 * n;
if(abs(t1 - a1) <= 100) cout << "bit-noob\n";
else cout << "buaa-noob\n";
return 0;
}
2024牛客寒假算法基础集训营2
打比赛的感觉一眼完全背包,但是容量大,复杂度太高,一直在想怎么去优化。后来想到了取余操作,不断的分析和取余过程中,完全背包被我写成了 01 背包,后来又通过卡数据,给过掉了。这个取余操作,是对 n 取余,因为移动 n 个位置等于没有移动。
下面是出题人写的 dp 思路, 我写的代码限制了一个比较大的操作次数来卡数据,看样子并不是数据水过了,而是我代码跑的次数满足了最大的操作次数。
先贴一个自己的代码,正解思路以后再看吧:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e7+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m, k;
int v[N], w[N], id[N], f[N];
bool st[N];
int cnt;
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
cin >> n >> m >> k;
for(int i = 0; i < n; i ++) st[i] = 0;
int t = n - k;
int cnt = 0;
for(int i = 1; i <= m; i ++)
{
int x, y;
cin >> x >> y;
for(int j = 1; j <= n; j ++)
{
int tt = x*j%n;
if(!st[tt])
{
st[tt] = true;
v[++cnt] = tt;
w[cnt] = y*j;
id[tt] = cnt;
}
else w[id[tt]] = min(w[id[tt]], y*j);
}
}
if(k == n)
{
cout << 0 << '\n';
continue;
}
int mx = min(100000000/cnt, n*(n+1)/2);
for(int i = 0; i <= mx; i ++) f[i] = Max;
f[0] = 0;
for(int i = 1; i <= cnt; i ++)
{
for(int j = mx; j >= v[i]; j --)
{
f[j] = min(f[j], f[j-v[i]] + w[i]);
}
}
int ans = Max;
for(int i = t; i <= mx; i += n)
{
ans = min(ans, f[i]);
}
if(ans == Max) cout << "-1\n";
else cout << ans << '\n';
}
return 0;
}
做法二:同余最短路(恍然大悟)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e3+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m, k;
int dis[N], d[N], w[N];
bool st[N];
priority_queue<PII, vector<PII>, greater<PII>>q;
void dijkstra()
{
memset(dis, 0x3f, sizeof dis);
dis[0] = 0;
memset(st, 0, sizeof st);
q.push({0, 0});
while(q.size())
{
int t = q.top().second;
q.pop();
if(st[t]) continue;
st[t] = true;
for(int i = 0; i < m; i ++)
{
int z = (d[i] + t) % n;
if(dis[z] > dis[t] + w[i])
{
dis[z] = dis[t] + w[i];
q.push({dis[z], z});
}
}
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
cin >> n >> m >> k;
for(int i = 0; i < m; i ++)
{
int x, y;
cin >> d[i] >> w[i];
}
dijkstra();
if(dis[n-k] == Max) cout << "-1\n";
else cout << dis[n-k] << '\n';
}
return 0;
}
Tokitsukaze and Password (easy)
打比赛的时候,看到是输出对 1e9+7 取模后的结果。感觉是 dfs 剪枝优化,而且会推出来什么式子之类的来减少算法复杂度,一直想不到怎么写。结果是为了让 easy 和 hard 题目尽量一致就没有删除这句话。
这个题,暴力枚举即可,dfs 的做法以后再补。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m, y;
string s;
int check(int x)
{
if(x == 0) return 1;
else
{
int cnt = 0;
while(x)
{
cnt ++;
x /= 10;
}
return cnt;
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
set<int>ans;
cin >> n >> s >> y;
for(int a = 0; a < 10; a ++)
for(int b = 0; b < 10; b ++)
for(int c = 0; c < 10; c ++)
for(int d = 0; d < 10; d ++)
{
set<int>v = {a, b, c, d};
if(v.size() < 4) continue;
for(int _ = 0; _ < 10; _ ++)
{
int res = 0;
for(auto x : s)
{
if(x == 'a') res = res * 10 + a;
else if(x == 'b') res = res * 10 + b;
else if(x == 'c') res = res * 10 + c;
else if(x == 'd') res = res * 10 + d;
else if(x == '_') res = res * 10 + _;
else res = res * 10 + (x ^ 48);
}
if(check(res) == n && res % 8 == 0 && res <= y) ans.insert(res);
}
}
cout << ans.size() << '\n';
}
return 0;
}
2024牛客寒假算法基础集训营5
打比赛的时候没有想明白从后向前遍历的思路,一直在考虑从前向后遍历。但是一直 wa,直到最后也没想到哪里错了,现在来看,突然就反应过来了。我在从前向后一点点遍历的时候,对于每个 i 都分奇偶判断,并且记录了一个能加的最大次数 cnt,a[i] + cnt * i <= a[i + 1] 是一种情况,会让 a[i] = a[i] + { ( a[i - 1] - a[i] ) / i + ( ( a[i - 1] - a[i] ) % i != 0 ) } * i;反之的另外一种情况则是 cnt 小,增大cnt,然后再同上一种情况加上,每次都会比较。其实,最大的问题在于,假设我在 i = 2 时增加了2 * i,又在 i = 4 时增加了 3 * i,那么在 i = 2 时就不可能只增加了 2 * i,至少也是 3 * i,也就是说后面的增加次数一定小于等于前面位置的增加次数,这里欠考虑了。
从后倒着遍历会简单很多,首先我们已经把 n 是偶数的情况都判掉了,都是 YES。所以从倒数第二个数开始,把数加满,令 a[i] 变成不大于 a[i+1] 的最大数,然后是倒数第四个数...... 每次判一判就可以了。
赛时错误代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5;
typedef pair<int, int> PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int a[N];
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while (T--)
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
if (n % 2 == 0)
{
cout << "YES\n";
continue;
}
int mx = a[n], f = 0, cnt = 0;
for (int i = 1; i < n; i++)
{
if (a[i] > a[i + 1])
{
if (i == n - 1)
{
// cout << 1;
f = 1;
break;
}
if (a[i + 1] + cnt * (i + 1) < a[i])
{
if ((i + 1) % 2 == 0)
{
a[i + 1] = a[i + 1] + cnt * (i + 1);
int tt = a[i] - a[i + 1];
cnt += tt;
if (mx - tt * (i + 1) < a[i + 1])
{
// cout << 2;
f = 1;
break;
}
a[i + 1] = a[i + 1] + tt * (i + 1);
}
else
{
a[i + 1] = a[i + 1] + cnt * (i + 1);
if (mx - cnt * (i + 2) < a[i + 2])
{
// cout << 3;
f = 1;
break;
}
a[i + 2] = a[i + 2] + cnt * (i + 2);
int tt = a[i] - a[i + 1];
cnt += tt;
if (mx - tt * (i + 1) < a[i + 1] || mx - tt * (i + 2) < a[i + 2])
{
// cout << 4;
f = 1;
break;
}
a[i + 1] = a[i + 1] + tt * (i + 1);
a[i + 2] = a[i + 2] + tt * (i + 2);
}
}
else
{
int tt = a[i] - a[i + 1];
int t1 = tt / (i + 1), t2 = tt % (i + 1);
if (t2 != 0)
t1++;
if ((i + 1) % 2 == 0)
{
if (mx - t1 * (i + 1) < a[i + 1])
{
// cout << 5;
f = 1;
break;
}
a[i + 1] = a[i + 1] + t1 * (i + 1);
}
else
{
if (mx - t1 * (i + 1) < a[i + 1] || mx - t1 * (i + 2) < a[i + 2])
{
// cout << 6;
f = 1;
break;
}
a[i + 1] = a[i + 1] + t1 * (i + 1);
a[i + 2] = a[i + 2] + t1 * (i + 2);
}
}
}
}
// for(int i = 1; i <= n; i ++) cout << a[i] << " ";
if (f == 1)
cout << "NO\n";
else
cout << "YES\n";
}
return 0;
}
ac代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int a[N];
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
cin >> n;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
}
if(n % 2 == 0)
{
cout << "YES\n";
continue;
}
int f = 0, cnt = 0;
for(int i = n - 1; i >= 1; i -= 2)
{
a[i] = a[i] + cnt * i;
if(a[i] <= a[i+1])
{
int t = (a[i+1] - a[i]) / i;
cnt += t;
a[i-1] = a[i-1] + cnt * (i-1);
a[i] = a[i] + t * i;
}
else
{
f = 1;
break;
}
}
for(int i = 1; i < n; i ++)
{
if(a[i] > a[i + 1])
{
f = 1;
break;
}
}
if(f == 1) cout << "NO\n";
else cout << "YES\n";
}
return 0;
}
判断可不可行,然后可行的话取 max(a[i] - a[i-1])即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int a[N];
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
cin >> n;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
}
int mx = 0;
for(int i = 1; i < n; i ++)
{
mx = max(mx, a[i] - a[i+1]);
}
if(n % 2 == 0)
{
cout << mx << "\n";
continue;
}
int f = 0, cnt = 0;
for(int i = n - 1; i >= 1; i -= 2)
{
a[i] = a[i] + cnt * i;
if(a[i] <= a[i+1])
{
int t = (a[i+1] - a[i]) / i;
cnt += t;
a[i-1] = a[i-1] + cnt * (i-1);
a[i] = a[i] + t * i;
}
else
{
f = 1;
break;
}
}
for(int i = 1; i < n; i ++)
{
if(a[i] > a[i + 1])
{
f = 1;
break;
}
}
if(f == 1) cout << "-1\n";
else cout << mx << "\n";
}
return 0;
}
CF
Codeforces Round 932 (Div. 2)
对于一组 pi 的 bi 值,让其绝对值之和最小,很容易就能想到排序,但是如果仔细看的话就会发现,它们的绝对值之和其实就是 bmax - bmin。而且顺序并不会影响 ai 的和大小。在按照 bi 的值 sort 之后,对于每个 i 开始向后遍历元素,计算总和与 L 比较,如果大于 L,把 ai 最大的元素删除。用 multiset 来维护这个序列。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
typedef pair<int, int>PII;
int T, n, m;
PII p[N];
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
cin >> n >> m;
for(int i = 0; i < n; i ++)
{
cin >> p[i].second >> p[i].first;
}
sort(p, p + n);
int ans = 0;
for(int i = 0; i < n; i ++)
{
multiset<int>s;
int res = 0;
for(int j = i; j < n; j ++)
{
s.insert(p[j].second);
res += p[j].second;
while(s.size() && p[j].first - p[i].first + res > m)
{
int it = *s.rbegin();
res -= it;
s.extract(it);
}
ans = max(ans, (int) s.size());
}
}
cout << ans << '\n';
}
}
还有一种dp的做法,记 f[ i ][ j ] 是前 i 个物品取 j 个的最小价值。那么 f[ i ][ j ] = min(f[ k ][j-1] + a[ i ] + b[ i ] - b[ k ]),由于 a[ i ] 和 b[ i ] 是确定的,所以 f[ i ][ j ] = min(f[ k ][ j - 1] - b[ k ]) + a[ i ] + b[ i ]。
由于 k 值不确定,所以复杂度是 n^3 的,但是在更新时,令 g[ j ] = min(f[ k ][ j ] - b[ k ]) 就可以优化成 n^2 的复杂度。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
const int N = 2e3+5;
typedef pair<int, int>PII;
int T, n, m;
PII p[N];
int f[N][N], g[N];
bool cmp(PII aa, PII bb)
{
return aa.second < bb.second;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> p[i].first >> p[i].second;
}
sort(p+1, p+n+1, cmp);
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
{
f[i][j] = Max;
g[j] = Max;
}
}
f[0][0] = 0, g[0] = 0;
for(int i = 1; i <= n; i ++)
{
for(int j = i; j >= 2; j --)
{
f[i][j] = g[j-1] + p[i].first + p[i].second;
g[j] = min(g[j], f[i][j] - p[i].second);
}
f[i][1] = p[i].first; // 当只有一个值时,它的价值就是ai
g[1] = min(g[1], f[i][1] - p[i].second);
}
for(int i = 1; i <= n; i ++) g[i] = Max;
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= i; j ++)
{
g[j] = min(g[j], f[i][j]);
}
}
int ans = n;
for(int i = 0; i <= n; i ++)
{
if(g[i] > m)
{
ans = i - 1;
break;
}
}
cout << ans << '\n';
}
return 0;
}
容斥,首先求出总的,然后减去符合 x + y 的,减去符合 y - x 的,最后再加上两者都符合的即可。
对于两者都符合的情况:x + y = ai 且 y - x = aj,那么两式相加得到 2y = ai + aj,当 ai + aj 为偶数时可以得到唯一解,所以都符合的数量就是 ai 与 aj 同余的全部组合。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
const int N = 2e5+5;
typedef pair<int, int>PII;
int T, n, m;
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while(T --)
{
cin >> n >> m;
int s = (m + 1) * (m + 2) / 2;
int cnt[2] = {0};
for(int i = 1; i <= n; i ++)
{
int x;
cin >> x;
s -= x / 2 + 1;
s -= m - x + 1;
cnt[x&1] ++;
s += cnt[x&1];
}
cout << s << '\n';
}
return 0;
}
OJ
409组队赛1
题意很清楚,就是保证最短路径的情况下,使价值差最大。比赛的时候考虑到了三种情况
在这种情况下舍弃哪一种情况都不可以,如果舍弃(7,10)那么后面如果有个价值 1,就会出错;如果舍弃(4,9)但是图就只是这样,也会出错;如果舍弃(3, 7),但是后面有个价值10,一样出错。所以在比赛的时候,我们存下了这三种情况,从头递推到尾。但是没想到真能 ac 了。
代码写的非常复杂,由于边权是 1,一开始 bfs 找到从 1 到 n 的所有的最短路径并且标记,构建出一个新的图,由于最短路的性质,可以看作从 1 到 n 是一个有向图,然后利用拓扑排序递推维护这三种情况。
代码如下:
#include <bits/stdc++.h>
#define huoguo_fy ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
using namespace std;
#define endl '\n'
#define xx first
#define yy second
typedef long long LL;
typedef pair<int, int> PII;
const int inf = 0x3f3f3f3f;
const int N = 1e6 + 10;
int dis[N];
int n, m;
struct node
{
int t1, z1, t2, z2, t3, z3;
} tr[N];
vector<PII> v[N], ve[N];
bool st[N], st1[N];
int ru[N];
queue<int> q;
void bfs(int u)
{
q.push(u);
st1[u] = true;
while (q.size())
{
int tt = q.front();
q.pop();
for (int i = 0; i < v[tt].size(); i++)
{
int tv = v[tt][i].first, tv2 = v[tt][i].second;
if (st1[tv])
{
if (dis[tv] == dis[tt] + 1)
{
ve[tt].push_back({tv, tv2});
}
continue;
}
dis[tv] = dis[tt] + 1;
// cout << tt << " " << tv << '\n';
ru[tv]++;
q.push(tv);
st1[tv] = true;
ve[tt].push_back({tv, tv2});
}
}
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
tr[i] = {inf, -1, inf, -1, inf, -1};
}
for (int i = 1; i <= m; i++)
{
int x, y, z;
cin >> x >> y >> z;
v[x].push_back({y, z});
v[y].push_back({x, z});
}
bfs(1);
for (int i = 1; i <= n; i++)
{
if (ru[i] == 0)
q.push(i);
}
while (q.size())
{
int t = q.front();
q.pop();
for (int i = 0; i < ve[t].size(); i++)
{
int tt = ve[t][i].first, tt1 = ve[t][i].second;
if (tr[tt].t1 == -1 || tr[tt].z1 == -1)
{
tr[tt].t1 = min(tr[tt].t1, tt1);
tr[tt].z1 = max(tr[tt].z1, tt1);
}
if (tr[tt].t2 == -1 || tr[tt].z2 == -1)
{
tr[tt].t2 = min(tr[tt].t2, tt1);
tr[tt].z2 = max(tr[tt].z2, tt1);
}
if (tr[tt].t3 == -1 || tr[tt].z3 == -1)
{
tr[tt].t3 = min(tr[tt].t3, tt1);
tr[tt].z3 = max(tr[tt].z3, tt1);
}
// if (t == 3 && tt == 4)
// cout << min(tr[t].t1, tt1) << max(tr[t].z1, tt1);
if (min(tt1, tr[t].t1) < tr[tt].t1 || (min(tt1, tr[t].t1) == tr[tt].t1 && max(tt1, tr[t].z1) > tr[tt].z1))
{
tr[tt].z1 = max(tr[t].z1, tt1);
tr[tt].t1 = min(tr[t].t1, tt1);
}
if (max(tt1, tr[t].z2) > tr[tt].z2 || (max(tt1, tr[t].z2) == tr[tt].z2 && min(tt1, tr[t].t2) < tr[tt].t2))
{
tr[tt].z2 = max(tr[t].z2, tt1);
tr[tt].t2 = min(tr[t].t2, tt1);
}
if (max(tt1, tr[t].z3) - min(tt1, tr[t].t3) > tr[tt].z3 - tr[tt].t3)
{
tr[tt].z3 = max(tr[t].z3, tt1);
tr[tt].t3 = min(tr[t].t3, tt1);
}
ru[tt]--;
if (ru[tt] == 0)
q.push(tt);
}
}
// for (int i = 1; i <= n; i ++)
// {
// cout << tr[i].t1 << " " << tr[i].z1 << " " << tr[i].t2 << " " << tr[i].z2 << " " << tr[i].t3 << " " << tr[i].z3 << '\n';
// }
int ans = max(tr[n].z1 - tr[n].t1, max(tr[n].z2 - tr[n].t2, tr[n].z3 - tr[n].t3));
cout << ans << '\n';
// for (int i = 1; i <= n; i ++)
// {
// for (int j = 0; j < ve[i].size(); j ++)
// {
// cout << ve[i][j].first << " ";
// }
// cout << '\n';
// }
}
signed main()
{
huoguo_fy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
题解思路倒是明白了,代码看的不是特别明白,所以自己思考了很久。思路就是正反两遍 dijkstra,维护出走到每个位置时的最短路径。然后遍历最短路径的两个直接相连的端点,得出答案。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
typedef pair<int, int> PII;
int T, n, m;
vector<PII> v[N];
int dis[N][2], mi[N][2], mx[N][2];
bool st[N][2];
int ans = 0;
priority_queue<PII, vector<PII>, greater<PII>> q;
void dijkstra(int u, int op)
{
dis[u][op] = 0;
q.push({dis[u][op], u});
while (q.size())
{
int t = q.top().second;
q.pop();
if (st[t][op])
continue;
st[t][op] = true;
for (int i = 0; i < v[t].size(); i++)
{
int z1 = v[t][i].first, z2 = v[t][i].second;
if (dis[z1][op] > dis[t][op] + 1)
{
dis[z1][op] = dis[t][op] + 1;
mi[z1][op] = min({mi[t][op], mi[z1][op], z2});
mx[z1][op] = max({mx[t][op], mx[z1][op], z2});
q.push({dis[z1][op], z1});
}
else if (dis[z1][op] == dis[t][op] + 1)
{
mi[z1][op] = min({mi[t][op], mi[z1][op], z2});
mx[z1][op] = max({mx[t][op], mx[z1][op], z2});
}
}
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
while (m--)
{
int x, y, z;
cin >> x >> y >> z;
v[x].push_back({y, z});
v[y].push_back({x, z});
}
memset(dis, 0x3f, sizeof dis);
memset(mi, 0x3f, sizeof mi);
dijkstra(1, 0);
dijkstra(n, 1);
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < v[i].size(); j++)
{
int t1 = i, t2 = v[i][j].first, z = v[i][j].second;
if (dis[t1][0] + 1 + dis[t2][1] == dis[n][0])
{
int a1 = max(mx[t1][0], z) - min(mi[t2][1], z);
int a2 = max(mx[t2][1], z) - min(mi[t1][0], z);
ans = max({ans, a1, a2});
}
}
}
cout << ans << '\n';
return 0;
}
但是通过遍历所有最短路的路径,来维护答案的这个过程非常难想到,也就是那两个 for 循环那里。正常来说,直接遍历最短路上的点来更新答案更容易想到。所以就要学会如何存点。对于一个最短路径,如果我们是存任意一条路径的话会比较容易,只需加上一句 path[v] = u。但是这个题明显要把所有最短路上的点都存下来,想到用 vector。
相关链接:最短路的路径保存
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
typedef pair<int, int> PII;
int T, n, m;
vector<PII> v[N];
int dis[N][2], mi[N][2], mx[N][2];
bool st[N][2];
vector<int>path[N];
int ans = 0;
priority_queue<PII, vector<PII>, greater<PII>> q;
void dijkstra(int u, int op)
{
dis[u][op] = 0;
q.push({dis[u][op], u});
while (q.size())
{
int t = q.top().second;
q.pop();
if (st[t][op])
continue;
st[t][op] = true;
for (int i = 0; i < v[t].size(); i++)
{
int z1 = v[t][i].first, z2 = v[t][i].second;
if (dis[z1][op] > dis[t][op] + 1)
{
dis[z1][op] = dis[t][op] + 1;
mi[z1][op] = min({mi[t][op], mi[z1][op], z2});
mx[z1][op] = max({mx[t][op], mx[z1][op], z2});
q.push({dis[z1][op], z1});
if(op == 0) //一定要注意 op == 0,因为会正反跑两边,不 if 就跑乱了
{
path[z1].clear();
path[z1].push_back(t);
}
}
else if (dis[z1][op] == dis[t][op] + 1)
{
mi[z1][op] = min({mi[t][op], mi[z1][op], z2});
mx[z1][op] = max({mx[t][op], mx[z1][op], z2});
if(op == 0) path[z1].push_back(t);
}
}
}
}
void dfs(int u)
{
int a1 = mx[u][0] - mi[u][1];
int a2 = mx[u][1] - mi[u][0];
ans = max({ans, a1, a2});
for(int i = 0; i < path[u].size(); i ++)
{
dfs(path[u][i]);
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
while (m--)
{
int x, y, z;
cin >> x >> y >> z;
v[x].push_back({y, z});
v[y].push_back({x, z});
}
memset(dis, 0x3f, sizeof dis);
memset(mi, 0x3f, sizeof mi);
dijkstra(1, 0);
dijkstra(n, 1);
// 从 1 到 n 所有最短路径上的点
dfs(n);
cout << ans << '\n';
return 0;
}
最后再附上一份不断修改 bug 和思考过程的代码QAQ
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
typedef pair<int, int> PII;
int T, n, m;
vector<PII> v[N];
int dis[N][2], mi[N][2], mx[N][2];
bool st[N][2], sc[N];
vector<int>p[N];
int ans = 0;
priority_queue<PII, vector<PII>, greater<PII>> q;
void dijkstra(int u, int op)
{
dis[u][op] = 0;
q.push({dis[u][op], u});
while (q.size())
{
int t = q.top().second;
q.pop();
if (st[t][op])
continue;
st[t][op] = true;
for (int i = 0; i < v[t].size(); i++)
{
int z1 = v[t][i].first, z2 = v[t][i].second;
if (dis[z1][op] > dis[t][op] + 1)
{
dis[z1][op] = dis[t][op] + 1;
// mi[z1][op] = min({mi[t][op], z2});
// mx[z1][op] = max({mx[t][op], z2});
mi[z1][op] = min({mi[t][op], mi[z1][op], z2});
mx[z1][op] = max({mx[t][op], mx[z1][op], z2});
// cout << t << " " << z1 << " " << mi[z1][op] << " " << mx[z1][op] << '\n';
q.push({dis[z1][op], z1});
if(op == 0)
{
p[z1].clear();
p[z1].push_back(t);
}
}
else if (dis[z1][op] == dis[t][op] + 1)
{
// mi[z1][op] = min({mi[t][op], z2});
// mx[z1][op] = max({mx[t][op], z2});
mi[z1][op] = min({mi[t][op], mi[z1][op], z2});
mx[z1][op] = max({mx[t][op], mx[z1][op], z2});
if(op == 0) p[z1].push_back(t);
// cout << t << " " << z1 << " " << mi[z1][op] << " " << mx[z1][op] << '\n';
}
}
}
}
void dfs(int u)
{
int a1 = mx[u][0] - mi[u][1];
int a2 = mx[u][1] - mi[u][0];
ans = max({ans, a1, a2});
for(int i = 0; i < p[u].size(); i ++)
{
// if(sc[p[u][i]]) continue;
// sc[p[u][i]] = true;
dfs(p[u][i]);
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
while (m--)
{
int x, y, z;
cin >> x >> y >> z;
v[x].push_back({y, z});
v[y].push_back({x, z});
}
memset(dis, 0x3f, sizeof dis);
memset(mi, 0x3f, sizeof mi);
dijkstra(1, 0);
// cout << '\n';
dijkstra(n, 1);
// cout << "\n";
//
dfs(n);
// cout << sc[1];
// for(int i = 2; i <= n-1; i ++)
// {
// int a1 = mx[i][0] - mi[i][1];
// int a2 = mx[i][1] - mi[i][0];
// ans = max({ans, a1, a2});
// }
// for (int i = 1; i <= n; i++)
// {
// for (int j = 0; j < v[i].size(); j++)
// {
// int t1 = i, t2 = v[i][j].first, z = v[i][j].second;
// if (dis[t1][0] + 1 + dis[t2][1] == dis[n][0])
// {
// // int mxx = max({mx[t1][0], z, mx[t2][1]});
// // int mii = min({mi[t1][0], z, mi[t2][1]});
// int a1 = max(mx[t1][0], z) - min(mi[t2][1], z);
// int a2 = max(mx[t2][1], z) - min(mi[t1][0], z);
// // if(ans < mxx - mii)
// // {
// // cout << t1 << " " << t2 << " " << mxx << " " << mii << '\n';
// // cout << mx[t1][0] << " " << z << " " << mx[t2][1] << '\n';
// // cout << mi[t1][0] << " " << z << " " << mi[t2][1] << "\n";
// // }
// ans = max({ans, a1, a2});
// }
// }
// }
cout << ans << '\n';
return 0;
}