题目链接
2024-2025 ICPC Northwestern European Regional Programming Contest (NWERC 2024)
A. Alphabetical Aristocrats
题意
给定 行字符串,从第一个大写字符开始计算有效字符,按字典序进行排序
题解
签到,按题意模拟即可,注意在输入 后,使用 将第一行的回车读入缓存区
代码
void idol_produce(int testCase) {
/*Code Here*/
int n;
cin >> n;
cin.get();
vector<pair<string, string>> a(n + 1);
for (int i = 1; i <= n; i++) {
getline(cin, a[i].second);
for (int j = 0; j < a[i].second.size(); j++) {
if (a[i].second[j] >= 'A' && a[i].second[j] <= 'Z') {
a[i].first = a[i].second.substr(j);
break;
}
}
}
sort(a.begin() + 1, a.end());
for (int i = 1; i <= n; i++) {
cout << a[i].second << '\n';
}
}
E. Evolving Etymology
题意
给定一个长度为 的初始的字符串 ,按以下规则构造成一个新的字符串 :
- 将两个 合成一个字符串 即:
- 为 所有的偶数下标的字符集,即
重复 次过程,输出最后的
数据范围
,
题解
第一次过程获得的字符串为:
第二次过程获得的字符串为:
第三次过程获得的字符串为:
因此,第 次过程获得的字符串为:
总时间复杂度为 ,若预处理 将优化到
代码
void idol_produce(int testCase) {
/*Code Here*/
long long n, k;
cin >> n >> k;
mod = n;
string s;
cin >> s;
string tmp;
for(int i = 0; i < s.size(); i++){
tmp += s[qpow(2, k) * i % n];
}
cout << tmp << '\n';
}
J. Jib Job
题意
给定 个不同位置和高度的起重机塔。在每台起重机顶部放置一个吊臂,使得:
- 任何臂架都不能与任何其他起重机塔碰撞
- 臂架不能超过起重机的高度,且为整数长度
- 使可达的面积最大
数据范围
题解
每个起重机的最大臂长为所有与比它高的起重机的距离以及它本身高度的最小值
由于 ,所以直接模拟即可
代码
void idol_produce(int testCase) {
/*Code Here*/
int n;
cin >> n;
vector<int>x(n + 1), y(n + 1), h(n + 1);
for(int i = 1;i <= n;i++){
cin >> x[i] >> y[i] >> h[i];
}
for(int i = 1;i <= n; i++){
int ans = h[i];
for(int j = 1; j <= n; j++){
if(h[j] <= h[i]){
continue;
}
ans = min(ans, (int)sqrt(pow(x[i] - x[j],2) + pow(y[i] - y[j], 2)));
}
cout << ans << '\n';
}
}
D. Dutch Democracy
题意
给定 个数字,计所有满足以条件的子集的数目:
- 子集数字之和严格大于总和的一半
- 去掉子集的最小值后子集之和小于等于总和的一半
数据范围
,
题解
从大到小dp每一个可能的和的子集数量,转移方程为:
由于是从大到小dp,此时所有新增的子集的最小值一定是 ,那么根据题意更新答案即可
时间复杂度为
代码
void idol_produce(int testCase) {
/*Code Here*/
int n;
cin >> n;
vector<long long> a(n);
long long sum = 0;
long long ans = 0;
for (int i = 0; i < n; i++) {
cin >> a[i];
sum += a[i];
}
sort(a.begin(), a.end(), greater<long long>());
vector<ll> f(sum + 1);
long long half = sum / 2;
for (int i = 0; i < n; i++) {
for (int j = half; j >= 0; j--) {
if (f[j] > 0) {
f[j + a[i]] += f[j];
if (j + a[i] > half) {
ans += f[j];
}
}
}
f[a[i]] += 1;
if (a[i] > half) {
ans += 1;
}
}
cout << ans << '\n';
}
L. Limited Library
题意
有 个书架, 本书,书架和书都有各自的高度,书只能放在比它高的书架里。
如果一个书架放满书,那么最多放 本书,如果一个书架放艺术品,那么最多放 本书
求保证书全放在书架的情况下,最多放几个书架的艺术品
题解
在确定放几件艺术品的情况下,显然让最矮的书架放艺术品更优,所以二分放艺术品的书架数量即可
代码
void idol_produce(int testCase) {
/*Code Here*/
int n, m, x, y;
cin >> n >> m >> x >> y;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
vector<int> b(m + 2);
for (int i = 1; i <= m; i++) {
cin >> b[i];
}
b[m + 1] = INF;
int l = 0, r = n;
int ans = -1;
sort(a.begin() + 1, a.end());
sort(b.begin() + 1, b.end());
auto check = [&](int mid) {
int book = 1;
for (int i = 1; i <= mid; i++) {
for (int j = 1; j <= y; j++) {
if (a[i] >= b[book]) {
book++;
}
if (book == m + 1) {
return true;
}
}
}
for (int i = mid + 1; i <= n; i++) {
for (int j = 1; j <= x; j++) {
if (a[i] >= b[book]) {
book++;
}
if (book == m + 1) {
return true;
}
}
}
return false;
};
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
if (ans == -1) {
cout << "impossible\n";
} else {
cout << ans << '\n';
}
}
F. Flowing Fountain
题意
模拟香槟塔的倒入流程
题解
用单调栈来初始化每一层塔所对应的下一层塔的位置,用并查集实现倒入流程的路径压缩
代码
void idol_produce(int testCase) {
/*Code Here*/
int n, q;
cin >> n >> q;
vector<ll> siz(n + 2);
vector<ll> val(n + 2);
vector<ll> nex(n + 2);
for (int i = 1; i <= n; i++) {
cin >> siz[i];
val[i] = 0;
}
siz[n + 1] = 1e18;
vector<int> p;
for (int i = 1; i <= n + 1; i++) {
while (!p.empty() && siz[i] > siz[p.back()]) {
nex[p.back()] = i;
p.pop_back();
}
p.emplace_back(i);
}
auto get_next = [&](int x, auto& get_next) {
if (val[nex[x]] < siz[nex[x]]) {
return nex[x];
} else {
return nex[x] = get_next(nex[x], get_next);
}
};
while (q--) {
char op;
cin >> op;
if (op == '+') {
ll l, x;
cin >> l >> x;
if (val[l] + x <= siz[l]) {
val[l] += x;
} else {
x -= siz[l] - val[l];
val[l] = siz[l];
while (x) {
l = get_next(l, get_next);
ll nextval = val[l] + x;
if (nextval <= siz[l]) {
val[l] = nextval;
break;
} else {
x -= siz[l] - val[l];
val[l] = siz[l];
}
}
}
}
else{
int x;
cin >> x;
cout << val[x] << '\n';
}
}
}
H. Hash Collision
题意
给定一个范围 和一个散列函数 ,这个散列函数接受两个 的参数 , , 表示将初始值 进行 次散列化,并返回一个范围为 的散列值,在 次询问内找到一对 , 满足
题解
- 询问 , ,得到
- 如果 ,那么就找到一对答案
- 否则必然满足 ,询问 ,得到
- 此时满足
代码
void idol_produce(int testCase) {
/*Code Here*/
int n;
cin >> n;
cout << "? " << n << ' ' << 1 << endl;
int x;
// f(n, 1)
cin >> x;
if (x == n) {
cout << "! " << n << ' ' << 1 << endl;
return;
}
cout << "? " << n - x << ' ' << 1 << endl;
// f(n - x, 1)
int y;
cin >> y;
// f(x, f(n - x, 1)) = f(n, 1) = x
// f(x, y) = x
cout << "! " << x << ' ' << y << endl;
}
K. Kruidnoten
题意
在无向图中,其中一些点有 的概率成为途径点,每个点之间的概率独立计算,问在至少经过一个途径点的情况下的期望最短路径是多少
题解
当选择其中一个点作为路径上的途径点时最优时,必然满足经过其他途径点的且比这条路径更短的路径上的途径点都在 的概率下未能成为途径点,且这个点在 的概率下成为了途径点
故使用双向Dijkstra算法求出每个途径点所在的最短路径,并排序。
得到
答案为
代码
constexpr int MAXN = 1e6 + 100;
struct edge {
ll to, weight;
};
struct node {
ll id, weight;
bool operator<(const node& rhs) const { return weight > rhs.weight; }
};
vector<edge> e[MAXN];
vector<ll> dijkstra(int s) {
vector<ll> dis(MAXN, 1e18);
vector<bool> vis(MAXN, false);
dis[s] = 0;
priority_queue<node> q;
q.push({s, 0});
while (!q.empty()) {
auto [u, d] = q.top();
q.pop();
if (vis[u]) continue;
vis[u] = true;
for (auto [v, w] : e[u]) {
if (dis[v] > d + w) {
dis[v] = d + w;
q.push({v, dis[v]});
}
}
}
return dis;
}
struct reslut {
ll dis, id;
long double p;
bool operator<(const reslut& rhs) const { return dis < rhs.dis; }
};
void idol_produce(int testCase) {
/*Code Here*/
int n, m, k;
cin >> n >> m >> k;
vector<reslut>res(k + 1);
for (int i = 1; i <= m; i++) {
ll u, v, w;
cin >> u >> v >> w;
e[u].push_back({v, w});
e[v].push_back({u, w});
}
for (int i = 1; i <= k; i++) {
cin >> res[i].id >> res[i].p;
}
if (!any_of(res.begin() + 1, res.end(), [](const reslut& x) { return x.p == 1; })){
cout << "impossible\n";
return;
}
auto dis = dijkstra(1);
for(int i = 1; i <= k; i++){
res[i].dis = dis[res[i].id];
}
dis = dijkstra(n);
for(int i = 1; i <= k; i++){
res[i].dis += dis[res[i].id];
}
sort(res.begin() + 1, res.end());
long double ans = 0;
long double pre = 1;
for(int i = 1;i <= k; i++){
ans += res[i].dis * pre * res[i].p;
pre *= (1.0 - res[i].p);
}
cout << fixed << setprecision(10) << ans << '\n';
}
C. Connect Five
题意
矩阵上有5个点,用最短的路径连接这5个点满足每个点之间的路径长度最短
题解
注意到,对于最左侧的点,必然有一条直线向右的路径直到与另一个点在同一条直线上。因此可以将最左侧的点向右平移,这对于上下左右方向都同理。
如果操作后使得多个点在同一个位置,那么合并这多个点,因为这多个点接下来一定共享接下来的路径
重复操作,直到没有点可以缩短和合并
所有操作完成后,剩下的点集必然构成一个点,一个矩阵,或两个相邻矩阵,根据不同的结果进行划分即可。
代码
void idol_produce(int testCase) {
/*Code Here*/
vector<pair<ll, ll> > a(5);
for (int i = 0; i < 5; i++) {
cin >> a[i].first >> a[i].second;
}
ll ans = 0;
for (int i = 1; i <= 100 && a.size() > 1; i++) {
sort(a.begin(), a.end(), [](pair<int, int> x, pair<int, int> y) {
if(x.first == y.first){
return x.second < y.second;
}
return x.first < y.first;
});
ans += a[1].first - a[0].first;
a[0].first = a[1].first;
ans += a[a.size() - 1].first - a[a.size() - 2].first;
a[a.size() - 1].first = a[a.size() - 2].first;
sort(a.begin(), a.end(), [](pair<int, int> x, pair<int, int> y) {
if(x.second == y.second){
return x.first < y.first;
}
return x.second < y.second;
});
ans += a[1].second - a[0].second;
a[0].second = a[1].second;
ans += a[a.size() - 1].second - a[a.size() - 2].second;
a[a.size() - 1].second = a[a.size() - 2].second;
sort(a.begin(), a.end());
a.erase(unique(a.begin(), a.end()), a.end());
}
if(a.size() == 1){
cout << ans << '\n';
return;
}
assert(a.size() >= 4);
set<int>s1, s2;
for(int i = 0; i < a.size(); i++){
s1.insert(a[i].first);
s2.insert(a[i].second);
}
assert(s1.size() <= 3 && s2.size() <= 3);
assert(s1.size() > 1 && s2.size() > 1);
if(s1.size() <= 2 || s2.size() <= 2){
ans += 2 * (*s1.rbegin() - *s1.begin());
ans += 2 * (*s2.rbegin() - *s2.begin());
cout << ans << '\n';
return;
}
else{
ans += 2 * (*s1.rbegin() - *s1.begin());
ans += 2 * (*s2.rbegin() - *s2.begin());
int l1 = *s1.begin(), r1 = *s1.rbegin(), l2 = *s2.begin(), r2 = *s2.rbegin();
for(int i = 0;i < a.size(); i++){
if(a[i].first == l1 || a[i].first == r1 || a[i].second == l2 || a[i].second == r2){
continue;
}
ans += min(*s1.rbegin() - *s1.begin(), *s2.rbegin() - *s2.begin());
break;
}
cout << ans << '\n';
return;
}
}
M. Mouse Trap
题意
凸多边形中任意三个点会组成一个三角形覆盖凸多边形的一部分区域,求凸多边形内部的一个点期望被多少个三角形覆盖
题解
题目可以直接转化成求凸多边形内所有的三元组点构成的三角形面积之和与凸多边形的面积的除数
重点在于如何求出所有的三角形面积之和,我们转化公式:
代码
struct point{
double x, y;
constexpr double operator^(point & rhs) const{
return x * rhs.y - y * rhs.x;
}
};
void idol_produce(int testCase) {
/*Code Here*/
int n;
cin >> n;
vector<point> p(n + 10);
vector<double> prex(n + 10), prey(n + 10), sufx(n + 10), sufy(n + 10);
for (int i = 1; i <= n; i++) {
cin >> p[i].x >> p[i].y;
}
prex[1] = p[1].x;
sufx[n] = p[n].x;
for (int i = 2; i <= n; i++) {
prex[i] = prex[i - 1] + p[i].x;
}
for (int i = n - 1; i >= 1; i--) {
sufx[i] = sufx[i + 1] + p[i].x;
}
prey[1] = p[1].y;
sufy[n] = p[n].y;
for (int i = 2; i <= n; i++) {
prey[i] = prey[i - 1] + p[i].y;
}
for (int i = n - 1; i >= 1; i--) {
sufy[i] = sufy[i + 1] + p[i].y;
}
auto get_area = [&]() -> double{
double ans = 0;
for(int i = 1;i <= n; i ++){
int nxt = (i + 1);
if (i == n) nxt = 1;
ans += (p[i] ^ p[nxt]);
}
return abs(ans) / 2;
};
double area = get_area();
double ans = 0;
for(int i = 1;i <= n; i++){
point a, b;
a.x = (i - 1) * p[i].x - prex[i - 1];
a.y = (i - 1) * p[i].y - prey[i - 1];
b.x = sufx[i + 1] - (n - i) * p[i].x;
b.y = sufy[i + 1] - (n - i) * p[i].y;
ans += (a ^ b) / 2;
}
cout << fixed << setprecision(10) << ans / area << '\n';
}